diff --git a/seminar/views/views_all.py b/seminar/views/views_all.py index 3d31dfc4..6a3f1c3a 100644 --- a/seminar/views/views_all.py +++ b/seminar/views/views_all.py @@ -324,10 +324,16 @@ class ArchivView(generic.ListView): ### Výsledky -# ze seznamu obsahujícího sestupně setřízené body řešitelů za daný ročník -# vytvoří seznam s pořadími (včetně 3.-5. a pak 2 volná místa atp.), -# podle toho, jak jdou za sebou ve výsledkovce def sloupec_s_poradim(setrizene_body): + """ Ze seznamu obsahujícího sestupně setřízené body řešitelů za daný ročník + vytvoří seznam s pořadími (včetně 3.-5. a pak 2 volná místa atp.), + podle toho, jak jdou za sebou ve výsledkovce. + Parametr: + setrizene_body (seznam integerů): sestupně setřízená čísla + + Výstup: + sloupec_s_poradim (seznam stringů) + """ # ze seznamu obsahujícího setřízené body spočítáme sloupec s pořadím aktualni_poradi = 1 @@ -358,20 +364,28 @@ def sloupec_s_poradim(setrizene_body): aktualni_poradi = aktualni_poradi + velikost_skupiny return sloupec_s_poradim -# vrátí všechna čísla daného ročníku def cisla_rocniku(rocnik, jen_verejne=True): + """ Vrátí všechna čísla daného ročníku. + Parametry: + rocnik (Rocnik): ročník semináře + jen_verejne (bool): zda se mají vrátit jen veřejná, nebo všechna čísla + Vrátí: + seznam objektů typu Cislo + """ if jen_verejne: return rocnik.verejna_cisla() else: return rocnik.cisla.all() -# pro daný problém vrátí jeho nejvyšší nadproblém def hlavni_problem(problem): + """ Pro daný problém vrátí jeho nejvyšší nadproblém.""" while not(problem.nadproblem == None): problem = problem.nadproblem return problem def hlavni_problemy_rocniku(rocnik, jen_verejne=True): + """ Pro zadaný ročník vrátí hlavní problémy ročníku, + tj. ty, které už nemají nadproblém.""" hlavni_problemy = [] for cislo in cisla_rocniku(rocnik, jen_verejne): for problem in hlavni_problemy_cisla(cislo): @@ -382,8 +396,8 @@ def hlavni_problemy_rocniku(rocnik, jen_verejne=True): return hlavni_problemy -# vrátí list všech problémů s body v daném čísle, které již nemají nadproblém def hlavni_problemy_cisla(cislo): + """ Vrátí seznam všech problémů s body v daném čísle, které již nemají nadproblém. """ hodnoceni = cislo.hodnoceni.select_related('problem', 'reseni').all() # hodnocení, která se vážou k danému číslu @@ -405,36 +419,71 @@ def hlavni_problemy_cisla(cislo): return hlavni_problemy -# vrátí slovník řešitel:body obsahující počty bodů zadaných řešitelů za daný ročník -def body_resitelu_odjakziva(rocnik, resitele): +def body_resitelu(resitele, za, odjakziva=True): + """ Funkce počítající počty bodů pro zadané řešitele, + buď odjakživa do daného ročníku/čísla anebo za daný ročník/číslo. + Parametry: + resitele (seznam obsahující položky typu Resitel): aktivní řešitelé + za (Rocnik/Cislo): za co se mají počítat body + (generování starších výsledkovek) + odjakziva (bool): zda se mají počítat body odjakživa, nebo jen za číslo/ročník + zadané v "za" + Výstup: + slovník (Resitel.id):body + """ + resitele_id = [r.id for r in resitele] + # Zjistíme, typ objektu v parametru "za" + if isinstance(za, Rocnik): + cislo = None + rocnik = za + rok = rocnik.prvni_rok + elif isinstance(za, Cislo): + cislo = za + rocnik = None + rok = cislo.rocnik.prvni_rok + else: + assert True, "body_resitelu: za není ani číslo ani ročník." + + + # Kvůli rychlosti používáme sčítáme body už v databázi, viz + # https://docs.djangoproject.com/en/3.0/topics/db/aggregation/, + # sekce Filtering on annotations (protože potřebujeme filtrovat výsledky + # jen do nějakého data, abychom uměli správně nagenerovat výsledkovky i + # za historická čísla. + + # Níže se vytváří dotaz na součet bodů za správně vyfiltrovaná hodnocení, + # který se použije ve výsledném dotazu. + if cislo and odjakziva: # Body se sčítají odjakživa do zadaného čísla. + # Vyfiltrujeme všechna hodnocení, která jsou buď ze starších ročníků, + # anebo ze stejného ročníku, jak je zadané číslo, tam ale sčítáme jen + # pro čísla s pořadím nejvýše stejným, jako má zadané číslo. + body_k_zapocteni = Sum('reseni__hodnoceni__body', + filter=( Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok__lt=rok) | + Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok=rok, + reseni__hodnoceni__cislo_body__poradi__lte=cislo.poradi) )) + elif cislo and not odjakziva: # Body se sčítají za dané číslo. + body_k_zapocteni = Sum('reseni__hodnoceni__body', + filter=( Q(reseni__hodnoceni__cislo_body=cislo) )) + elif rocnik and odjakziva: # Spočítáme body za starší ročníky až do zadaného včetně. + body_k_zapocteni = Sum('reseni__hodnoceni__body', + filter= Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok__lte=rok)) + elif rocnik and not odjakziva: # Spočítáme body za daný ročník. + body_k_zapocteni = Sum('reseni__hodnoceni__body', + filter= Q(reseni__hodnoceni__cislo_body__rocnik=rocnik)) + else: + assert True, "body_resitelu: Neplatná kombinace za a odjakživa." + # Následující řádek přidá ke každému řešiteli údaj ".body" se součtem jejich bodů - resitele_s_body = Resitel.objects.annotate(body=Sum('reseni__hodnoceni__body')) - # Teď jen z QuerySetu řešitelů anotovaných body vygenerujeme slovník indexovaný řešitelským id obsahující body - # ... ale jen ro řešitele, které dostaneme jako parametr. - # TODO: Zjistit, co ten parametr říká a proč je potřeba - body_odjakziva = {int(res.id) : res.body for res in resitele_s_body if res in resitele} - return body_odjakziva - -# vrátí slovník řešitel:body obsahující počty bodů zadaných řešitelů za daný ročník -def body_resitelu_za_rocnik(rocnik, aktivni_resitele): - body_za_rocnik = {} - # inicializujeme na 0 pro všechny aktivní řešitele - for ar in aktivni_resitele: - body_za_rocnik[ar.id] = 0 - - # spočítáme body řešitelům přes všechna řešení s hodnocením v daném ročníku - print("Před dotazem:{}".format(time.time())) - reseni = Reseni.objects.prefetch_related('resitele', 'hodnoceni_set').filter(hodnoceni__cislo_body__rocnik=rocnik) - print("Po dotazu:{}".format(time.time())) - for res in reseni: - for resitel in res.resitele.all(): - for hodn in res.hodnoceni_set.all(): - pricti_body(body_za_rocnik, resitel, hodn.body) - print("Po for-cyklu:{}".format(time.time())) - return body_za_rocnik + resitele_s_body = Resitel.objects.filter(id__in=resitele_id).annotate( + body=body_k_zapocteni) + # Teď jen z QuerySetu řešitelů anotovaných body vygenerujeme slovník + # indexovaný řešitelským id obsahující body. + # Pokud jsou body None, nahradíme za 0. + slovnik = {int(res.id) : (res.body if res.body else 0) for res in resitele_s_body} + return slovnik class RadekVysledkovkyRocniku(object): - """Obsahuje věci, které se hodí vědět při konstruování výsledkovky. + """ Obsahuje věci, které se hodí vědět při konstruování výsledkovky. Umožňuje snazší práci v templatu (lepší, než seznam).""" def __init__(self, poradi, resitel, body_cisla_sezn, body_rocnik, body_odjakziva, rok): @@ -447,7 +496,7 @@ class RadekVysledkovkyRocniku(object): self.titul = resitel.get_titul(body_odjakziva) def vysledkovka_rocniku(rocnik, jen_verejne=True): - """Přebírá ročník (např. context["rocnik"]) a vrací výsledkovou listinu ve + """ Přebírá ročník (např. context["rocnik"]) a vrací výsledkovou listinu ve formě vhodné pro šablonu "seminar/vysledkovka_rocniku.html" """ @@ -460,7 +509,6 @@ def vysledkovka_rocniku(rocnik, jen_verejne=True): #.filter(hodnoceni_set__rocnik__eq=cislo_rocnik) cisla = cisla_rocniku(rocnik, jen_verejne) body_cisla_slov = {} - print("Jen veřejná: {}, čísla: {}".format(jen_verejne, cisla)) for cislo in cisla: # získáme body za číslo _, cislobody = secti_body_za_cislo(cislo, aktivni_resitele) @@ -476,7 +524,7 @@ def vysledkovka_rocniku(rocnik, jen_verejne=True): poradi = sloupec_s_poradim(setrizene_body) # získáme body odjakživa - resitel_odjakzivabody_slov = body_resitelu_odjakziva(rocnik, aktivni_resitele) + resitel_odjakzivabody_slov = body_resitelu(aktivni_resitele, rocnik) # vytvoříme jednotlivé sloupce výsledkovky radky_vysledkovky = [] @@ -495,11 +543,7 @@ def vysledkovka_rocniku(rocnik, jen_verejne=True): setrizene_body[i], # body za ročník (spočítané výše s pořadím) resitel_odjakzivabody_slov[ar_id], # body odjakživa rocnik) # ročník semináře pro získání ročníku řešitele - print("{}: číslobody - {}, ročníkbody - {}," - "odjakživabody - {}".format(radek.resitel, radek.body_cisla_sezn, - radek.body_rocnik, radek.body_celkem_odjakziva)) radky_vysledkovky.append(radek) - print("Přikládám {}-tý řádek.".format(i)) i += 1 return radky_vysledkovky @@ -575,8 +619,8 @@ class RadekVysledkovkyCisla(object): self.titul = resitel.get_titul(body_odjakziva) -# přiřazuje danému řešiteli body do slovníku def pricti_body(slovnik, resitel, body): + """ Přiřazuje danému řešiteli body do slovníku. """ # testujeme na None (""), pokud je to první řešení # daného řešitele, předěláme na 0 # (v dalším kroku přičteme reálný počet bodů), @@ -587,15 +631,16 @@ def pricti_body(slovnik, resitel, body): slovnik[resitel.id] += body def secti_body_za_rocnik(rocnik, aktivni_resitele): - # spočítáme všem řešitelům jejich body za ročník - resitel_rocnikbody_slov = body_resitelu_za_rocnik(rocnik, aktivni_resitele) + """ Spočítá body za ročník, setřídí je sestupně a vrátí jako seznam.""" + # spočítáme všem řešitelům jejich body za ročník (False => ne odjakživa) + resitel_rocnikbody_slov = body_resitelu(aktivni_resitele, rocnik, False) # zeptáme se na dvojice (řešitel, body) za ročník a setřídíme sestupně resitel_rocnikbody_sezn = sorted(resitel_rocnikbody_slov.items(), key = lambda x: x[1], reverse = True) return resitel_rocnikbody_sezn -# spočítá u řešitelů body za číslo a za jednotlivé hlavní problémy (témata) def secti_body_za_cislo(cislo, aktivni_resitele, hlavni_problemy=None): + """ Spočítá u řešitelů body za číslo a za jednotlivé hlavní problémy (témata).""" # TODO setřídit hlavní problémy čísla podle id, ať jsou ve stejném pořadí pokaždé # pro každý hlavní problém zavedeme slovník s body za daný hlavní problém # pro jednotlivé řešitele (slovník slovníků hlavních problémů) @@ -658,8 +703,7 @@ def vysledkovka_cisla(cislo, context=None): resitel_rocnikbody_sezn = secti_body_za_rocnik(cislo.rocnik, aktivni_resitele) # získáme body odjakživa - resitel_odjakzivabody_slov = body_resitelu_odjakziva(cislo.rocnik, - aktivni_resitele) + resitel_odjakzivabody_slov = body_resitelu(aktivni_resitele, cislo) # řešitelé setřídění podle bodů za číslo sestupně setrizeni_resitele_id = [dvojice[0] for dvojice in resitel_rocnikbody_sezn] @@ -686,10 +730,7 @@ def vysledkovka_cisla(cislo, context=None): setrizeni_resitele_body[i], # body za ročník (spočítané výše s pořadím) resitel_odjakzivabody_slov[ar_id], # body odjakživa cislo.rocnik) # ročník semináře pro zjištění ročníku řešitele - print("{}: body za problémy - {}, číslobody - {}, ročníkbody - {}, odjakživabody - {}".format(radek.resitel, - radek.body_problemy_sezn, radek.body_cislo, radek.body_rocnik, radek.body_celkem_odjakziva)) radky_vysledkovky.append(radek) - print("Přikládám {}-tý řádek.".format(i)) i += 1 # vytahané informace předáváme do kontextu @@ -698,7 +739,6 @@ def vysledkovka_cisla(cislo, context=None): context['problemy'] = hlavni_problemy #context['v_cisle_zadane'] = TODO #context['resene_problemy'] = resene_problemy - print("Předávám kontext.") return context class CisloView(generic.DetailView): @@ -736,7 +776,7 @@ class ArchivTemataView(generic.ListView): ### Generovani vysledkovky class CisloVysledkovkaView(CisloView): - "View vytvořené pro stránku zobrazující výsledkovku čísla v TeXu." + """View vytvořené pro stránku zobrazující výsledkovku čísla v TeXu.""" model = Cislo template_name = 'seminar/archiv/cislo_vysledkovka.tex' @@ -746,7 +786,7 @@ class CisloVysledkovkaView(CisloView): #vypise na stranku textovy obsah vyTeXane vysledkovky k okopirovani class RocnikVysledkovkaView(RocnikView): - "View vytvořené pro stránku zobrazující výsledkovku ročníku v TeXu." + """ View vytvořené pro stránku zobrazující výsledkovku ročníku v TeXu.""" model = Rocnik template_name = 'seminar/archiv/rocnik_vysledkovka.tex' #content_type = 'application/x-tex; charset=UTF8' @@ -832,13 +872,15 @@ def oldObalkovaniView(request, rocnik, cislo): ### Tituly def TitulyView(request, rocnik, cislo): + """ View pro stažení makra titulů v TeXu.""" rocnik_obj = Rocnik.objects.get(rocnik = rocnik) resitele = Resitel.objects.filter(rok_maturity__gte = rocnik_obj.prvni_rok) cislo_obj = Cislo.objects.get(rocnik = rocnik_obj, poradi = cislo) asciijmena = [] - jmenovci = False # detekuje, zda jsou dva řešitelé jmenovci (modulo nabodeníčka), pokud ano, vrátí se jako true - slovnik_s_body = body_resitelu_odjakziva(rocnik_obj, resitele) + jmenovci = False # detekuje, zda jsou dva řešitelé jmenovci (modulo nabodeníčka), + # pokud ano, vrátí se jako true + slovnik_s_body = body_resitelu(resitele, rocnik_obj) for resitel in resitele: resitel.titul = resitel.get_titul(slovnik_s_body[resitel.id]) @@ -886,13 +928,13 @@ class SoustredeniUcastniciBaseView(generic.ListView): class SoustredeniMailyUcastnikuView(SoustredeniUcastniciBaseView): - """Seznam e-mailů řešitelů oddělených čárkami""" + """ Seznam e-mailů řešitelů oddělených čárkami. """ model = Soustredeni_Ucastnici template_name = 'seminar/soustredeni/maily_ucastniku.txt' class SoustredeniUcastniciView(SoustredeniUcastniciBaseView): - """HTML tabulka účastníků pro tisk""" + """ HTML tabulka účastníků pro tisk. """ model = Soustredeni_Ucastnici template_name = 'seminar/soustredeni/seznam_ucastniku.html'