import abc from functools import cached_property import seminar.models as m from django.db.models import Q, Sum from seminar.utils import resi_v_rocniku, cisla_rocniku, hlavni_problem,\ hlavni_problemy_f, problemy_cisla, podproblemy_v_cislu ROCNIK_ZRUSENI_TEMAT = 25 class FixedIterator: def next(self): return self.niter.__next__() def __init__(self, niter): self.niter = niter def body_resitelu( za=None, do: m.Deadline = None, od: m.Deadline = None, jen_verejne: bool = True, resitele=None, null=0 ) -> dict[int, int]: filtr = Q() if jen_verejne: filtr &= Q(reseni__hodnoceni__deadline_body__verejna_vysledkovka=True) # Zjistíme, typ objektu v parametru "za" if isinstance(za, m.Rocnik): filtr &= Q(reseni__hodnoceni__deadline_body__cislo__rocnik=za) elif isinstance(za, m.Cislo): filtr &= Q(reseni__hodnoceni__deadline_body__cislo=za) if do: filtr &= Q(reseni__hodnoceni__deadline_body__lte=do) if od: filtr &= Q(reseni__hodnoceni__deadline_body__gte=od) resiteleQuery = m.Resitel.objects.all() if resitele is not None: resitele_id = [r.id for r in resitele] resiteleQuery = resiteleQuery.filter(id__in=resitele_id) # Přidáme ke každému řešiteli údaj ".body" se součtem jejich bodů resitele_s_body = resiteleQuery.annotate( body=Sum('reseni__hodnoceni__body', filter=filtr)) # 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 null) for res in resitele_s_body } return slovnik class Vysledkovka(abc.ABC): jen_verejne: bool rocnik: m.Rocnik do_deadlinu: m.Deadline @property @abc.abstractmethod def aktivni_resitele(self) -> list[m.Resitel]: ... @cached_property def resitele_s_body_za_rocnik_setrizeny_seznam(self) -> list[tuple[int, int]]: # spočítáme všem řešitelům jejich body za ročník resitel_body_za_rocnik_slovnik = body_resitelu( resitele=self.aktivni_resitele, za=self.rocnik, jen_verejne=self.jen_verejne, do=self.do_deadlinu ) # zeptáme se na dvojice (řešitel, body) za ročník a setřídíme sestupně resitele_s_body_za_rocnik_setrizeny_seznam = sorted( resitel_body_za_rocnik_slovnik.items(), key=lambda x: x[1], reverse=True ) return resitele_s_body_za_rocnik_setrizeny_seznam @cached_property def body_za_rocnik_seznamy(self) -> tuple[list[int], list[int]]: if len(self.resitele_s_body_za_rocnik_setrizeny_seznam) == 0: return [], [] return tuple(zip(*self.resitele_s_body_za_rocnik_setrizeny_seznam)) @cached_property def setrizeni_resitele_id(self) -> list[int]: return self.body_za_rocnik_seznamy[0] @cached_property def setrizene_body(self) -> list[int]: return self.body_za_rocnik_seznamy[1] @cached_property def resitel_body_odjakziva_slovnik(self) -> dict[int, int]: return body_resitelu(jen_verejne=self.jen_verejne, do=self.do_deadlinu) @cached_property def poradi(self): # 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(self.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 self.setrizene_body[index] == self.setrizene_body[ index + velikost_skupiny]: velikost_skupiny += 1 # na konci musíme ošetřit přetečení seznamu if (index + velikost_skupiny) > len(self.setrizene_body) - 1: break # pokud je velikost skupiny 1, vypíšu pořadí if velikost_skupiny == 1: sloupec_s_poradim.append(f"{aktualni_poradi}.") # pokud je skupina větší, vypíšu rozsah else: sloupec_s_poradim.append( f"{aktualni_poradi}.–{aktualni_poradi + velikost_skupiny - 1}." ) # zvětšíme aktuální pořadí o tolik, kolik pozic bylo přeskočeno aktualni_poradi += velikost_skupiny return sloupec_s_poradim class VysledkovkaRocniku(Vysledkovka): def __init__(self, rocnik: m.Rocnik, jen_verejne: bool = True): self.rocnik = rocnik self.jen_verejne = jen_verejne self.do_deadlinu = m.Deadline.objects.filter(cislo__rocnik=rocnik).last() @cached_property def aktivni_resitele(self) -> list[m.Resitel]: return list(resi_v_rocniku(self.rocnik)) @cached_property def cisla_rocniku(self) -> list[m.Cislo]: return cisla_rocniku(self.rocnik, self.jen_verejne) @cached_property def body_za_cisla_slovnik(self) -> dict[int, dict[int, int]]: body_cisla_slovnik = dict() for cislo in self.cisla_rocniku: # získáme body za číslo body_za_cislo = body_resitelu( za=cislo, resitele=self.aktivni_resitele, jen_verejne=self.jen_verejne, null="" ) body_cisla_slovnik[cislo.id] = body_za_cislo return body_cisla_slovnik class RadekVysledkovkyRocniku: """ 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_seznam, 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_seznam = body_cisla_seznam self.titul = resitel.get_titul(body_odjakziva) @cached_property def radky_vysledkovky(self) -> list[RadekVysledkovkyRocniku]: radky_vysledkovky = [] i = 0 setrizeni_resitele_dict = dict() for r in m.Resitel.objects.filter( id__in=self.setrizeni_resitele_id ).select_related('osoba'): setrizeni_resitele_dict[r.id] = r for ar_id in self.setrizeni_resitele_id: if self.setrizene_body[i] > 0: # seznam počtu bodů daného řešitele pro jednotlivá čísla body_cisla_seznam = [] for cislo in self.cisla_rocniku: body_cisla_seznam.append(self.body_za_cisla_slovnik[cislo.id][ar_id]) # Pokud řešitel dostal nějaké body if self.resitele_s_body_za_rocnik_setrizeny_seznam[i] != 0: # vytáhneme informace pro daného řešitele radek = self.RadekVysledkovkyRocniku( poradi=self.poradi[i], resitel=setrizeni_resitele_dict[ar_id], body_cisla_seznam=body_cisla_seznam, body_rocnik=self.setrizene_body[i], body_odjakziva=self.resitel_body_odjakziva_slovnik[ar_id], rok=self.rocnik) # ročník semináře pro získání ročníku řešitele radky_vysledkovky.append(radek) i += 1 return radky_vysledkovky class VysledkovkaCisla(Vysledkovka): def __init__( self, cislo: m.Cislo, jen_verejne: bool = True, do_deadlinu: m.Deadline = None ): self.cislo = cislo self.rocnik = cislo.rocnik self.jen_verejne = jen_verejne if do_deadlinu is None: do_deadlinu = m.Deadline.objects.filter(cislo=cislo).last() self.do_deadlinu = do_deadlinu @cached_property def aktivni_resitele(self) -> list[m.Resitel]: # TODO možná chytřeji vybírat aktivní řešitele return list(resi_v_rocniku(self.rocnik)) @cached_property def problemy(self) -> list[m.Problem]: return problemy_cisla(self.cislo) @cached_property def hlavni_problemy(self) -> list[m.Problem]: return hlavni_problemy_f(self.problemy) @cached_property def problemy_s_body_za_cislo(self): hlavni_problemy_slovnik = dict() for hp in self.hlavni_problemy: hlavni_problemy_slovnik[hp.id] = {} hlavni_problemy_slovnik[-1] = {} # zakládání prázdných záznamů pro řešitele cislobody = {} for ar in self.aktivni_resitele: # řešitele převedeme na řetězec pomocí unikátního id cislobody[ar.id] = "" for hp in self.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(deadline_body__cislo=self.cislo) for hodnoceni in hodnoceni_do_cisla: prob = hodnoceni.problem nadproblem = hlavni_problem(prob) if self.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 self.aktivni_resitele: continue self.pricti_body(cislobody, resitel, body) self.pricti_body(nadproblem_slovnik, resitel, body) return hlavni_problemy_slovnik, cislobody @cached_property def hlavni_problemy_slovnik(self) -> dict[int, dict[int, str]]: return self.problemy_s_body_za_cislo[0] @cached_property def body_za_cislo(self) -> dict[int, str]: return self.problemy_s_body_za_cislo[1] @cached_property def temata_a_spol(self) -> list[m.Problem]: if self.cislo.rocnik.rocnik < ROCNIK_ZRUSENI_TEMAT: return self.hlavni_problemy else: return list(filter(self.ne_clanek_ne_konfera, self.hlavni_problemy)) @cached_property def je_nejake_ostatni(self): return len(self.hlavni_problemy) - len(self.temata_a_spol) > 0 @cached_property def podproblemy(self) -> list[list[m.Problem]]: return podproblemy_v_cislu(self.cislo, self.problemy, self.temata_a_spol) @cached_property def podproblemy_seznam(self) -> list[list[m.Problem]]: return [self.podproblemy[it.id] for it in self.temata_a_spol] + [self.podproblemy[-1]] @cached_property def podproblemy_iter(self) -> FixedIterator: return FixedIterator(self.podproblemy_seznam.__iter__()) @cached_property def problemy_slovnik(self): # získáme body u jednotlivých témat """ Spočítá u řešitelů body za číslo za úlohy v jednotlivých hlavních problémech (témata). """ body_slovnik = {} for tema in self.temata_a_spol: body_slovnik[tema.id] = {} for problem in self.podproblemy[tema.id]: body_slovnik[tema.id][problem.id] = {} body_slovnik[-1] = {} for problem in self.podproblemy[-1]: body_slovnik[-1][problem.id] = {} # zakládání prázdných záznamů pro řešitele for ar in self.aktivni_resitele: for tema in self.temata_a_spol: for problem in self.podproblemy[tema.id]: body_slovnik[tema.id][problem.id][ar.id] = "" for problem in self.podproblemy[-1]: body_slovnik[-1][problem.id][ar.id] = "" temata = set(t.id for t in self.temata_a_spol) hodnoceni = m.Hodnoceni.objects.prefetch_related( 'problem', 'reseni', 'reseni__resitele') if self.jen_verejne: hodnoceni = hodnoceni.filter(deadline_body__verejna_vysledkovka=True) hodnoceni_do_cisla = hodnoceni.filter(deadline_body__cislo=self.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 self.aktivni_resitele: continue self.pricti_body(problem_slovnik, resitel, body) return body_slovnik 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_hlavni_problemy_seznam, 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_hlavni_problemy_seznam = body_hlavni_problemy_seznam self.titul = resitel.get_titul(body_odjakziva) self.body_podproblemy = body_podproblemy self.body_podproblemy_iter = body_podproblemy_iter @cached_property def radky_vysledkovky(self) -> list[RadekVysledkovkyCisla]: # vytvoříme jednotlivé sloupce výsledkovky radky_vysledkovky = [] i = 0 setrizeni_resitele_slovnik = {} setrizeni_resitele = m.Resitel.objects.filter(id__in=self.setrizeni_resitele_id).select_related('osoba') for r in setrizeni_resitele: setrizeni_resitele_slovnik[r.id] = r for ar_id in self.setrizeni_resitele_id: if self.setrizene_body[i] > 0: # získáme seznam bodů za problémy pro daného řešitele body_problemy = [] body_podproblemy = [] for hp in self.temata_a_spol: body_problemy.append(self.hlavni_problemy_slovnik[hp.id][ar_id]) body_podproblemy.append([ self.problemy_slovnik[hp.id][it.id][ar_id] for it in self.podproblemy[hp.id] ]) if self.je_nejake_ostatni: body_problemy.append(self.hlavni_problemy_slovnik[-1][ar_id]) body_podproblemy.append( [self.problemy_slovnik[-1][it.id][ar_id] for it in self.podproblemy[-1]]) # vytáhneme informace pro daného řešitele radek = self.RadekVysledkovkyCisla( poradi=self.poradi[i], resitel=setrizeni_resitele_slovnik[ar_id], body_hlavni_problemy_seznam=body_problemy, body_cislo=self.body_za_cislo[ar_id], body_rocnik=self.setrizene_body[i], body_odjakziva=self.resitel_body_odjakziva_slovnik[ar_id], rok=self.rocnik, body_podproblemy=body_podproblemy, # body všech podproblémů body_podproblemy_iter=FixedIterator(body_podproblemy.__iter__()) ) # ročník semináře pro zjištění ročníku řešitele radky_vysledkovky.append(radek) i += 1 return radky_vysledkovky @staticmethod 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 @staticmethod def ne_clanek_ne_konfera(problem): inst = problem.get_real_instance() return not (isinstance(inst, m.Clanek) or isinstance(inst, m.Konfera))