2021-11-16 00:44:26 +01:00
from django . core . exceptions import PermissionDenied
2021-01-19 19:25:39 +01:00
from django . views . generic import ListView , DetailView , FormView
2021-10-08 10:36:59 +02:00
from django . contrib . auth . mixins import LoginRequiredMixin
2023-04-17 20:15:39 +02:00
from django . core . mail import EmailMessage
2021-10-08 10:36:59 +02:00
from django . utils import timezone
from django . views . generic import ListView , DetailView , FormView , CreateView
2021-01-19 21:18:48 +01:00
from django . views . generic . list import MultipleObjectTemplateResponseMixin , MultipleObjectMixin
from django . views . generic . base import View
2021-10-08 10:36:59 +02:00
from django . shortcuts import redirect , get_object_or_404 , render
2021-01-19 21:18:48 +01:00
from django . urls import reverse
2021-02-16 22:59:45 +01:00
from django . db import transaction
2021-09-05 01:27:42 +02:00
from django . db . models import Q
2020-08-17 20:07:49 +02:00
from dataclasses import dataclass
import datetime
2023-05-22 23:30:57 +02:00
from decimal import Decimal
2021-04-06 20:30:48 +02:00
from itertools import groupby
2021-02-16 22:59:45 +01:00
import logging
2020-08-17 20:07:49 +02:00
2021-10-08 10:36:59 +02:00
from . import forms as f
from . forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm
2024-11-01 11:44:17 +01:00
from . models import Hodnoceni , Reseni
from personalni . models import Resitel , Osoba , Organizator
from tvorba . models import Problem , Deadline , Rocnik
2024-08-05 11:46:38 +02:00
from tvorba . utils import resi_v_rocniku
2024-11-01 11:44:17 +01:00
from various . models import Nastaveni
2024-08-04 17:41:24 +02:00
from various . views . pomocne import formularOKView
2020-08-17 20:07:49 +02:00
2021-02-16 22:59:45 +01:00
logger = logging . getLogger ( __name__ )
2020-08-17 20:07:49 +02:00
# Co chceme?
# - "Tabulku" aktuální řešitelé x zveřejněné problémy, v buňkách počet řešení
# - TabulkaOdevzdanychReseniView
# - Detail konkrétního problému a řešitele -- přehled všech řešení odevzdaných k tomuto problému
# - ReseniProblemuView
# - Detail konkrétního řešení -- všechny soubory, datum, ...
# - DetailReseniView
2021-04-06 23:33:10 +02:00
# - Pro řešitele: přehled jejich odevzdaných řešení
# - PrehledOdevzdanychReseni
2020-08-17 20:07:49 +02:00
#
# Taky se může hodit:
# - Tabulka všech řešitelů x všech problémů?
2020-11-18 00:35:18 +01:00
class TabulkaOdevzdanychReseniView ( ListView ) :
2021-10-08 10:36:59 +02:00
template_name = ' odevzdavatko/tabulka.html '
2024-11-01 11:44:17 +01:00
model = Hodnoceni
2020-11-18 00:35:18 +01:00
2021-01-26 19:12:01 +01:00
def inicializuj_osy_tabulky ( self ) :
""" Vyrobí prvotní querysety pro sloupce a řádky, tj. seznam všech řešitelů a problémů """
2021-03-02 22:46:43 +01:00
# FIXME: jméno metody není vypovídající...
2021-01-26 19:12:01 +01:00
# 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, ...
2024-11-01 11:44:17 +01:00
self . resitele = Resitel . objects . all ( )
self . problemy = Problem . objects . all ( )
self . reseni = Reseni . objects . all ( )
2021-03-02 22:46:43 +01:00
2024-11-01 11:44:17 +01:00
self . aktualni_rocnik = Nastaveni . get_solo ( ) . aktualni_rocnik # .get_solo() vrátí tu jedinou instanci
2021-09-11 08:21:16 +02:00
if ' rocnik ' in self . kwargs :
2024-11-01 11:44:17 +01:00
self . aktualni_rocnik = get_object_or_404 ( Rocnik , rocnik = self . kwargs [ ' rocnik ' ] )
2021-09-11 08:21:16 +02:00
2021-09-12 02:20:06 +02:00
form = FiltrForm ( self . request . GET , rocnik = self . aktualni_rocnik )
2021-03-02 22:46:43 +01:00
if form . is_valid ( ) :
fcd = form . cleaned_data
resitele = fcd [ " resitele " ]
problemy = fcd [ " problemy " ]
reseni_od = fcd [ " reseni_od " ]
reseni_do = fcd [ " reseni_do " ]
2021-06-30 04:16:29 +02:00
jen_neobodovane = fcd [ " neobodovane " ]
2023-05-23 00:28:13 +02:00
self . barvicky = fcd [ " barvicky " ]
2021-03-02 22:46:43 +01:00
else :
2021-09-12 02:20:06 +02:00
initial = FiltrForm . gen_initial ( self . aktualni_rocnik )
2021-03-09 21:33:53 +01:00
resitele = initial [ ' resitele ' ]
problemy = initial [ ' problemy ' ]
2021-03-16 21:37:58 +01:00
reseni_od = initial [ ' reseni_od ' ] [ 0 ]
reseni_do = initial [ ' reseni_do ' ] [ 0 ]
2021-06-30 04:16:29 +02:00
jen_neobodovane = initial [ " neobodovane " ]
2023-05-23 00:28:13 +02:00
self . barvicky = initial [ " barvicky " ]
2021-03-02 22:46:43 +01:00
2021-09-05 01:27:42 +02:00
# Chceme jen letošní problémy
2021-09-12 02:20:06 +02:00
self . problemy = self . problemy . filter ( Q ( Tema___rocnik = self . aktualni_rocnik ) | Q ( Uloha___cislo_zadani__rocnik = self . aktualni_rocnik ) | Q ( Clanek___cislo__rocnik = self . aktualni_rocnik ) | Q ( Konfera___soustredeni__rocnik = self . aktualni_rocnik ) )
2021-09-05 01:27:42 +02:00
2021-03-16 20:06:45 +01:00
self . chteni_resitele = resitele # Zapamatování pro get_context_data
2021-03-02 22:46:43 +01:00
if resitele == FiltrForm . RESITELE_RELEVANTNI :
2021-09-05 13:24:45 +02:00
# Nejde použít utils.resi_v_rocniku, protože noví řešitelé mohou mít neobodované řešení a takoví technicky zatím neřeší.
# Proto používám neodmaturovavší řešitele, TODO: Chceme to takhle nebo jinak?
2021-09-12 02:20:06 +02:00
self . resitele = self . resitele . filter ( rok_maturity__gt = self . aktualni_rocnik . prvni_rok ) # Prvotní sada, pokud nebude mít body, odstraní se v get_context_data
2021-09-05 13:24:45 +02:00
elif resitele == FiltrForm . RESITELE_NEODMATUROVAVSI :
2021-09-12 02:20:06 +02:00
self . resitele = self . resitele . filter ( rok_maturity__gt = self . aktualni_rocnik . prvni_rok )
2021-03-02 22:46:43 +01:00
if problemy == FiltrForm . PROBLEMY_MOJE :
2024-11-01 11:44:17 +01:00
org = Organizator . objects . get ( osoba__user = self . request . user )
2021-11-15 20:45:10 +01:00
self . problemy = self . problemy . filter (
Q ( autor = org ) | Q ( garant = org ) | Q ( opravovatele = org ) ,
2024-11-01 11:44:17 +01:00
Q ( stav = Problem . STAV_ZADANY ) | Q ( stav = Problem . STAV_VYRESENY ) ,
2021-11-15 20:45:10 +01:00
)
2021-03-02 22:46:43 +01:00
elif problemy == FiltrForm . PROBLEMY_LETOSNI :
2021-11-15 20:45:10 +01:00
self . problemy = self . problemy . filter (
2024-11-01 11:44:17 +01:00
Q ( stav = Problem . STAV_ZADANY ) | Q ( stav = Problem . STAV_VYRESENY ) ,
2021-11-15 20:45:10 +01:00
)
2021-09-12 02:20:06 +02:00
#self.problemy = list(filter(lambda problem: problem.rocnik() == self.aktualni_rocnik, self.problemy)) # DB HOG? # FIXME: některé problémy nemají ročník....
2021-03-02 22:46:43 +01:00
# 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.
2022-12-14 22:41:00 +01:00
self . problemy = self . problemy . non_polymorphic ( ) . distinct ( )
2021-03-02 22:46:43 +01:00
2022-10-09 10:21:28 +02:00
self . reseni = self . reseni . filter ( cas_doruceni__date__gt = reseni_od , cas_doruceni__date__lte = reseni_do )
2021-06-30 04:16:29 +02:00
if jen_neobodovane :
self . reseni = self . reseni . filter ( hodnoceni__body__isnull = True )
2022-12-14 22:41:00 +01:00
self . jen_neobodovane = jen_neobodovane
2021-01-26 19:12:01 +01:00
2020-11-18 00:35:18 +01:00
def get_queryset ( self ) :
2021-01-26 19:12:01 +01:00
self . inicializuj_osy_tabulky ( )
2020-11-18 00:35:18 +01:00
qs = super ( ) . get_queryset ( )
2022-12-14 22:41:00 +01:00
if self . jen_neobodovane :
qs = qs . filter ( body__isnull = True )
2023-02-13 20:47:42 +01:00
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 ' ) . distinct ( )
2022-12-14 22:41:00 +01:00
# FIXME tohle je ošklivé, na špatném místě a pomalé. Ale moc mě štvalo, že musím hledat správná místa v tabulce.
self . problemy = self . problemy . filter ( id__in = qs . values ( " problem__id " ) )
2020-11-18 00:35:18 +01:00
return qs
2020-08-17 20:07:49 +02:00
def get_context_data ( self , * args , * * kwargs ) :
2023-05-22 22:15:27 +02:00
# TODO: refactor asi. Přepisoval jsem to jen syntakticky, nejspíš půlka kódu přestala dávat smysl…
2021-03-02 22:46:43 +01:00
# self.resitele, self.reseni a self.problemy jsou již nastavené
2020-12-02 23:33:12 +01:00
2020-11-18 00:35:18 +01:00
ctx = super ( ) . get_context_data ( * args , * * kwargs )
2021-01-26 19:12:01 +01:00
ctx [ ' problemy ' ] = self . problemy
2020-11-18 00:35:18 +01:00
ctx [ ' resitele ' ] = self . resitele
2024-11-01 11:44:17 +01:00
tabulka : dict [ Problem , dict [ Resitel , list [ tuple [ Reseni , Hodnoceni ] ] ] ] = dict ( )
soucty : dict [ Problem , dict [ Resitel , Decimal ] ] = dict ( )
2020-11-18 00:35:18 +01:00
2023-05-22 21:47:27 +02:00
def pridej_reseni ( resitel , hodnoceni ) :
problem = hodnoceni . problem
body = hodnoceni . body
cas = hodnoceni . reseni . cas_doruceni
2023-05-22 22:15:27 +02:00
reseni = hodnoceni . reseni
2020-11-18 00:35:18 +01:00
if problem not in tabulka :
tabulka [ problem ] = dict ( )
2023-05-22 23:30:57 +02:00
soucty [ problem ] = dict ( )
2020-11-18 00:35:18 +01:00
if resitel not in tabulka [ problem ] :
2023-05-22 22:46:47 +02:00
tabulka [ problem ] [ resitel ] = [ ( reseni , hodnoceni ) ]
2023-05-22 23:30:57 +02:00
soucty [ problem ] [ resitel ] = hodnoceni . body or 0 # Neobodované neřešíme
2020-11-18 00:35:18 +01:00
else :
2023-05-22 22:46:47 +02:00
tabulka [ problem ] [ resitel ] . append ( ( reseni , hodnoceni ) )
2023-05-22 23:30:57 +02:00
soucty [ problem ] [ resitel ] + = hodnoceni . body or 0 # Neobodované neřešíme
2020-08-17 20:07:49 +02:00
2020-11-18 00:35:18 +01:00
for hodnoceni in self . get_queryset ( ) :
for resitel in hodnoceni . reseni . resitele . all ( ) :
2023-05-22 21:47:27 +02:00
pridej_reseni ( resitel , hodnoceni )
2020-11-18 00:35:18 +01:00
2024-11-01 11:44:17 +01:00
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 ] = [ ]
2020-11-18 00:35:18 +01:00
for resitel in self . resitele :
2021-03-16 20:06:45 +01:00
dostal_body = False
2024-11-01 11:44:17 +01:00
resiteluv_radek : list [ tuple [ Decimal , list [ tuple [ Reseni , Hodnoceni ] ] ] ] = [ ] # podle pořadí v self.problemy
2021-01-26 19:12:01 +01:00
for problem in self . problemy :
2020-11-18 00:35:18 +01:00
if problem in tabulka and resitel in tabulka [ problem ] :
2023-05-22 23:30:57 +02:00
resiteluv_radek . append ( ( soucty [ problem ] [ resitel ] , tabulka [ problem ] [ resitel ] ) )
2021-03-16 20:06:45 +01:00
dostal_body = True
2020-10-27 23:50:05 +01:00
else :
2023-05-22 23:30:57 +02:00
resiteluv_radek . append ( ( Decimal ( 0 ) , [ ] ) )
2021-03-16 20:06:45 +01:00
if self . chteni_resitele != FiltrForm . RESITELE_RELEVANTNI or dostal_body :
hodnoty . append ( resiteluv_radek )
resitele_do_tabulky . append ( resitel )
ctx [ ' radky ' ] = list ( zip ( resitele_do_tabulky , hodnoty ) )
2021-09-12 02:20:06 +02:00
ctx [ ' filtr ' ] = FiltrForm ( initial = self . request . GET , rocnik = self . aktualni_rocnik )
2021-02-23 22:13:14 +01:00
# Pro použití hacku na automatické {{form.media}} v template:
ctx [ ' form ' ] = ctx [ ' filtr ' ]
2021-09-12 03:40:19 +02:00
# Pro maximum v přesměrovátku ročníků
2024-11-01 11:44:17 +01:00
ctx [ ' aktualni_rocnik ' ] = Nastaveni . get_solo ( ) . aktualni_rocnik
2023-05-23 00:28:13 +02:00
ctx [ ' barvicky ' ] = self . barvicky
2021-09-16 15:58:45 +02:00
if ' rocnik ' in self . kwargs :
ctx [ ' rocnik ' ] = self . kwargs [ ' rocnik ' ]
else :
ctx [ ' rocnik ' ] = ctx [ ' aktualni_rocnik ' ] . rocnik
2021-02-23 22:13:14 +01:00
2020-08-17 20:07:49 +02:00
return ctx
2021-01-19 21:18:48 +01:00
# Velmi silně inspirováno zdrojáky, FIXME: Nedá se to udělat smysluplněji?
class ReseniProblemuView ( MultipleObjectTemplateResponseMixin , MultipleObjectMixin , View ) :
2023-05-22 22:15:07 +02:00
""" Rozskok mezi více řešeními téhož problému od téhož řešitele.
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 . """
2024-11-01 11:44:17 +01:00
model = Reseni
2021-10-08 10:36:59 +02:00
template_name = ' odevzdavatko/seznam.html '
2020-08-17 20:07:49 +02:00
def get_queryset ( self ) :
qs = super ( ) . get_queryset ( )
2020-10-27 22:37:03 +01:00
resitel_id = self . kwargs [ ' resitel ' ]
if resitel_id is None :
raise ValueError ( " Nemám řešitele! " )
problem_id = self . kwargs [ ' problem ' ]
if problem_id is None :
raise ValueError ( " Nemám problém! (To je problém!) " )
2024-11-01 11:44:17 +01:00
resitel = Resitel . objects . get ( id = resitel_id )
problem = Problem . objects . get ( id = problem_id )
2020-10-27 22:37:03 +01:00
qs = qs . filter (
problem__in = [ problem ] ,
resitele__in = [ resitel ] ,
)
return qs
2021-01-19 21:18:48 +01:00
def get ( self , request , * args , * * kwargs ) :
self . object_list = self . get_queryset ( )
if self . object_list . count ( ) == 1 :
jedine_reseni = self . object_list . first ( )
return redirect ( reverse ( " odevzdavatko_detail_reseni " , kwargs = { " pk " : jedine_reseni . id } ) )
context = self . get_context_data ( )
return self . render_to_response ( context )
2021-04-06 20:30:48 +02:00
def get_context_data ( self , * args , * * kwargs ) :
ctx = super ( ) . get_context_data ( * args , * * kwargs )
2021-04-06 20:39:18 +02:00
# XXX: Předat groupby do template nejde: https://stackoverflow.com/questions/6906593/itertools-groupby-in-a-django-template
# Django má {% regroup %}, ale ten potřebuje, aby klíč byl atribut položky: https://docs.djangoproject.com/en/3.2/ref/templates/builtins/#regroup
# Takže rozbalíme groupby do slovníku klíč → seznam sami (dictionary comphrehension)
2022-10-09 11:50:27 +02:00
ctx [ ' reseni_podle_deadlinu ' ] = { k : list ( v ) for k , v in groupby ( ctx [ ' object_list ' ] , lambda r : r . deadline_reseni ) }
2021-09-17 22:43:27 +02:00
# Pro sitetree:
ctx [ " resitel_id " ] = self . kwargs [ ' resitel ' ]
ctx [ " problem_id " ] = self . kwargs [ ' problem ' ]
2021-04-06 20:30:48 +02:00
return ctx
2020-08-17 20:07:49 +02:00
2021-01-19 19:25:39 +01:00
## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex
2020-10-27 22:37:03 +01:00
class DetailReseniView ( DetailView ) :
2022-11-21 22:09:24 +01:00
""" Náhled na řešení. Editace je v :py:class:`EditReseniView`. """
2024-11-01 11:44:17 +01:00
model = Reseni
2021-10-08 10:36:59 +02:00
template_name = ' odevzdavatko/detail.html '
2021-01-19 19:25:39 +01:00
def aktualni_hodnoceni ( self ) :
2024-11-01 11:44:17 +01:00
self . reseni = get_object_or_404 ( Reseni , id = self . kwargs [ ' pk ' ] )
2022-10-01 14:41:47 +02:00
result = [ ] # Slovníky s klíči problem, body, deadline_body -- initial data pro f.OhodnoceniReseniFormSet
2024-11-01 11:44:17 +01:00
for hodn in Hodnoceni . objects . filter ( reseni = self . reseni ) :
2023-01-02 23:44:04 +01:00
seznam_atributu = [
" problem " ,
" body " ,
" body_celkem " ,
" body_neprepocitane " ,
" body_neprepocitane_celkem " ,
" body_max " ,
" body_neprepocitane_max " ,
" deadline_body " ,
" feedback " ,
]
result . append ( { attr : getattr ( hodn , attr ) for attr in seznam_atributu } )
2021-02-16 19:36:36 +01:00
return result
2021-01-19 19:25:39 +01:00
def get_context_data ( self , * * kw ) :
2022-11-21 22:09:24 +01:00
self . check_access ( )
2021-01-19 19:25:39 +01:00
ctx = super ( ) . get_context_data ( * * kw )
2023-01-08 08:51:01 +01:00
detaily_hodnoceni = self . aktualni_hodnoceni ( )
ctx [ " hodnoceni " ] = detaily_hodnoceni
2023-01-08 08:52:01 +01:00
# Subject případného mailu (template neumí použitelně spojovat řetězce: https://stackoverflow.com/q/4386168)
2023-02-06 20:27:35 +01:00
ctx [ " predmetmailu " ] = " Oprava řešení M&M " + self . reseni . problem . first ( ) . hlavni_problem . nazev
2023-02-06 21:56:06 +01:00
ctx [ " maily_vsech_resitelu " ] = [ y for x in self . reseni . resitele . all ( ) . values_list ( ' osoba__email ' ) for y in x ]
2022-11-21 22:09:24 +01:00
return ctx
def get ( self , request , * args , * * kwargs ) :
"""
Oproti : py : class : ` django . views . generic . detail . BaseDetailView `
kontroluje přístup pomocí : py : meth : ` check_access `
"""
response = super ( ) . get ( self , request , * args , * * kwargs )
self . check_access ( )
return response
def check_access ( self ) :
2023-02-22 07:15:18 +01:00
""" Řešitel musí být součástí řešení, jinak se na něj nemá co dívat. Případně to může být org. """
if not self . object . resitele . filter ( osoba__user = self . request . user ) . exists ( ) and not self . request . user . je_org :
2022-11-21 22:09:24 +01:00
raise PermissionDenied ( )
class EditReseniView ( DetailReseniView ) :
""" Editace (hlavně hodnocení) řešení. """
def get_context_data ( self , * * kw ) :
ctx = super ( ) . get_context_data ( * * kw )
ctx [ ' form ' ] = f . OhodnoceniReseniFormSet ( initial = ctx [ " hodnoceni " ] )
2021-09-20 04:17:04 +02:00
ctx [ ' poznamka_form ' ] = f . PoznamkaReseniForm ( instance = self . reseni )
2022-11-21 22:09:24 +01:00
ctx [ ' edit ' ] = True
2021-01-19 19:25:39 +01:00
return ctx
2022-11-21 22:09:24 +01:00
def check_access ( self ) :
2022-12-19 23:12:23 +01:00
# Na orga máme nároky už v urls.py ale better safe then sorry
if not self . request . user . je_org :
raise PermissionDenied ( )
2022-11-21 22:09:24 +01:00
2021-01-19 19:25:39 +01:00
2021-02-16 22:59:45 +01:00
def hodnoceniReseniView ( request , pk , * args , * * kwargs ) :
2024-11-01 11:44:17 +01:00
reseni = get_object_or_404 ( Reseni , pk = pk )
2021-02-16 22:59:45 +01:00
success_url = reverse ( ' odevzdavatko_detail_reseni ' , kwargs = { ' pk ' : pk } )
2021-02-16 23:37:59 +01:00
# FIXME: Použit initial i tady a nebastlit hodnocení tak nízkoúrovňově
# Also: https://docs.djangoproject.com/en/2.2/topics/forms/modelforms/#django.forms.ModelForm
2021-02-16 22:59:45 +01:00
formset = f . OhodnoceniReseniFormSet ( request . POST )
2021-09-20 04:17:04 +02:00
poznamka_form = f . PoznamkaReseniForm ( request . POST , instance = reseni )
2021-02-16 23:49:42 +01:00
# TODO: Napsat validaci formuláře a formsetu
2022-11-03 19:56:11 +01:00
if not ( formset . is_valid ( ) and poznamka_form . is_valid ( ) ) :
raise ValueError ( formset . errors , poznamka_form . errors )
with transaction . atomic ( ) :
# Poznámka je jednoduchá na zpracování:
poznamka_form . save ( )
# Smažeme všechna dosavadní hodnocení tohoto řešení
2024-11-01 11:44:17 +01:00
qs = Hodnoceni . objects . filter ( reseni = reseni )
2022-11-03 19:56:11 +01:00
logger . info ( f " Will delete { qs . count ( ) } objects: { qs } " )
qs . delete ( )
# Vyrobíme nová podle formsetu
for form in formset :
2023-01-02 23:44:04 +01:00
data_for_hodnoceni = form . cleaned_data
data_for_body = data_for_hodnoceni . copy ( )
del ( data_for_hodnoceni [ " body_celkem " ] )
del ( data_for_hodnoceni [ " body_neprepocitane " ] )
del ( data_for_hodnoceni [ " body_neprepocitane_celkem " ] )
2024-11-01 11:44:17 +01:00
hodnoceni = Hodnoceni (
2021-02-16 22:59:45 +01:00
reseni = reseni ,
2022-11-03 19:56:11 +01:00
* * form . cleaned_data ,
2021-02-16 22:59:45 +01:00
)
2022-11-03 19:56:11 +01:00
logger . info ( f " Creating Hodnoceni: { hodnoceni } " )
2023-01-02 23:44:36 +01:00
zmeny_bodu = [ it for it in form . changed_data if it . startswith ( " body " ) ]
if len ( zmeny_bodu ) == 1 :
hodnoceni . __setattr__ ( zmeny_bodu [ 0 ] , data_for_body [ zmeny_bodu [ 0 ] ] )
2023-03-06 20:16:36 +01:00
# > jedna změna je špatně, ale 4 "změny" znamenají že nebylo nic zadáno
2023-09-16 12:31:30 +02:00
if len ( zmeny_bodu ) > 1 and len ( zmeny_bodu ) != 4 and len ( zmeny_bodu ) != 2 :
# 4 znamená vše už vyplněno a nic nezměněno, 2 znamená předvyplnili se součty a nic se nezměnilo
2023-02-13 22:32:14 +01:00
logger . warning ( f " Hodnocení { hodnoceni } mělo mít nastavené víc různých bodů: { zmeny_bodu } . Nastavuji -0.1. " )
hodnoceni . body = - 0.1
2022-11-03 19:56:11 +01:00
hodnoceni . save ( )
2021-01-19 19:25:39 +01:00
2021-02-16 22:59:45 +01:00
return redirect ( success_url )
2021-01-19 19:25:39 +01:00
2020-08-17 20:07:49 +02:00
2021-11-16 00:44:26 +01:00
2021-04-06 23:33:10 +02:00
class PrehledOdevzdanychReseni ( ListView ) :
2024-11-01 11:44:17 +01:00
model = Hodnoceni
2021-10-08 10:36:59 +02:00
template_name = ' odevzdavatko/prehled_reseni.html '
2021-04-06 23:33:10 +02:00
def get_queryset ( self ) :
if not self . request . user . is_authenticated :
raise RuntimeError ( " Uživatel měl být přihlášený! " )
2021-09-16 14:34:00 +02:00
# get_or_none, aby neexistence řešitele (např. u orgů) neházela chybu
2024-11-01 11:44:17 +01:00
resitel = Resitel . objects . filter ( osoba__user = self . request . user ) . first ( )
2021-04-06 23:33:10 +02:00
qs = super ( ) . get_queryset ( )
qs = qs . filter ( reseni__resitele__in = [ resitel ] )
2021-11-22 23:13:30 +01:00
# Setřídíme podle času doručení řešení, aby se netřídily podle okamžiku vyrobení Hodnocení
qs = qs . order_by ( ' reseni__cas_doruceni ' )
2021-04-06 23:33:10 +02:00
return qs
def get_context_data ( self , * args , * * kwargs ) :
ctx = super ( ) . get_context_data ( * args , * * kwargs )
# Ročník určujeme podle čísla, do jehož deadlinu došlo řešení.
# Chceme to mít seřazené, takže místo comphrerehsion ručně postavíme pole polí. Django templates neumí použít OrderedDict :-/
podle_rocniku = [ ]
2022-10-09 11:50:27 +02:00
for rocnik , hodnoceni in groupby ( ctx [ ' object_list ' ] , lambda ho : ho . deadline_body . cislo . rocnik if ho . deadline_body is not None else None ) :
2024-11-05 22:10:45 +01:00
suma_bodu = 0
hodnoceni = list ( hodnoceni )
for i in hodnoceni :
if i . body != None : suma_bodu + = i . body
2024-11-05 22:20:48 +01:00
podle_rocniku . append ( ( rocnik , hodnoceni , suma_bodu ) )
2024-11-05 22:10:45 +01:00
2021-04-06 23:33:10 +02:00
ctx [ ' podle_rocniku ' ] = reversed ( podle_rocniku ) # Od nejnovějšího ročníku
# TODO: Umožnit stažení / zobrazení řešení
return ctx
2020-08-17 20:07:49 +02:00
# Přehled všech řešení kvůli debugování
class SeznamReseniView ( ListView ) :
2024-11-01 11:44:17 +01:00
model = Reseni
2021-10-08 10:36:59 +02:00
template_name = ' odevzdavatko/seznam.html '
2020-08-17 20:07:49 +02:00
class SeznamAktualnichReseniView ( SeznamReseniView ) :
def get_queryset ( self ) :
qs = super ( ) . get_queryset ( )
2024-11-01 11:44:17 +01:00
akt_rocnik = Nastaveni . get_solo ( ) . aktualni_rocnik # .get_solo() vrátí tu jedinou instanci, asi...
2020-08-17 20:07:49 +02:00
resitele = resi_v_rocniku ( akt_rocnik )
2020-10-28 00:32:20 +01:00
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
2020-08-17 20:07:49 +02:00
return qs
2021-10-08 10:36:59 +02:00
2023-06-19 20:46:12 +02:00
class VlozReseniView ( LoginRequiredMixin , FormView ) :
template_name = ' odevzdavatko/vloz_reseni.html '
2021-10-08 10:36:59 +02:00
form_class = f . PosliReseniForm
def form_valid ( self , form ) :
data = form . cleaned_data
2024-11-01 11:44:17 +01:00
nove_reseni = Reseni . objects . create (
2021-10-08 10:36:59 +02:00
cas_doruceni = data [ ' cas_doruceni ' ] ,
forma = data [ ' forma ' ] ,
poznamka = data [ ' poznamka ' ] ,
)
2023-05-22 23:34:37 +02:00
nove_reseni . resitele . add ( * data [ ' resitel ' ] )
nove_reseni . problem . add ( * data [ ' problem ' ] )
2021-10-08 10:36:59 +02:00
nove_reseni . save ( )
2021-12-25 14:35:58 +01:00
context = self . get_context_data ( )
prilohy = context [ ' prilohy ' ]
prilohy . instance = nove_reseni
prilohy . save ( )
2021-10-08 10:36:59 +02:00
# Chtěl jsem, aby bylo vidět, že se to uložilo, tak přesměrovávám na profil.
return redirect ( reverse ( ' profil ' ) )
2021-12-25 14:35:58 +01:00
def get_context_data ( self , * * kwargs ) :
data = super ( ) . get_context_data ( * * kwargs )
if self . request . POST :
data [ ' prilohy ' ] = f . ReseniSPrilohamiFormSet ( self . request . POST , self . request . FILES )
else :
data [ ' prilohy ' ] = f . ReseniSPrilohamiFormSet ( )
return data
2023-06-19 20:15:35 +02:00
class NahrajReseniRozcestnikTematekView ( LoginRequiredMixin , ListView ) :
2024-11-01 11:44:17 +01:00
model = Problem
2023-05-15 21:49:35 +02:00
template_name = ' odevzdavatko/nahraj_reseni_nadproblem.html '
def get_queryset ( self ) :
2024-11-01 11:44:17 +01:00
return super ( ) . get_queryset ( ) . filter ( stav = Problem . STAV_ZADANY , nadproblem__isnull = True )
2023-05-15 21:49:35 +02:00
2021-10-08 10:36:59 +02:00
class NahrajReseniView ( LoginRequiredMixin , CreateView ) :
2024-11-01 11:44:17 +01:00
model = Reseni
2021-10-08 10:36:59 +02:00
template_name = ' odevzdavatko/nahraj_reseni.html '
form_class = f . NahrajReseniForm
2024-11-01 11:44:17 +01:00
nadproblem : Problem
2021-10-08 10:36:59 +02:00
2023-06-24 17:55:51 +02:00
def setup ( self , request , * args , * * kwargs ) :
super ( ) . setup ( request , * args , * * kwargs )
2023-05-22 22:28:14 +02:00
nadproblem_id = self . kwargs [ " nadproblem_id " ]
2024-11-01 11:44:17 +01:00
self . nadproblem = get_object_or_404 ( Problem , id = nadproblem_id )
2023-06-24 17:55:51 +02:00
def get ( self , request , * args , * * kwargs ) :
# Zaříznutí nezadaných problémů
2024-11-01 11:44:17 +01:00
if self . nadproblem . stav != Problem . STAV_ZADANY :
2023-05-22 22:28:14 +02:00
raise PermissionDenied ( )
2023-06-24 17:55:51 +02:00
2021-10-08 10:36:59 +02:00
# Zaříznutí starých řešitelů:
# FIXME: Je to tady dost naprasené, mělo by to asi být jinde…
2024-11-01 11:44:17 +01:00
osoba = Osoba . objects . get ( user = self . request . user )
2021-10-08 10:36:59 +02:00
resitel = osoba . resitel
2024-11-01 11:44:17 +01:00
if resitel . rok_maturity < = Nastaveni . get_solo ( ) . aktualni_rocnik . prvni_rok :
2021-10-08 10:36:59 +02:00
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í. ' ,
' text ' : ' Pokud se ti zdá, že to je chyba, napiš nám prosím e-mail. Díky. ' ,
} )
return super ( ) . get ( request , * args , * * kwargs )
2023-05-15 21:49:35 +02:00
def get_initial ( self ) :
2023-06-24 17:48:49 +02:00
nadproblem_id = self . nadproblem . id
2023-05-22 21:53:14 +02:00
return {
" nadproblem_id " : nadproblem_id ,
2024-11-01 11:44:17 +01:00
" problem " : [ ] if self . nadproblem . podproblem . filter ( stav = Problem . STAV_ZADANY ) . exists ( ) else nadproblem_id
2023-05-22 21:53:14 +02:00
}
2023-05-15 21:49:35 +02:00
2021-10-08 10:36:59 +02:00
def get_context_data ( self , * * kwargs ) :
data = super ( ) . get_context_data ( * * kwargs )
if self . request . POST :
data [ ' prilohy ' ] = f . ReseniSPrilohamiFormSet ( self . request . POST , self . request . FILES )
else :
data [ ' prilohy ' ] = f . ReseniSPrilohamiFormSet ( )
2023-05-15 22:22:03 +02:00
2023-06-19 20:24:19 +02:00
data [ " nadproblem_id " ] = self . nadproblem . id
2024-11-01 11:44:17 +01:00
data [ " nadproblem " ] = get_object_or_404 ( Problem , id = self . nadproblem . id )
2021-10-08 10:36:59 +02:00
return data
# FIXME prepsat tak, aby form_valid se volalo jen tehdy, kdyz je form i formset validni
# Inspirace: https://stackoverflow.com/questions/41599809/using-a-django-filefield-in-an-inline-formset
def form_valid ( self , form ) :
context = self . get_context_data ( )
prilohy = context [ ' prilohy ' ]
if not prilohy . is_valid ( ) :
return super ( ) . form_invalid ( form )
with transaction . atomic ( ) :
self . object = form . save ( )
2024-11-01 11:44:17 +01:00
self . object . resitele . add ( Resitel . objects . get ( osoba__user = self . request . user ) )
2022-11-22 00:15:20 +01:00
self . object . resitele . add ( * form . cleaned_data [ " resitele " ] )
2021-10-08 10:36:59 +02:00
self . object . cas_doruceni = timezone . now ( )
2024-11-01 11:44:17 +01:00
self . object . forma = Reseni . FORMA_UPLOAD
2021-10-08 10:36:59 +02:00
self . object . save ( )
prilohy . instance = self . object
prilohy . save ( )
2022-10-17 09:57:54 +02:00
for hodnoceni in self . object . hodnoceni_set . all ( ) :
2024-11-01 11:44:17 +01:00
hodnoceni . deadline_body = Deadline . objects . filter ( deadline__gte = self . object . cas_doruceni ) . first ( )
2022-10-17 09:57:54 +02:00
hodnoceni . save ( )
2021-10-08 10:36:59 +02:00
# Pošleme mail opravovatelům a garantovi
# FIXME: Nechat spočítat databázi? Je to pár dotazů (pravděpodobně), takže to za to možná nestojí
prijemci = set ( )
problemy = [ ]
for prob in form . cleaned_data [ ' problem ' ] :
prijemci . update ( prob . opravovatele . all ( ) )
if prob . garant is not None :
prijemci . add ( prob . garant )
problemy . append ( prob )
# FIXME: Možná poslat mail i relevantním orgům nadproblémů?
if len ( prijemci ) < 1 :
logger . warning ( f " Pozor, neposílám e-mail nikomu. Problémy: { problemy } " )
# FIXME: Víc informativní obsah mailů, možná vč. příloh?
prijemci = map ( lambda it : it . osoba . email , prijemci )
2024-11-01 11:44:17 +01:00
resitel = Osoba . objects . get ( user = self . request . user )
2021-10-08 10:36:59 +02:00
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 } ) " )
2023-04-17 20:15:39 +02:00
EmailMessage (
2021-10-08 10:36:59 +02:00
subject = " Nové řešení k " + seznam_do_subjectu ,
2024-04-13 15:36:21 +02:00
body = f " { resitel } posílá nové řešení k { seznam } . \n \n Hurá do opravování: { self . object . absolute_url ( ) } " ,
2021-10-08 10:36:59 +02:00
from_email = " submitovatko@mam.mff.cuni.cz " , # FIXME: Chceme to mít radši tady, nebo v nastavení?
2023-04-17 20:15:39 +02:00
to = list ( prijemci ) ,
) . send ( )
2021-10-08 10:36:59 +02:00
2023-05-15 23:06:52 +02:00
return formularOKView (
self . request ,
text = ' Řešení úspěšně odevzdáno ' ,
2024-11-01 12:38:47 +01:00
dalsi_odkazy = [ ( " Odevzdat další řešení " , reverse ( " odevzdavatko_nahraj_reseni " ) ) ] ,
2023-05-15 23:06:52 +02:00
)