import seminar.models as m from django.db.models import Q, Sum, Count from seminar.utils import aktivniResitele, resi_v_rocniku, cisla_rocniku, hlavni_problem, hlavni_problemy_f, problemy_cisla, podproblemy_v_cislu import time ROCNIK_ZRUSENI_TEMAT = 25 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 sloupec_s_poradim = [] # seskupíme seznam všech bodů podle hodnot for index in range(0, len(setrizene_body)): # pokud je pořadí větší než číslo řádku, tak jsme vypsali větší rozsah a chceme # vypsat už jen prázdné místo, než dojdeme na správný řádek if (index + 1) < aktualni_poradi: sloupec_s_poradim.append("") continue velikost_skupiny = 0 # zjistíme počet po sobě jdoucích stejných hodnot while setrizene_body[index] == setrizene_body[index + velikost_skupiny]: velikost_skupiny = velikost_skupiny + 1 # na konci musíme ošetřit přetečení seznamu if (index + velikost_skupiny) > len(setrizene_body) - 1: break # pokud je velikost skupiny 1, vypíšu pořadí if velikost_skupiny == 1: sloupec_s_poradim.append("{}.".format(aktualni_poradi)) # pokud je skupina větší, vypíšu rozsah else: sloupec_s_poradim.append("{}.–{}.".format(aktualni_poradi, aktualni_poradi+velikost_skupiny-1)) # zvětšíme aktuální pořadí o tolik, kolik pozic bylo přeskočeno aktualni_poradi = aktualni_poradi + velikost_skupiny return sloupec_s_poradim def body_resitelu(resitele, za, odjakziva=True, jen_verejne=False): """ 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, m.Rocnik): cislo = None rocnik = za rok = rocnik.prvni_rok elif isinstance(za, m.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__rocnik__prvni_rok=rok, reseni__hodnoceni__cislo_body__poradi__lte=cislo.poradi) )) elif rocnik and odjakziva: # Spočítáme body za starší ročníky až do zadaného včetně. if jen_verejne: body_k_zapocteni = Sum('reseni__hodnoceni__body', filter= Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok__lte=rok, reseni__hodnoceni__cislo_body__verejna_vysledkovka=True)) else: 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. if jen_verejne: body_k_zapocteni = Sum('reseni__hodnoceni__body', filter=Q(reseni__hodnoceni__cislo_body__rocnik=rocnik, reseni__hodnoceni__cislo_body__verejna_vysledkovka=True)) else: 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 = m.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. Umožňuje snazší práci v templatu (lepší, než seznam).""" def __init__(self, poradi, resitel, body_cisla_sezn, body_rocnik, body_odjakziva, rok): self.poradi = poradi self.resitel = resitel self.rocnik_resitele = resitel.rocnik(rok) self.body_rocnik = body_rocnik self.body_celkem_odjakziva = body_odjakziva self.body_cisla_sezn = body_cisla_sezn self.titul = resitel.get_titul(body_odjakziva) def setrid_resitele_a_body(slov_resitel_body): setrizeni_resitele_id = [dvojice[0] for dvojice in slov_resitel_body] setrizene_body = [dvojice[1] for dvojice in slov_resitel_body] return setrizeni_resitele_id, setrizene_body def data_vysledkovky_rocniku(rocnik, jen_verejne=True): """ Přebírá ročník (např. context["rocnik"]) a vrací výsledkovou listinu ve formě vhodné pro šablonu "seminar/vysledkovka_rocniku.html" """ start = time.time() ## TODO možná chytřeji vybírat aktivní řešitele # aktivní řešitelé - chceme letos něco poslal, TODO později vyfiltrujeme ty, kdo mají # u alespoň jedné hodnoty něco jiného než NULL aktivni_resitele = list(resi_v_rocniku(rocnik)) cisla = cisla_rocniku(rocnik, jen_verejne) body_cisla_slov = {} for cislo in cisla: # získáme body za číslo _, cislobody = secti_body_za_cislo(cislo, aktivni_resitele) body_cisla_slov[cislo.id] = cislobody # získáme body za ročník, seznam obsahuje dvojice (řešitel_id, body) setřízené sestupně resitel_rocnikbody_sezn = secti_body_za_rocnik(rocnik, aktivni_resitele, jen_verejne=jen_verejne) # setřídíme řešitele podle počtu bodů a získáme seznam s body od nejvyšších po nenižší setrizeni_resitele_id, setrizene_body = setrid_resitele_a_body(resitel_rocnikbody_sezn) poradi = sloupec_s_poradim(setrizene_body) # získáme body odjakživa resitel_odjakzivabody_slov = body_resitelu(aktivni_resitele, rocnik, jen_verejne=jen_verejne) # vytvoříme jednotlivé sloupce výsledkovky radky_vysledkovky = [] i = 0 setrizeni_resitele_dict = {} # Tento slovnik se vyrab for r in m.Resitel.objects.filter(id__in=setrizeni_resitele_id).select_related('osoba'): setrizeni_resitele_dict[r.id] = r for ar_id in setrizeni_resitele_id: # seznam počtu bodů daného řešitele pro jednotlivá čísla body_cisla_sezn = [] for cislo in cisla: body_cisla_sezn.append(body_cisla_slov[cislo.id][ar_id]) # vytáhneme informace pro daného řešitele radek = RadekVysledkovkyRocniku( poradi[i], # pořadí setrizeni_resitele_dict[ar_id], # řešitel (z id) body_cisla_sezn, # seznam bodů za čísla 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 radky_vysledkovky.append(radek) i += 1 end = time.time() print("Vysledkovka rocniku",end-start) radky_vysledkovky = [radek for radek in radky_vysledkovky if radek.body_rocnik > 0] return radky_vysledkovky, cisla class RadekVysledkovkyCisla(object): """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_problemy_sezn, body_cislo, body_rocnik, body_odjakziva, rok, body_podproblemy, body_podproblemy_iter): self.resitel = resitel self.rocnik_resitele = resitel.rocnik(rok) self.body_cislo = body_cislo self.body_rocnik = body_rocnik self.body_celkem_odjakziva = body_odjakziva self.poradi = poradi self.body_problemy_sezn = body_problemy_sezn self.titul = resitel.get_titul(body_odjakziva) self.body_podproblemy = body_podproblemy self.body_podproblemy_iter = body_podproblemy_iter # TODELETE 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ů), # rozlišujeme tím mezi 0 a neodevzdaným řešením # Speciálně pokud jsou body None (hodnocení není obodované), vraťse # TODO nejde to udělat lépe? if body is None: return if slovnik[resitel.id] == "": slovnik[resitel.id] = 0 slovnik[resitel.id] += body def secti_body_za_rocnik(za, aktivni_resitele, jen_verejne): """ Spočítá body za ročník (celý nebo do daného čísla), setřídí je sestupně a vrátí jako seznam. Parametry: za (typu Rocnik nebo Cislo) spočítá za ročník, nebo za ročník až do daného čísla """ # 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, za, False, jen_verejne=jen_verejne) # 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 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ů) print("Scitam cislo",cislo) if hlavni_problemy is None: hlavni_problemy = hlavni_problemy_f(problemy_cisla(cislo)) def ne_clanek_ne_konfera(problem): inst = problem.get_real_instance() return not(isinstance(inst, m.Clanek) or isinstance(inst, m.Konfera)) if cislo.rocnik.rocnik < ROCNIK_ZRUSENI_TEMAT: temata_a_spol = hlavni_problemy else: temata_a_spol = list(filter(ne_clanek_ne_konfera, hlavni_problemy)) hlavni_problemy_slovnik = {} for hp in temata_a_spol: hlavni_problemy_slovnik[hp.id] = {} hlavni_problemy_slovnik[-1] = {} # zakládání prázdných záznamů pro řešitele cislobody = {} for ar in aktivni_resitele: # řešitele převedeme na řetězec pomocí unikátního id cislobody[ar.id] = "" for hp in temata_a_spol: slovnik = hlavni_problemy_slovnik[hp.id] slovnik[ar.id] = "" hlavni_problemy_slovnik[-1][ar.id] = "" hodnoceni_do_cisla = m.Hodnoceni.objects.prefetch_related('problem', 'reseni', 'reseni__resitele').filter(cislo_body=cislo) start = time.time() for hodnoceni in hodnoceni_do_cisla: prob = hodnoceni.problem nadproblem = hlavni_problem(prob) if ne_clanek_ne_konfera(nadproblem): nadproblem_slovnik = hlavni_problemy_slovnik[nadproblem.id] else: nadproblem_slovnik = hlavni_problemy_slovnik[-1] body = hodnoceni.body # a mít více řešitelů for resitel in hodnoceni.reseni.resitele.all(): if resitel not in aktivni_resitele: print("Skipping {}".format(resitel.id)) continue pricti_body(cislobody, resitel, body) pricti_body(nadproblem_slovnik, resitel, body) end = time.time() print("for cykly:", end-start) return hlavni_problemy_slovnik, cislobody def secti_body_za_cislo_podle_temat(cislo, aktivni_resitele, podproblemy=None, temata=None): """ Spočítá u řešitelů body za číslo za úlohy v jednotlivých hlavních problémech (témata).""" if temata is None: temata = hlavni_problemy_f(problemy_cisla(cislo)) if podproblemy is None: podproblemy_v_cislu(cislo, hlavni_problemy=temata) body_slovnik = {} for tema in temata: body_slovnik[tema.id] = {} for problem in podproblemy[tema.id]: body_slovnik[tema.id][problem.id] = {} body_slovnik[-1] = {} for problem in podproblemy[-1]: body_slovnik[-1][problem.id] = {} # zakládání prázdných záznamů pro řešitele for ar in aktivni_resitele: for tema in temata: for problem in podproblemy[tema.id]: body_slovnik[tema.id][problem.id][ar.id] = "" for problem in podproblemy[-1]: body_slovnik[-1][problem.id][ar.id] = "" temata = set(t.id for t in temata) hodnoceni_do_cisla = m.Hodnoceni.objects.prefetch_related('problem', 'reseni', 'reseni__resitele').filter(cislo_body=cislo) for hodnoceni in hodnoceni_do_cisla: prob = hodnoceni.problem nadproblem = hlavni_problem(prob) if nadproblem.id in temata: nadproblem_slovnik = body_slovnik[nadproblem.id] else: nadproblem_slovnik = body_slovnik[-1] problem_slovnik = nadproblem_slovnik[prob.id] body = hodnoceni.body # a mít více řešitelů for resitel in hodnoceni.reseni.resitele.all(): if resitel not in aktivni_resitele: print("Skipping {}".format(resitel.id)) continue pricti_body(problem_slovnik, resitel, body) return body_slovnik # TODELETE class FixedIterator: def next(self): return self.niter.__next__() def __init__(self, niter): self.niter = niter # TODELETE def data_vysledkovky_cisla(cislo): problemy = problemy_cisla(cislo) hlavni_problemy = hlavni_problemy_f(problemy) ## TODO možná chytřeji vybírat aktivní řešitele # aktivní řešitelé - chceme letos něco poslal, TODO později vyfiltrujeme ty, kdo mají # u alespoň jedné hodnoty něco jiného než NULL aktivni_resitele = list(resi_v_rocniku(cislo.rocnik)) # získáme body za číslo hlavni_problemy_slovnik, cislobody = secti_body_za_cislo(cislo, aktivni_resitele, hlavni_problemy) # získáme body za ročník, seznam obsahuje dvojice (řešitel_id, body) setřízené sestupně resitel_rocnikbody_sezn = secti_body_za_rocnik(cislo, aktivni_resitele, jen_verejne=True) # získáme body odjakživa resitel_odjakzivabody_slov = body_resitelu(aktivni_resitele, cislo, jen_verejne=True) # řešitelé setřídění podle bodů za číslo sestupně setrizeni_resitele_id = [dvojice[0] for dvojice in resitel_rocnikbody_sezn] # spočítáme pořadí řešitelů setrizeni_resitele_body = [dvojice[1] for dvojice in resitel_rocnikbody_sezn] poradi = sloupec_s_poradim(setrizeni_resitele_body) # vytvoříme jednotlivé sloupce výsledkovky radky_vysledkovky = [] i = 0 def ne_clanek_ne_konfera(problem): return not(isinstance(problem.get_real_instance(), m.Clanek) or isinstance(problem.get_real_instance(), m.Konfera)) if cislo.rocnik.rocnik < ROCNIK_ZRUSENI_TEMAT: temata_a_spol = hlavni_problemy else: temata_a_spol = list(filter(ne_clanek_ne_konfera, hlavni_problemy)) # získáme body u jednotlivých témat podproblemy = podproblemy_v_cislu(cislo, problemy, temata_a_spol) problemy_slovnik = secti_body_za_cislo_podle_temat(cislo, aktivni_resitele, podproblemy, temata_a_spol) # def not_empty(value): # return value != '' # # je_nejake_ostatni = any(filter(not_empty, hlavni_problemy_slovnik[-1].values())) > 0 je_nejake_ostatni = len(hlavni_problemy) - len(temata_a_spol) > 0 setrizeni_resitele_slovnik = {} setrizeni_resitele = m.Resitel.objects.filter(id__in=setrizeni_resitele_id).select_related('osoba') for r in setrizeni_resitele: setrizeni_resitele_slovnik[r.id] = r for ar_id in setrizeni_resitele_id: # získáme seznam bodů za problémy pro daného řešitele body_problemy = [] body_podproblemy = [] for hp in temata_a_spol: body_problemy.append(hlavni_problemy_slovnik[hp.id][ar_id]) body_podproblemy.append([problemy_slovnik[hp.id][it.id][ar_id] for it in podproblemy[hp.id]]) if je_nejake_ostatni: body_problemy.append(hlavni_problemy_slovnik[-1][ar_id]) body_podproblemy.append([problemy_slovnik[-1][it.id][ar_id] for it in podproblemy[-1]]) # vytáhneme informace pro daného řešitele radek = RadekVysledkovkyCisla( poradi[i], # pořadí setrizeni_resitele_slovnik[ar_id], # řešitel (z id) body_problemy, # seznam bodů za hlavní problémy čísla cislobody[ar_id], # body za číslo 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, body_podproblemy, # body všech podproblémů FixedIterator(body_podproblemy.__iter__()) # TODELETE ) # ročník semináře pro zjištění ročníku řešitele radky_vysledkovky.append(radek) i += 1 # vytahané informace předáváme do kontextu pt = [podproblemy[it.id] for it in temata_a_spol]+[podproblemy[-1]] radky_vysledkovky = [radek for radek in radky_vysledkovky if radek.body_rocnik > 0] return ( radky_vysledkovky, temata_a_spol, je_nejake_ostatni, pt, FixedIterator(pt.__iter__()) )