You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

452 lines
14 KiB

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
3 years ago
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__deadline__lte=do.deadline)
if od:
filtr &= Q(reseni__hodnoceni__deadline_body__deadline__gte=od.deadline)
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))