diff --git a/seminar/forms.py b/seminar/forms.py index c64095c6..39714fae 100644 --- a/seminar/forms.py +++ b/seminar/forms.py @@ -316,3 +316,77 @@ class JednoHodnoceniForm(forms.ModelForm): OhodnoceniReseniFormSet = formset_factory(JednoHodnoceniForm, extra = 0, ) + +# FIXME: Ideálně by mělo být součástí třídy níž, ale neumím to udělat +DATE_FORMAT = '%Y-%m-%d' + +class OdevzdavatkoTabulkaFiltrForm(forms.Form): + """Form pro filtrování přehledové odevzdávátkové tabulky + + Inspirováno https://kam.mff.cuni.cz/mffzoom/""" + + # Věci definované níž se importují i ve views pro odevzdávátko (Inspirováno https://docs.djangoproject.com/en/3.1/ref/models/fields/#field-choices) + + RESITELE_RELEVANTNI = 'relevantni' + RESITELE_LETOSNI = 'letosni' + RESITELE_CHOICES = [ + (RESITELE_RELEVANTNI, 'Relevantní řešitelé'), # I.e. nezobrazovat prázdné řádky tabulky + (RESITELE_LETOSNI, 'Všichni letošní'), + # Možná: všechny vč. historických? + ] + + PROBLEMY_MOJE = 'moje' + PROBLEMY_LETOSNI = 'letosni' + PROBLEMY_CHOICES = [ + (PROBLEMY_MOJE, 'Moje problémy'), # Letošní problémy, které mají v sobě nebo v nadproblémech přiřazeného daného orga + (PROBLEMY_LETOSNI, 'Všechny letošní'), + # TODO: *hlavní problémy, možná všechny... + # XXX: Chtělo by to i "aktuálně zadané... + ] + + # TODO: Typy problémů (problémy, úlohy, ostatní, všechny)? Jen některá řešení (obodovaná/neobodovaná, víc řešitelů, ...)? + + + def gen_terminy(): + import datetime + from time import strftime + + aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik + aktualni_cislo = m.Nastaveni.get_solo().aktualni_cislo + + result = [] + + for cislo in m.Cislo.objects.filter( + rocnik=aktualni_rocnik, + poradi__lte=aktualni_cislo.poradi, + ).reverse(): # Standardně se řadí od nejnovějšího čísla + # Předem je mi líto kohokoliv, kdo tyhle řádky bude číst... + if cislo.datum_vydani is not None and cislo.datum_vydani <= datetime.date.today(): + result.append(( + strftime(DATE_FORMAT, cislo.datum_vydani.timetuple()), + f"Vydání {cislo.poradi}. čísla")) + if cislo.datum_preddeadline is not None and cislo.datum_preddeadline <= datetime.date.today(): + result.append(( + strftime(DATE_FORMAT, cislo.datum_preddeadline.timetuple()), + f"Předdeadline {cislo.poradi}. čísla")) + if cislo.datum_deadline_soustredeni is not None and cislo.datum_deadline_soustredeni <= datetime.date.today(): + result.append(( + strftime(DATE_FORMAT, cislo.datum_deadline_soustredeni.timetuple()), + f"Sous. deadline {cislo.poradi}. čísla")) + if cislo.datum_deadline is not None and cislo.datum_deadline <= datetime.date.today(): + result.append(( + strftime(DATE_FORMAT, cislo.datum_deadline.timetuple()), + f"Finální deadline {cislo.poradi}. čísla")) + result.append(( + strftime(DATE_FORMAT, datetime.date.today().timetuple()), f"Dnes")) + + return result + + # NOTE: Initial definuji pro jednotlivé fieldy, aby to bylo tady a nebylo potřeba to řešit ve views... + resitele = forms.ChoiceField(choices=RESITELE_CHOICES, initial=RESITELE_RELEVANTNI) + problemy = forms.ChoiceField(choices=PROBLEMY_CHOICES, initial=PROBLEMY_MOJE) + + # choices jako parametr Select widgetu neumí brát callable, jen iterable, takže si pro jednoduchost můžu rovnou uložit výsledek sem... + terminy = gen_terminy() + reseni_od = forms.DateField(input_formats=[DATE_FORMAT], widget=forms.Select(choices=terminy), initial=terminy[-2]) + reseni_do = forms.DateField(input_formats=[DATE_FORMAT], widget=forms.Select(choices=terminy), initial=terminy[-1]) diff --git a/seminar/templates/seminar/odevzdavatko/tabulka.html b/seminar/templates/seminar/odevzdavatko/tabulka.html index ff396ce4..26a6f922 100644 --- a/seminar/templates/seminar/odevzdavatko/tabulka.html +++ b/seminar/templates/seminar/odevzdavatko/tabulka.html @@ -4,6 +4,14 @@ {% block content %} +
+{{ filtr.resitele }} +{{ filtr.problemy }} +Od: {{ filtr.reseni_od }} +Do: {{ filtr.reseni_do }} + +
+ {# Prázdná buňka v levém horním rohu #} diff --git a/seminar/views/odevzdavatko.py b/seminar/views/odevzdavatko.py index e81f119f..d1cedb1b 100644 --- a/seminar/views/odevzdavatko.py +++ b/seminar/views/odevzdavatko.py @@ -12,6 +12,7 @@ import logging import seminar.models as m import seminar.forms as f +from seminar.forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm from seminar.utils import aktivniResitele, resi_v_rocniku logger = logging.getLogger(__name__) @@ -41,28 +42,55 @@ class TabulkaOdevzdanychReseniView(ListView): def inicializuj_osy_tabulky(self): """Vyrobí prvotní querysety pro sloupce a řádky, tj. seznam všech řešitelů a problémů""" + # FIXME: jméno metody není vypovídající... # NOTE: Tenhle blok nemůže být přímo ve třídě, protože před vyrobením databáze neexistují ty objekty (?). TODO: Otestovat # TODO: Prefetches, Select related, ... self.resitele = m.Resitel.objects.all() self.problemy = m.Problem.objects.all() + self.reseni = m.Reseni.objects.all() + + form = FiltrForm(self.request.GET) + if form.is_valid(): + fcd = form.cleaned_data + resitele = fcd["resitele"] + problemy = fcd["problemy"] + reseni_od = fcd["reseni_od"] + reseni_do = fcd["reseni_do"] + else: + resitele = FiltrForm.get_initial_for_field(FormFiltr.resitele, "resitele") + problemy = FiltrForm.get_initial_for_field(FormFiltr.problemy, "problemy") + resitele_od = FiltrForm.get_initial_for_field(FormFiltr.resitele_od, "resitele_od") + resitele_do = FiltrForm.get_initial_for_field(FormFiltr.resitele_do, "resitele_do") + - def get_queryset(self): - self.inicializuj_osy_tabulky() - self.akt_rocnik = m.Nastaveni.get_solo().aktualni_rocnik # .get_solo() vrátí tu jedinou instanci, asi... - self.resitele = resi_v_rocniku(self.akt_rocnik) + # Filtrujeme! + aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik # .get_solo() vrátí tu jedinou instanci + if resitele == FiltrForm.RESITELE_RELEVANTNI: + logger.warning("Někdo chtěl v tabulce jen relevantní řešitele a měl smůlu :-(") + resitele = FiltrForm.RESITELE_LETOSNI # Fall-through + elif resitele == FiltrForm.RESITELE_LETOSNI: + self.resitele = resi_v_rocniku(aktualni_rocnik) + + if problemy == FiltrForm.PROBLEMY_MOJE: + org = m.Organizator.objects.get(osoba__user=self.request.user) + from django.db.models import Q + self.problemy = self.problemy.filter(Q(autor=org)|Q(garant=org)|Q(opravovatele=org), stav=m.Problem.STAV_ZADANY) + elif problemy == FiltrForm.PROBLEMY_LETOSNI: + self.problemy = self.problemy.filter(stav=m.Problem.STAV_ZADANY) + #self.problemy = list(filter(lambda problem: problem.rocnik() == aktualni_rocnik, self.problemy)) # DB HOG? # FIXME: některé problémy nemají ročník.... # NOTE: Protože řešení odkazuje přímo na Problém a QuerySet na Hodnocení je nepolymorfní, musíme porovnávat taky s nepolymorfními Problémy. - self.problemy = m.Problem.objects.filter(stav=m.Problem.STAV_ZADANY).non_polymorphic() + self.problemy = self.problemy.non_polymorphic() + + self.reseni = self.reseni.filter(cas_doruceni__date__gte=reseni_od, cas_doruceni__date__lte=reseni_do) + def get_queryset(self): + self.inicializuj_osy_tabulky() qs = super().get_queryset() - qs = qs.filter(problem__in=self.problemy).select_related('reseni', 'problem').prefetch_related('reseni__resitele__osoba') + qs = qs.filter(problem__in=self.problemy, reseni__in=self.reseni, reseni__resitele__in=self.resitele).select_related('reseni', 'problem').prefetch_related('reseni__resitele__osoba') return qs def get_context_data(self, *args, **kwargs): - # FIXME: Tenhle blok nemůže být přímo ve třídě, protože před vyrobením databáze neexistuje Nastavení. - self.akt_rocnik = m.Nastaveni.get_solo().aktualni_rocnik # .get_solo() vrátí tu jedinou instanci, asi... - self.resitele = resi_v_rocniku(self.akt_rocnik) - # NOTE: Protože řešení odkazuje přímo na Problém a QuerySet na Hodnocení je nepolymorfní, musíme porovnávat taky s nepolymorfními Problémy. - self.problemy = m.Problem.objects.filter(stav=m.Problem.STAV_ZADANY).non_polymorphic() + # self.resitele, self.reseni a self.problemy jsou již nastavené ctx = super().get_context_data(*args, **kwargs) ctx['problemy'] = self.problemy @@ -100,6 +128,10 @@ class TabulkaOdevzdanychReseniView(ListView): hodnoty.append(resiteluv_radek) ctx['radky'] = list(zip(self.resitele, hodnoty)) + ctx['filtr'] = FiltrForm(initial=self.request.GET) + # Pro použití hacku na automatické {{form.media}} v template: + ctx['form'] = ctx['filtr'] + return ctx # Velmi silně inspirováno zdrojáky, FIXME: Nedá se to udělat smysluplněji?