mamweb/odevzdavatko/views.py

526 lines
21 KiB
Python
Raw Normal View History

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
from django.contrib.auth.mixins import LoginRequiredMixin
2023-04-17 20:15:39 +02:00
from django.core.mail import EmailMessage
from django.utils import timezone
from django.views.generic import ListView, DetailView, FormView, CreateView
from django.views.generic.list import MultipleObjectTemplateResponseMixin,MultipleObjectMixin
from django.views.generic.base import View
from django.shortcuts import redirect, get_object_or_404, render
from django.urls import reverse
2021-02-16 22:59:45 +01:00
from django.db import transaction
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
from itertools import groupby
2021-02-16 22:59:45 +01:00
import logging
2020-08-17 20:07:49 +02:00
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
2024-08-05 11:46:38 +02:00
from tvorba.utils import resi_v_rocniku
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
# - 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ů?
class TabulkaOdevzdanychReseniView(ListView):
template_name = 'odevzdavatko/tabulka.html'
model = Hodnoceni
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í...
# 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 = Resitel.objects.all()
self.problemy = Problem.objects.all()
self.reseni = Reseni.objects.all()
2021-03-02 22:46:43 +01:00
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(Rocnik, rocnik=self.kwargs['rocnik'])
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"]
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:
initial = FiltrForm.gen_initial(self.aktualni_rocnik)
resitele = initial['resitele']
problemy = initial['problemy']
reseni_od = initial['reseni_od'][0]
reseni_do = initial['reseni_do'][0]
jen_neobodovane = initial["neobodovane"]
2023-05-23 00:28:13 +02:00
self.barvicky = initial["barvicky"]
2021-03-02 22:46:43 +01:00
# Chceme jen letošní problémy
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))
self.chteni_resitele = resitele # Zapamatování pro get_context_data
2021-03-02 22:46:43 +01:00
if resitele == FiltrForm.RESITELE_RELEVANTNI:
# 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?
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
elif resitele == FiltrForm.RESITELE_NEODMATUROVAVSI:
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:
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=Problem.STAV_ZADANY)|Q(stav=Problem.STAV_VYRESENY),
)
2021-03-02 22:46:43 +01:00
elif problemy == FiltrForm.PROBLEMY_LETOSNI:
self.problemy = self.problemy.filter(
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....
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
self.reseni = self.reseni.filter(cas_doruceni__date__gt=reseni_od, cas_doruceni__date__lte=reseni_do)
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
def get_queryset(self):
self.inicializuj_osy_tabulky()
qs = super().get_queryset()
2022-12-14 22:41:00 +01:00
if self.jen_neobodovane:
qs = qs.filter(body__isnull=True)
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"))
return qs
2020-08-17 20:07:49 +02:00
def get_context_data(self, *args, **kwargs):
# 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é
ctx = super().get_context_data(*args, **kwargs)
ctx['problemy'] = self.problemy
ctx['resitele'] = self.resitele
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
body = hodnoceni.body
cas = hodnoceni.reseni.cas_doruceni
reseni = hodnoceni.reseni
if problem not in tabulka:
tabulka[problem] = dict()
2023-05-22 23:30:57 +02:00
soucty[problem] = dict()
if resitel not in tabulka[problem]:
tabulka[problem][resitel] = [(reseni, hodnoceni)]
2023-05-22 23:30:57 +02:00
soucty[problem][resitel] = hodnoceni.body or 0 # Neobodované neřešíme
else:
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
for hodnoceni in self.get_queryset():
for resitel in hodnoceni.reseni.resitele.all():
pridej_reseni(resitel, hodnoceni)
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[Reseni, Hodnoceni]]]] = [] # podle pořadí v self.problemy
for problem in self.problemy:
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]))
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),[]))
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))
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']
# Pro maximum v přesměrovátku ročníků
ctx['aktualni_rocnik'] = Nastaveni.get_solo().aktualni_rocnik
2023-05-23 00:28:13 +02:00
ctx['barvicky'] = self.barvicky
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
# 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 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 = Reseni
template_name = 'odevzdavatko/seznam.html'
2020-08-17 20:07:49 +02:00
def get_queryset(self):
qs = super().get_queryset()
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!)")
resitel = Resitel.objects.get(id=resitel_id)
problem = Problem.objects.get(id=problem_id)
qs = qs.filter(
problem__in=[problem],
resitele__in=[resitel],
)
return qs
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)
def get_context_data(self, *args, **kwargs):
ctx = super().get_context_data(*args, **kwargs)
# 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)}
# Pro sitetree:
ctx["resitel_id"] = self.kwargs['resitel']
ctx["problem_id"] = self.kwargs['problem']
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
class DetailReseniView(DetailView):
2022-11-21 22:09:24 +01:00
""" Náhled na řešení. Editace je v :py:class:`EditReseniView`. """
model = Reseni
template_name = 'odevzdavatko/detail.html'
2021-01-19 19:25:39 +01:00
def aktualni_hodnoceni(self):
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 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})
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)
detaily_hodnoceni = self.aktualni_hodnoceni()
ctx["hodnoceni"] = detaily_hodnoceni
# Subject případného mailu (template neumí použitelně spojovat řetězce: https://stackoverflow.com/q/4386168)
ctx["predmetmailu"] = "Oprava řešení M&M "+self.reseni.problem.first().hlavni_problem.nazev
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):
""" Ř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):
# 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):
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)
# TODO: Napsat validaci formuláře a formsetu
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í
qs = Hodnoceni.objects.filter(reseni=reseni)
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"])
hodnoceni = Hodnoceni(
2021-02-16 22:59:45 +01:00
reseni=reseni,
**form.cleaned_data,
2021-02-16 22:59:45 +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
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
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
class PrehledOdevzdanychReseni(ListView):
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ý!")
2021-09-16 14:34:00 +02:00
# get_or_none, aby neexistence řešitele (např. u orgů) neházela chybu
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í
qs = qs.order_by('reseni__cas_doruceni')
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):
suma_bodu = 0
hodnoceni = list(hodnoceni)
for i in hodnoceni :
if i.body != None : suma_bodu += i.body
podle_rocniku.append((rocnik, hodnoceni, suma_bodu))
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):
model = Reseni
template_name = 'odevzdavatko/seznam.html'
2020-08-17 20:07:49 +02:00
class SeznamAktualnichReseniView(SeznamReseniView):
def get_queryset(self):
qs = super().get_queryset()
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)
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
2023-06-19 20:46:12 +02:00
class VlozReseniView(LoginRequiredMixin, FormView):
template_name = 'odevzdavatko/vloz_reseni.html'
form_class = f.PosliReseniForm
def form_valid(self, form):
data = form.cleaned_data
nove_reseni = Reseni.objects.create(
cas_doruceni=data['cas_doruceni'],
forma=data['forma'],
poznamka=data['poznamka'],
)
nove_reseni.resitele.add(*data['resitel'])
nove_reseni.problem.add(*data['problem'])
nove_reseni.save()
context = self.get_context_data()
prilohy = context['prilohy']
prilohy.instance = nove_reseni
prilohy.save()
# Chtěl jsem, aby bylo vidět, že se to uložilo, tak přesměrovávám na profil.
return redirect(reverse('profil'))
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
class NahrajReseniRozcestnikTematekView(LoginRequiredMixin, ListView):
model = Problem
template_name = 'odevzdavatko/nahraj_reseni_nadproblem.html'
def get_queryset(self):
return super().get_queryset().filter(stav=Problem.STAV_ZADANY, nadproblem__isnull=True)
class NahrajReseniView(LoginRequiredMixin, CreateView):
model = Reseni
template_name = 'odevzdavatko/nahraj_reseni.html'
form_class = f.NahrajReseniForm
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(Problem, id=nadproblem_id)
def get(self, request, *args, **kwargs):
# Zaříznutí nezadaných problémů
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 = Osoba.objects.get(user=self.request.user)
resitel = osoba.resitel
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í.',
'text': 'Pokud se ti zdá, že to je chyba, napiš nám prosím e-mail. Díky.',
})
return super().get(request, *args, **kwargs)
def get_initial(self):
nadproblem_id = self.nadproblem.id
return {
"nadproblem_id": nadproblem_id,
"problem": [] if self.nadproblem.podproblem.filter(stav=Problem.STAV_ZADANY).exists() else nadproblem_id
}
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
data["nadproblem_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
# 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()
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"])
self.object.cas_doruceni = timezone.now()
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 = Deadline.objects.filter(deadline__gte=self.object.cas_doruceni).first()
hodnoceni.save()
# 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)
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 })")
2023-04-17 20:15:39 +02:00
EmailMessage(
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\nHurá do opravování: { self.object.absolute_url() }",
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()
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
)