@ -17,10 +17,14 @@ from decimal import Decimal
from itertools import groupby
import logging
import seminar . models as m
from . import forms as f
from . forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm
from . models import Hodnoceni , Reseni
from personalni . models import Resitel , Osoba , Organizator
from tvorba . models import Problem , Deadline , Rocnik
from tvorba . utils import resi_v_rocniku
from various . models import Nastaveni
from various . views . pomocne import formularOKView
logger = logging . getLogger ( __name__ )
@ -40,20 +44,20 @@ logger = logging.getLogger(__name__)
class TabulkaOdevzdanychReseniView ( ListView ) :
template_name = ' odevzdavatko/tabulka.html '
model = m . Hodnoceni
model = Hodnoceni
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 ( )
self . resitele = Resitel . objects . all ( )
self . problemy = Problem . objects . all ( )
self . reseni = Reseni . objects . all ( )
self . aktualni_rocnik = m . Nastaveni . get_solo ( ) . aktualni_rocnik # .get_solo() vrátí tu jedinou instanci
self . aktualni_rocnik = Nastaveni . get_solo ( ) . aktualni_rocnik # .get_solo() vrátí tu jedinou instanci
if ' rocnik ' in self . kwargs :
self . aktualni_rocnik = get_object_or_404 ( m . Rocnik , rocnik = self . kwargs [ ' rocnik ' ] )
self . aktualni_rocnik = get_object_or_404 ( Rocnik , rocnik = self . kwargs [ ' rocnik ' ] )
form = FiltrForm ( self . request . GET , rocnik = self . aktualni_rocnik )
if form . is_valid ( ) :
@ -86,14 +90,14 @@ class TabulkaOdevzdanychReseniView(ListView):
self . resitele = self . resitele . filter ( rok_maturity__gt = self . aktualni_rocnik . prvni_rok )
if problemy == FiltrForm . PROBLEMY_MOJE :
org = m . Organizator . objects . get ( osoba__user = self . request . user )
org = Organizator . objects . get ( osoba__user = self . request . user )
self . problemy = self . problemy . filter (
Q ( autor = org ) | Q ( garant = org ) | Q ( opravovatele = org ) ,
Q ( stav = m . Problem . STAV_ZADANY ) | Q ( stav = m . Problem . STAV_VYRESENY ) ,
Q ( stav = Problem . STAV_ZADANY ) | Q ( stav = Problem . STAV_VYRESENY ) ,
)
elif problemy == FiltrForm . PROBLEMY_LETOSNI :
self . problemy = self . problemy . filter (
Q ( stav = m . Problem . STAV_ZADANY ) | Q ( stav = m . Problem . STAV_VYRESENY ) ,
Q ( stav = Problem . STAV_ZADANY ) | Q ( stav = Problem . STAV_VYRESENY ) ,
)
#self.problemy = list(filter(lambda problem: problem.rocnik() == self.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.
@ -121,8 +125,8 @@ class TabulkaOdevzdanychReseniView(ListView):
ctx = super ( ) . get_context_data ( * args , * * kwargs )
ctx [ ' problemy ' ] = self . problemy
ctx [ ' resitele ' ] = self . resitele
tabulka : dict [ m . Problem , dict [ m . Resitel , list [ tuple [ m . Reseni , m . Hodnoceni ] ] ] ] = dict ( )
soucty : dict [ m . Problem , dict [ m . Resitel , Decimal ] ] = dict ( )
tabulka : dict [ Problem , dict [ Resitel , list [ tuple [ Reseni , Hodnoceni ] ] ] ] = dict ( )
soucty : dict [ Problem , dict [ Resitel , Decimal ] ] = dict ( )
def pridej_reseni ( resitel , hodnoceni ) :
problem = hodnoceni . problem
@ -143,11 +147,11 @@ class TabulkaOdevzdanychReseniView(ListView):
for resitel in hodnoceni . reseni . resitele . all ( ) :
pridej_reseni ( resitel , hodnoceni )
hodnoty : list [ list [ tuple [ Decimal , list [ tuple [ m . Reseni , m . Hodnoceni ] ] ] ] ] = [ ] # Seznam řádků výsledné tabulky podle self.resitele, v každém řádku buňky v pořadí podle self.problemy + jejich součty, v každé buňce seznam řešení k danému řešiteli a problému.
resitele_do_tabulky : list [ m . Resitel ] = [ ]
hodnoty : list [ list [ tuple [ Decimal , list [ tuple [ Reseni , Hodnoceni ] ] ] ] ] = [ ] # Seznam řádků výsledné tabulky podle self.resitele, v každém řádku buňky v pořadí podle self.problemy + jejich součty, v každé buňce seznam řešení k danému řešiteli a problému.
resitele_do_tabulky : list [ Resitel ] = [ ]
for resitel in self . resitele :
dostal_body = False
resiteluv_radek : list [ tuple [ Decimal , list [ tuple [ m . Reseni , m . Hodnoceni ] ] ] ] = [ ] # podle pořadí v self.problemy
resiteluv_radek : list [ tuple [ Decimal , list [ tuple [ Reseni , Hodnoceni ] ] ] ] = [ ] # podle pořadí v self.problemy
for problem in self . problemy :
if problem in tabulka and resitel in tabulka [ problem ] :
resiteluv_radek . append ( ( soucty [ problem ] [ resitel ] , tabulka [ problem ] [ resitel ] ) )
@ -162,7 +166,7 @@ class TabulkaOdevzdanychReseniView(ListView):
# Pro použití hacku na automatické {{form.media}} v template:
ctx [ ' form ' ] = ctx [ ' filtr ' ]
# Pro maximum v přesměrovátku ročníků
ctx [ ' aktualni_rocnik ' ] = m . Nastaveni . get_solo ( ) . aktualni_rocnik
ctx [ ' aktualni_rocnik ' ] = Nastaveni . get_solo ( ) . aktualni_rocnik
ctx [ ' barvicky ' ] = self . barvicky
if ' rocnik ' in self . kwargs :
ctx [ ' rocnik ' ] = self . kwargs [ ' rocnik ' ]
@ -178,7 +182,7 @@ class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixi
Asi už bude zastaralý v okamžiku , kdy se tenhle komentář nasadí na produkci : - )
V případě , že takové řešení existuje jen jedno , tak na něj přesměruje . """
model = m . Reseni
model = Reseni
template_name = ' odevzdavatko/seznam.html '
def get_queryset ( self ) :
@ -190,8 +194,8 @@ class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixi
if problem_id is None :
raise ValueError ( " Nemám problém! (To je problém!) " )
resitel = m . Resitel . objects . get ( id = resitel_id )
problem = m . Problem . objects . get ( id = problem_id )
resitel = Resitel . objects . get ( id = resitel_id )
problem = Problem . objects . get ( id = problem_id )
qs = qs . filter (
problem__in = [ problem ] ,
resitele__in = [ resitel ] ,
@ -221,13 +225,13 @@ class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixi
## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex
class DetailReseniView ( DetailView ) :
""" Náhled na řešení. Editace je v :py:class:`EditReseniView`. """
model = m . Reseni
model = Reseni
template_name = ' odevzdavatko/detail.html '
def aktualni_hodnoceni ( self ) :
self . reseni = get_object_or_404 ( m . Reseni , id = self . kwargs [ ' pk ' ] )
self . reseni = get_object_or_404 ( Reseni , id = self . kwargs [ ' pk ' ] )
result = [ ] # Slovníky s klíči problem, body, deadline_body -- initial data pro f.OhodnoceniReseniFormSet
for hodn in m . Hodnoceni . objects . filter ( reseni = self . reseni ) :
for hodn in Hodnoceni . objects . filter ( reseni = self . reseni ) :
seznam_atributu = [
" problem " ,
" body " ,
@ -284,7 +288,7 @@ class EditReseniView(DetailReseniView):
def hodnoceniReseniView ( request , pk , * args , * * kwargs ) :
reseni = get_object_or_404 ( m . Reseni , pk = pk )
reseni = get_object_or_404 ( Reseni , pk = pk )
success_url = reverse ( ' odevzdavatko_detail_reseni ' , kwargs = { ' pk ' : pk } )
# FIXME: Použit initial i tady a nebastlit hodnocení tak nízkoúrovňově
@ -300,7 +304,7 @@ def hodnoceniReseniView(request, pk, *args, **kwargs):
poznamka_form . save ( )
# Smažeme všechna dosavadní hodnocení tohoto řešení
qs = m . Hodnoceni . objects . filter ( reseni = reseni )
qs = Hodnoceni . objects . filter ( reseni = reseni )
logger . info ( f " Will delete { qs . count ( ) } objects: { qs } " )
qs . delete ( )
@ -311,7 +315,7 @@ def hodnoceniReseniView(request, pk, *args, **kwargs):
del ( data_for_hodnoceni [ " body_celkem " ] )
del ( data_for_hodnoceni [ " body_neprepocitane " ] )
del ( data_for_hodnoceni [ " body_neprepocitane_celkem " ] )
hodnoceni = m . Hodnoceni (
hodnoceni = Hodnoceni (
reseni = reseni ,
* * form . cleaned_data ,
)
@ -332,14 +336,14 @@ def hodnoceniReseniView(request, pk, *args, **kwargs):
class PrehledOdevzdanychReseni ( ListView ) :
model = m . Hodnoceni
model = Hodnoceni
template_name = ' odevzdavatko/prehled_reseni.html '
def get_queryset ( self ) :
if not self . request . user . is_authenticated :
raise RuntimeError ( " Uživatel měl být přihlášený! " )
# get_or_none, aby neexistence řešitele (např. u orgů) neházela chybu
resitel = m . Resitel . objects . filter ( osoba__user = self . request . user ) . first ( )
resitel = Resitel . objects . filter ( osoba__user = self . request . user ) . first ( )
qs = super ( ) . get_queryset ( )
qs = qs . filter ( reseni__resitele__in = [ resitel ] )
# Setřídíme podle času doručení řešení, aby se netřídily podle okamžiku vyrobení Hodnocení
@ -360,13 +364,13 @@ class PrehledOdevzdanychReseni(ListView):
# Přehled všech řešení kvůli debugování
class SeznamReseniView ( ListView ) :
model = m . Reseni
model = Reseni
template_name = ' odevzdavatko/seznam.html '
class SeznamAktualnichReseniView ( SeznamReseniView ) :
def get_queryset ( self ) :
qs = super ( ) . get_queryset ( )
akt_rocnik = m . Nastaveni . get_solo ( ) . aktualni_rocnik # .get_solo() vrátí tu jedinou instanci, asi...
akt_rocnik = Nastaveni . get_solo ( ) . aktualni_rocnik # .get_solo() vrátí tu jedinou instanci, asi...
resitele = resi_v_rocniku ( akt_rocnik )
qs = qs . filter ( resitele__in = resitele ) # FIXME: Najde řešení i ze starých ročníků, která odevzdal alespoň jeden aktuální řešitel
return qs
@ -378,7 +382,7 @@ class VlozReseniView(LoginRequiredMixin, FormView):
def form_valid ( self , form ) :
data = form . cleaned_data
nove_reseni = m . Reseni . objects . create (
nove_reseni = Reseni . objects . create (
cas_doruceni = data [ ' cas_doruceni ' ] ,
forma = data [ ' forma ' ] ,
poznamka = data [ ' poznamka ' ] ,
@ -405,35 +409,35 @@ class VlozReseniView(LoginRequiredMixin, FormView):
class NahrajReseniRozcestnikTematekView ( LoginRequiredMixin , ListView ) :
model = m . Problem
model = Problem
template_name = ' odevzdavatko/nahraj_reseni_nadproblem.html '
def get_queryset ( self ) :
return super ( ) . get_queryset ( ) . filter ( stav = m . Problem . STAV_ZADANY , nadproblem__isnull = True )
return super ( ) . get_queryset ( ) . filter ( stav = Problem . STAV_ZADANY , nadproblem__isnull = True )
class NahrajReseniView ( LoginRequiredMixin , CreateView ) :
model = m . Reseni
model = Reseni
template_name = ' odevzdavatko/nahraj_reseni.html '
form_class = f . NahrajReseniForm
nadproblem : m . Problem
nadproblem : Problem
def setup ( self , request , * args , * * kwargs ) :
super ( ) . setup ( request , * args , * * kwargs )
nadproblem_id = self . kwargs [ " nadproblem_id " ]
self . nadproblem = get_object_or_404 ( m . Problem , id = nadproblem_id )
self . nadproblem = get_object_or_404 ( Problem , id = nadproblem_id )
def get ( self , request , * args , * * kwargs ) :
# Zaříznutí nezadaných problémů
if self . nadproblem . stav != m . Problem . STAV_ZADANY :
if self . nadproblem . stav != Problem . STAV_ZADANY :
raise PermissionDenied ( )
# Zaříznutí starých řešitelů:
# FIXME: Je to tady dost naprasené, mělo by to asi být jinde…
osoba = m . Osoba . objects . get ( user = self . request . user )
osoba = Osoba . objects . get ( user = self . request . user )
resitel = osoba . resitel
if resitel . rok_maturity < = m . Nastaveni . get_solo ( ) . aktualni_rocnik . prvni_rok :
if resitel . rok_maturity < = Nastaveni . get_solo ( ) . aktualni_rocnik . prvni_rok :
return render ( request , ' universal.html ' , {
' title ' : ' Nelze odevzdat ' ,
' error ' : ' Zdá se, že jsi již odmaturoval/a, a tedy nemůžeš odevzdat do našeho semináře řešení. ' ,
@ -445,7 +449,7 @@ class NahrajReseniView(LoginRequiredMixin, CreateView):
nadproblem_id = self . nadproblem . id
return {
" nadproblem_id " : nadproblem_id ,
" problem " : [ ] if self . nadproblem . podproblem . filter ( stav = m . Problem . STAV_ZADANY ) . exists ( ) else nadproblem_id
" problem " : [ ] if self . nadproblem . podproblem . filter ( stav = Problem . STAV_ZADANY ) . exists ( ) else nadproblem_id
}
@ -457,7 +461,7 @@ class NahrajReseniView(LoginRequiredMixin, CreateView):
data [ ' prilohy ' ] = f . ReseniSPrilohamiFormSet ( )
data [ " nadproblem_id " ] = self . nadproblem . id
data [ " nadproblem " ] = get_object_or_404 ( m . Problem , id = self . nadproblem . id )
data [ " nadproblem " ] = get_object_or_404 ( Problem , id = self . nadproblem . id )
return data
# FIXME prepsat tak, aby form_valid se volalo jen tehdy, kdyz je form i formset validni
@ -469,17 +473,17 @@ class NahrajReseniView(LoginRequiredMixin, CreateView):
return super ( ) . form_invalid ( form )
with transaction . atomic ( ) :
self . object = form . save ( )
self . object . resitele . add ( m . Resitel . objects . get ( osoba__user = self . request . user ) )
self . object . resitele . add ( Resitel . objects . get ( osoba__user = self . request . user ) )
self . object . resitele . add ( * form . cleaned_data [ " resitele " ] )
self . object . cas_doruceni = timezone . now ( )
self . object . forma = m . Reseni . FORMA_UPLOAD
self . object . forma = Reseni . FORMA_UPLOAD
self . object . save ( )
prilohy . instance = self . object
prilohy . save ( )
for hodnoceni in self . object . hodnoceni_set . all ( ) :
hodnoceni . deadline_body = m . Deadline . objects . filter ( deadline__gte = self . object . cas_doruceni ) . first ( )
hodnoceni . deadline_body = Deadline . objects . filter ( deadline__gte = self . object . cas_doruceni ) . first ( )
hodnoceni . save ( )
# Pošleme mail opravovatelům a garantovi
@ -497,7 +501,7 @@ class NahrajReseniView(LoginRequiredMixin, CreateView):
# FIXME: Víc informativní obsah mailů, možná vč. příloh?
prijemci = map ( lambda it : it . osoba . email , prijemci )
resitel = m . Osoba . objects . get ( user = self . request . user )
resitel = Osoba . objects . get ( user = self . request . user )
seznam = " problému " + str ( problemy [ 0 ] ) if len ( problemy ) == 1 else ' následujícím problémům: \n ' + ' , \n ' . join ( map ( str , problemy ) )
seznam_do_subjectu = " problému " + str ( problemy [ 0 ] ) + ( " " if len ( problemy ) == 1 else f " (a dalším { len ( problemy ) - 1 } ) " )
@ -512,5 +516,5 @@ class NahrajReseniView(LoginRequiredMixin, CreateView):
return formularOKView (
self . request ,
text = ' Řešení úspěšně odevzdáno ' ,
dalsi_odkazy = [ ( " Odevzdat další řešení " , reverse ( " seminar _nahraj_reseni" ) ) ] ,
dalsi_odkazy = [ ( " Odevzdat další řešení " , reverse ( " odevzdavatko _nahraj_reseni" ) ) ] ,
)