Browse Source

Další částečné řešení #1465 (Podezřelé `seminar`e). Záměrně se vyhýbá treenode.

pull/65/head
Jonas Havelka 3 weeks ago
parent
commit
8fd582d194
  1. 18
      api/views/autocomplete.py
  2. 4
      mamweb/admin.py
  3. 23
      odevzdavatko/forms.py
  4. 16
      odevzdavatko/models.py
  5. 92
      odevzdavatko/views.py
  6. 12
      personalni/views.py
  7. 8
      soustredeni/models.py
  8. 4
      soustredeni/testutils.py
  9. 22
      tvorba/templatetags/deadliny.py
  10. 56
      tvorba/testutils.py
  11. 2
      tvorba/utils.py
  12. 55
      tvorba/views/__init__.py
  13. 14
      tvorba/views/docasne.py
  14. 1
      various/autentizace/views.py
  15. 2
      various/management/commands/generate_thumbnails.py
  16. 4
      various/management/commands/pregeneruj_zmrazene_vysledkovky.py
  17. 4
      various/management/commands/testdata.py
  18. 4
      various/testutils.py
  19. 27
      various/views/final.py
  20. 77
      vysledkovky/utils.py

18
api/views/autocomplete.py

@ -5,7 +5,9 @@ from dal import autocomplete
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.db.models import Q from django.db.models import Q
import seminar.models as m from personalni.models import Skola, Resitel
from tvorba.models import Problem
from various.models import Nastaveni
from .helpers import LoginRequiredAjaxMixin from .helpers import LoginRequiredAjaxMixin
# TODO filosofie - zkratky, jak v databázi, tak ve vyhledávání (SPŠE, GASOŠ, Kpt., soukr) # TODO filosofie - zkratky, jak v databázi, tak ve vyhledávání (SPŠE, GASOŠ, Kpt., soukr)
@ -13,7 +15,7 @@ class SkolaAutocomplete(autocomplete.Select2QuerySetView):
""" View k :mod:`dal.autocomplete` pro vyhledávání škol hlavně při registraci. """ """ View k :mod:`dal.autocomplete` pro vyhledávání škol hlavně při registraci. """
def get_queryset(self): def get_queryset(self):
# Don't forget to filter out results depending on the visitor ! # Don't forget to filter out results depending on the visitor !
qs = m.Skola.objects.all() qs = Skola.objects.all()
if self.q: if self.q:
words = self.q.split(' ') #TODO re split podle bileho znaku words = self.q.split(' ') #TODO re split podle bileho znaku
partq = Q() partq = Q()
@ -31,7 +33,7 @@ class SkolaAutocomplete(autocomplete.Select2QuerySetView):
class ResitelAutocomplete(LoginRequiredAjaxMixin,autocomplete.Select2QuerySetView): class ResitelAutocomplete(LoginRequiredAjaxMixin,autocomplete.Select2QuerySetView):
""" View k :mod:`dal.autocomplete` pro vyhledávání řešitelů především v odevzdávátku. """ """ View k :mod:`dal.autocomplete` pro vyhledávání řešitelů především v odevzdávátku. """
def get_queryset(self): def get_queryset(self):
qs = m.Resitel.objects.all() qs = Resitel.objects.all()
if self.q: if self.q:
parts = self.q.split() parts = self.q.split()
query = Q() query = Q()
@ -51,8 +53,8 @@ class PublicResitelAutocomplete(LoginRequiredAjaxMixin, autocomplete.Select2Quer
především v odevzdávátku. především v odevzdávátku.
""" """
def get_queryset(self): def get_queryset(self):
letos = m.Nastaveni.get_solo().aktualni_rocnik letos = Nastaveni.get_solo().aktualni_rocnik
qs = m.Resitel.objects.filter( qs = Resitel.objects.filter(
rok_maturity__gte=letos.druhy_rok() rok_maturity__gte=letos.druhy_rok()
).filter( ).filter(
prezdivka_resitele__isnull=False prezdivka_resitele__isnull=False
@ -70,7 +72,7 @@ class PublicResitelAutocomplete(LoginRequiredAjaxMixin, autocomplete.Select2Quer
class OdevzdatelnyProblemAutocomplete(autocomplete.Select2QuerySetView): class OdevzdatelnyProblemAutocomplete(autocomplete.Select2QuerySetView):
""" View k :mod:`dal.autocomplete` pro vyhledávání problémů především v odevzdávátku. """ """ View k :mod:`dal.autocomplete` pro vyhledávání problémů především v odevzdávátku. """
def get_queryset(self): def get_queryset(self):
qs = m.Problem.objects.filter(stav=m.Problem.STAV_ZADANY) qs = Problem.objects.filter(stav=Problem.STAV_ZADANY)
if self.q: if self.q:
qs = qs.filter( qs = qs.filter(
Q(nazev__icontains=self.q)) Q(nazev__icontains=self.q))
@ -87,12 +89,12 @@ class ProblemAutocomplete(autocomplete.Select2QuerySetView):
""" View k :mod:`dal.autocomplete` pro vyhledávání problémů především v odevzdávátku. """ """ View k :mod:`dal.autocomplete` pro vyhledávání problémů především v odevzdávátku. """
def get_queryset(self): def get_queryset(self):
# FIXME i starší úlohy # FIXME i starší úlohy
nastaveni = get_object_or_404(m.Nastaveni) nastaveni = get_object_or_404(Nastaveni)
rocnik = nastaveni.aktualni_rocnik rocnik = nastaveni.aktualni_rocnik
temaQ = Q(Tema___rocnik = rocnik) temaQ = Q(Tema___rocnik = rocnik)
ulohaQ = Q(Uloha___cislo_zadani__rocnik=rocnik) ulohaQ = Q(Uloha___cislo_zadani__rocnik=rocnik)
clanekQ = Q(Clanek___cislo__rocnik=rocnik) clanekQ = Q(Clanek___cislo__rocnik=rocnik)
qs = m.Problem.objects.filter(temaQ | ulohaQ | clanekQ).order_by("-stav", "nazev") qs = Problem.objects.filter(temaQ | ulohaQ | clanekQ).order_by("-stav", "nazev")
if self.q: if self.q:
qs = qs.filter( qs = qs.filter(
Q(nazev__icontains=self.q)) Q(nazev__icontains=self.q))

4
mamweb/admin.py

@ -43,7 +43,7 @@ def get_app_list(self, request, app_label=None):
app_dict = self._build_app_dict(request, label=app_label) app_dict = self._build_app_dict(request, label=app_label)
aplikace_nahore = [ aplikace_nahore = [
'seminar', 'tvorba',
'personalni', 'personalni',
'novinky', 'novinky',
'korektury', 'korektury',
@ -57,7 +57,7 @@ def get_app_list(self, request, app_label=None):
# Sort the models alphabetically within each app. # Sort the models alphabetically within each app.
for app in app_list: for app in app_list:
app['models'].sort(key=lambda x: locale.strxfrm('žž' + x['name'].lower()) if (x['name'].endswith("(Node)")) else locale.strxfrm(x['name'].lower())) app['models'].sort(key=lambda x: locale.strxfrm(x['name'].lower()))
return app_list return app_list

23
odevzdavatko/forms.py

@ -5,7 +5,10 @@ from django.forms.models import inlineformset_factory
from django.utils import timezone from django.utils import timezone
from personalni.models import Resitel from personalni.models import Resitel
import seminar.models as m from tvorba.models import Problem, Deadline
from various.models import Nastaveni
from odevzdavatko.models import Reseni, PrilohaReseni, Hodnoceni
import logging import logging
@ -22,7 +25,7 @@ class DateInput(forms.DateInput):
class PosliReseniForm(forms.Form): class PosliReseniForm(forms.Form):
problem = forms.ModelMultipleChoiceField( problem = forms.ModelMultipleChoiceField(
queryset=m.Problem.objects.all(), queryset=Problem.objects.all(),
label="Problémy", label="Problémy",
widget=autocomplete.ModelSelect2Multiple( widget=autocomplete.ModelSelect2Multiple(
url='autocomplete_problem', url='autocomplete_problem',
@ -58,7 +61,7 @@ class PosliReseniForm(forms.Form):
#cas_doruceni = models.DateTimeField('čas_doručení', default=timezone.now, blank=True) #cas_doruceni = models.DateTimeField('čas_doručení', default=timezone.now, blank=True)
forma = forms.ChoiceField(label="Forma řešení",choices = m.Reseni.FORMA_CHOICES) forma = forms.ChoiceField(label="Forma řešení",choices = Reseni.FORMA_CHOICES)
#forma = models.CharField('forma řešení', max_length=16, choices=FORMA_CHOICES, blank=False, #forma = models.CharField('forma řešení', max_length=16, choices=FORMA_CHOICES, blank=False,
# default=FORMA_EMAIL) # default=FORMA_EMAIL)
@ -69,7 +72,7 @@ class PosliReseniForm(forms.Form):
class NahrajReseniForm(forms.ModelForm): class NahrajReseniForm(forms.ModelForm):
class Meta: class Meta:
model = m.Reseni model = Reseni
fields = ('problem', 'resitele') fields = ('problem', 'resitele')
help_texts = {'problem':''} # Nezobrazovat help text ve formuláři help_texts = {'problem':''} # Nezobrazovat help text ve formuláři
@ -109,11 +112,11 @@ class NahrajReseniForm(forms.ModelForm):
def clean_problem(self): def clean_problem(self):
problem = self.cleaned_data.get('problem') problem = self.cleaned_data.get('problem')
for p in problem: for p in problem:
if p.stav != m.Problem.STAV_ZADANY: if p.stav != Problem.STAV_ZADANY:
raise forms.ValidationError("Problém " + str(p) + " již nelze řešit!") raise forms.ValidationError("Problém " + str(p) + " již nelze řešit!")
return problem return problem
ReseniSPrilohamiFormSet = inlineformset_factory(m.Reseni,m.PrilohaReseni, ReseniSPrilohamiFormSet = inlineformset_factory(Reseni, PrilohaReseni,
form = NahrajReseniForm, form = NahrajReseniForm,
fields = ('soubor','res_poznamka'), fields = ('soubor','res_poznamka'),
widgets = {'res_poznamka':forms.TextInput()}, widgets = {'res_poznamka':forms.TextInput()},
@ -125,7 +128,7 @@ ReseniSPrilohamiFormSet = inlineformset_factory(m.Reseni,m.PrilohaReseni,
class JednoHodnoceniForm(forms.ModelForm): class JednoHodnoceniForm(forms.ModelForm):
class Meta: class Meta:
model = m.Hodnoceni model = Hodnoceni
fields = ('problem', 'body', 'deadline_body', 'feedback',) fields = ('problem', 'body', 'deadline_body', 'feedback',)
widgets = { widgets = {
'problem': autocomplete.ModelSelect2( 'problem': autocomplete.ModelSelect2(
@ -158,7 +161,7 @@ OhodnoceniReseniFormSet = formset_factory(JednoHodnoceniForm,
class PoznamkaReseniForm(forms.ModelForm): class PoznamkaReseniForm(forms.ModelForm):
class Meta: class Meta:
model = m.Reseni model = Reseni
fields = ('poznamka',) fields = ('poznamka',)
# FIXME: Ideálně by mělo být součástí třídy níž, ale neumím to udělat # FIXME: Ideálně by mělo být součástí třídy níž, ale neumím to udělat
@ -198,7 +201,7 @@ class OdevzdavatkoTabulkaFiltrForm(forms.Form):
from django.db.utils import OperationalError from django.db.utils import OperationalError
try: try:
aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik aktualni_rocnik = Nastaveni.get_solo().aktualni_rocnik
except OperationalError: except OperationalError:
# django.db.utils.OperationalError: no such table: seminar_nastaveni # django.db.utils.OperationalError: no such table: seminar_nastaveni
# Nemáme databázi, takže to selhalo. Pro jistotu vrátíme aspoň dvě možnosti, ať to nepadá dál # Nemáme databázi, takže to selhalo. Pro jistotu vrátíme aspoň dvě možnosti, ať to nepadá dál
@ -214,7 +217,7 @@ class OdevzdavatkoTabulkaFiltrForm(forms.Form):
result.append(("0001-01-01", f"Odjakživa")) result.append(("0001-01-01", f"Odjakživa"))
for deadline in m.Deadline.objects.filter( for deadline in Deadline.objects.filter(
deadline__lte=timezone.now(), deadline__lte=timezone.now(),
cislo__rocnik=aktualni_rocnik cislo__rocnik=aktualni_rocnik
).order_by("deadline"): ).order_by("deadline"):

16
odevzdavatko/models.py

@ -9,7 +9,7 @@ from django.urls import reverse_lazy
from django.utils import timezone from django.utils import timezone
from django.conf import settings from django.conf import settings
import tvorba.models as am from tvorba.models import Problem, Deadline, Cislo, Uloha, aux_generate_filename
from seminar.models import base as bm from seminar.models import base as bm
from odevzdavatko.utils import vzorecek_na_prepocet, inverze_vzorecku_na_prepocet from odevzdavatko.utils import vzorecek_na_prepocet, inverze_vzorecku_na_prepocet
@ -29,7 +29,7 @@ class Reseni(bm.SeminarModelBase):
id = models.AutoField(primary_key = True) id = models.AutoField(primary_key = True)
# Ke každé dvojici řešní a problém existuje nanejvýš jedno hodnocení, doplnění vazby. # Ke každé dvojici řešní a problém existuje nanejvýš jedno hodnocení, doplnění vazby.
problem = models.ManyToManyField(am.Problem, verbose_name='problém', help_text='Problém', problem = models.ManyToManyField(Problem, verbose_name='problém', help_text='Problém',
through='Hodnoceni') through='Hodnoceni')
resitele = models.ManyToManyField(Resitel, verbose_name='autoři řešení', resitele = models.ManyToManyField(Resitel, verbose_name='autoři řešení',
@ -79,7 +79,7 @@ class Reseni(bm.SeminarModelBase):
# NOTE: Potenciální DB HOG (bez select_related) # NOTE: Potenciální DB HOG (bez select_related)
def deadline_reseni(self): def deadline_reseni(self):
return am.Deadline.objects.filter(deadline__gte=self.cas_doruceni).order_by("deadline").first() return Deadline.objects.filter(deadline__gte=self.cas_doruceni).order_by("deadline").first()
## Pravdepodobne uz nebude potreba: ## Pravdepodobne uz nebude potreba:
# def save(self, *args, **kwargs): # def save(self, *args, **kwargs):
@ -101,16 +101,16 @@ class Hodnoceni(bm.SeminarModelBase):
body = models.DecimalField(max_digits=8, decimal_places=1, verbose_name='body', body = models.DecimalField(max_digits=8, decimal_places=1, verbose_name='body',
blank=True, null=True) blank=True, null=True)
cislo_body = models.ForeignKey(am.Cislo, verbose_name='číslo pro body', cislo_body = models.ForeignKey(Cislo, verbose_name='číslo pro body',
related_name='hodnoceni', blank=True, null=True, on_delete=models.PROTECT) related_name='hodnoceni', blank=True, null=True, on_delete=models.PROTECT)
# V ročníku < 26 nastaveno na deadline vygenerovaný pro původní cislo_body # V ročníku < 26 nastaveno na deadline vygenerovaný pro původní cislo_body
deadline_body = models.ForeignKey(am.Deadline, verbose_name='deadline pro body', deadline_body = models.ForeignKey(Deadline, verbose_name='deadline pro body',
related_name='hodnoceni', blank=True, null=True, on_delete=models.PROTECT) related_name='hodnoceni', blank=True, null=True, on_delete=models.PROTECT)
reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE) reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE)
problem = models.ForeignKey(am.Problem, verbose_name='problém', problem = models.ForeignKey(Problem, verbose_name='problém',
related_name='hodnoceni', on_delete=models.PROTECT) related_name='hodnoceni', on_delete=models.PROTECT)
feedback = models.TextField('zpětná vazba', blank=True, default='', help_text='Zpětná vazba řešiteli (plain text)') feedback = models.TextField('zpětná vazba', blank=True, default='', help_text='Zpětná vazba řešiteli (plain text)')
@ -166,7 +166,7 @@ class Hodnoceni(bm.SeminarModelBase):
@property @property
def body_neprepocitane_max(self): def body_neprepocitane_max(self):
if not isinstance(self.problem.get_real_instance(), am.Uloha): if not isinstance(self.problem.get_real_instance(), Uloha):
return None return None
return self.problem.uloha.max_body return self.problem.uloha.max_body
@ -176,7 +176,7 @@ class Hodnoceni(bm.SeminarModelBase):
def generate_filename(self, filename): def generate_filename(self, filename):
return os.path.join( return os.path.join(
settings.SEMINAR_RESENI_DIR, settings.SEMINAR_RESENI_DIR,
am.aux_generate_filename(self, filename) aux_generate_filename(self, filename)
) )

92
odevzdavatko/views.py

@ -17,10 +17,14 @@ from decimal import Decimal
from itertools import groupby from itertools import groupby
import logging import logging
import seminar.models as m
from . import forms as f from . import forms as f
from .forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm 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 tvorba.utils import resi_v_rocniku
from various.models import Nastaveni
from various.views.pomocne import formularOKView from various.views.pomocne import formularOKView
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -40,20 +44,20 @@ logger = logging.getLogger(__name__)
class TabulkaOdevzdanychReseniView(ListView): class TabulkaOdevzdanychReseniView(ListView):
template_name = 'odevzdavatko/tabulka.html' template_name = 'odevzdavatko/tabulka.html'
model = m.Hodnoceni model = Hodnoceni
def inicializuj_osy_tabulky(self): def inicializuj_osy_tabulky(self):
"""Vyrobí prvotní querysety pro sloupce a řádky, tj. seznam všech řešitelů a problémů""" """Vyrobí prvotní querysety pro sloupce a řádky, tj. seznam všech řešitelů a problémů"""
# FIXME: jméno metody není vypovídající... # 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 # 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, ... # TODO: Prefetches, Select related, ...
self.resitele = m.Resitel.objects.all() self.resitele = Resitel.objects.all()
self.problemy = m.Problem.objects.all() self.problemy = Problem.objects.all()
self.reseni = m.Reseni.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: 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) form = FiltrForm(self.request.GET, rocnik=self.aktualni_rocnik)
if form.is_valid(): if form.is_valid():
@ -86,14 +90,14 @@ class TabulkaOdevzdanychReseniView(ListView):
self.resitele = self.resitele.filter(rok_maturity__gt=self.aktualni_rocnik.prvni_rok) self.resitele = self.resitele.filter(rok_maturity__gt=self.aktualni_rocnik.prvni_rok)
if problemy == FiltrForm.PROBLEMY_MOJE: 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( self.problemy = self.problemy.filter(
Q(autor=org)|Q(garant=org)|Q(opravovatele=org), 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: elif problemy == FiltrForm.PROBLEMY_LETOSNI:
self.problemy = self.problemy.filter( 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.... #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. # 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 = super().get_context_data(*args, **kwargs)
ctx['problemy'] = self.problemy ctx['problemy'] = self.problemy
ctx['resitele'] = self.resitele ctx['resitele'] = self.resitele
tabulka: dict[m.Problem, dict[m.Resitel, list[tuple[m.Reseni, m.Hodnoceni]]]] = dict() tabulka: dict[Problem, dict[Resitel, list[tuple[Reseni, Hodnoceni]]]] = dict()
soucty: dict[m.Problem, dict[m.Resitel, Decimal]] = dict() soucty: dict[Problem, dict[Resitel, Decimal]] = dict()
def pridej_reseni(resitel, hodnoceni): def pridej_reseni(resitel, hodnoceni):
problem = hodnoceni.problem problem = hodnoceni.problem
@ -143,11 +147,11 @@ class TabulkaOdevzdanychReseniView(ListView):
for resitel in hodnoceni.reseni.resitele.all(): for resitel in hodnoceni.reseni.resitele.all():
pridej_reseni(resitel, hodnoceni) 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. 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[m.Resitel] = [] resitele_do_tabulky: list[Resitel] = []
for resitel in self.resitele: for resitel in self.resitele:
dostal_body = False 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: for problem in self.problemy:
if problem in tabulka and resitel in tabulka[problem]: if problem in tabulka and resitel in tabulka[problem]:
resiteluv_radek.append((soucty[problem][resitel], tabulka[problem][resitel])) 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: # Pro použití hacku na automatické {{form.media}} v template:
ctx['form'] = ctx['filtr'] ctx['form'] = ctx['filtr']
# Pro maximum v přesměrovátku ročníků # 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 ctx['barvicky'] = self.barvicky
if 'rocnik' in self.kwargs: if 'rocnik' in self.kwargs:
ctx['rocnik'] = self.kwargs['rocnik'] ctx['rocnik'] = self.kwargs['rocnik']
@ -178,7 +182,7 @@ class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixi
Asi bude zastaralý v okamžiku, kdy se tenhle komentář nasadí na produkci :-) 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.""" 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' template_name = 'odevzdavatko/seznam.html'
def get_queryset(self): def get_queryset(self):
@ -190,8 +194,8 @@ class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixi
if problem_id is None: if problem_id is None:
raise ValueError("Nemám problém! (To je problém!)") raise ValueError("Nemám problém! (To je problém!)")
resitel = m.Resitel.objects.get(id=resitel_id) resitel = Resitel.objects.get(id=resitel_id)
problem = m.Problem.objects.get(id=problem_id) problem = Problem.objects.get(id=problem_id)
qs = qs.filter( qs = qs.filter(
problem__in=[problem], problem__in=[problem],
resitele__in=[resitel], 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 ## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex
class DetailReseniView(DetailView): class DetailReseniView(DetailView):
""" Náhled na řešení. Editace je v :py:class:`EditReseniView`. """ """ Náhled na řešení. Editace je v :py:class:`EditReseniView`. """
model = m.Reseni model = Reseni
template_name = 'odevzdavatko/detail.html' template_name = 'odevzdavatko/detail.html'
def aktualni_hodnoceni(self): 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 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 = [ seznam_atributu = [
"problem", "problem",
"body", "body",
@ -284,7 +288,7 @@ class EditReseniView(DetailReseniView):
def hodnoceniReseniView(request, pk, *args, **kwargs): 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}) success_url = reverse('odevzdavatko_detail_reseni', kwargs={'pk': pk})
# FIXME: Použit initial i tady a nebastlit hodnocení tak nízkoúrovňově # 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() poznamka_form.save()
# Smažeme všechna dosavadní hodnocení tohoto řešení # 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}") logger.info(f"Will delete {qs.count()} objects: {qs}")
qs.delete() qs.delete()
@ -311,7 +315,7 @@ def hodnoceniReseniView(request, pk, *args, **kwargs):
del(data_for_hodnoceni["body_celkem"]) del(data_for_hodnoceni["body_celkem"])
del(data_for_hodnoceni["body_neprepocitane"]) del(data_for_hodnoceni["body_neprepocitane"])
del(data_for_hodnoceni["body_neprepocitane_celkem"]) del(data_for_hodnoceni["body_neprepocitane_celkem"])
hodnoceni = m.Hodnoceni( hodnoceni = Hodnoceni(
reseni=reseni, reseni=reseni,
**form.cleaned_data, **form.cleaned_data,
) )
@ -332,14 +336,14 @@ def hodnoceniReseniView(request, pk, *args, **kwargs):
class PrehledOdevzdanychReseni(ListView): class PrehledOdevzdanychReseni(ListView):
model = m.Hodnoceni model = Hodnoceni
template_name = 'odevzdavatko/prehled_reseni.html' template_name = 'odevzdavatko/prehled_reseni.html'
def get_queryset(self): def get_queryset(self):
if not self.request.user.is_authenticated: if not self.request.user.is_authenticated:
raise RuntimeError("Uživatel měl být přihlášený!") raise RuntimeError("Uživatel měl být přihlášený!")
# get_or_none, aby neexistence řešitele (např. u orgů) neházela chybu # 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 = super().get_queryset()
qs = qs.filter(reseni__resitele__in=[resitel]) 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í # 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í # Přehled všech řešení kvůli debugování
class SeznamReseniView(ListView): class SeznamReseniView(ListView):
model = m.Reseni model = Reseni
template_name = 'odevzdavatko/seznam.html' template_name = 'odevzdavatko/seznam.html'
class SeznamAktualnichReseniView(SeznamReseniView): class SeznamAktualnichReseniView(SeznamReseniView):
def get_queryset(self): def get_queryset(self):
qs = super().get_queryset() 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) 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 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 return qs
@ -378,7 +382,7 @@ class VlozReseniView(LoginRequiredMixin, FormView):
def form_valid(self, form): def form_valid(self, form):
data = form.cleaned_data data = form.cleaned_data
nove_reseni = m.Reseni.objects.create( nove_reseni = Reseni.objects.create(
cas_doruceni=data['cas_doruceni'], cas_doruceni=data['cas_doruceni'],
forma=data['forma'], forma=data['forma'],
poznamka=data['poznamka'], poznamka=data['poznamka'],
@ -405,35 +409,35 @@ class VlozReseniView(LoginRequiredMixin, FormView):
class NahrajReseniRozcestnikTematekView(LoginRequiredMixin, ListView): class NahrajReseniRozcestnikTematekView(LoginRequiredMixin, ListView):
model = m.Problem model = Problem
template_name = 'odevzdavatko/nahraj_reseni_nadproblem.html' template_name = 'odevzdavatko/nahraj_reseni_nadproblem.html'
def get_queryset(self): 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): class NahrajReseniView(LoginRequiredMixin, CreateView):
model = m.Reseni model = Reseni
template_name = 'odevzdavatko/nahraj_reseni.html' template_name = 'odevzdavatko/nahraj_reseni.html'
form_class = f.NahrajReseniForm form_class = f.NahrajReseniForm
nadproblem: m.Problem nadproblem: Problem
def setup(self, request, *args, **kwargs): def setup(self, request, *args, **kwargs):
super().setup(request, *args, **kwargs) super().setup(request, *args, **kwargs)
nadproblem_id = self.kwargs["nadproblem_id"] 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): def get(self, request, *args, **kwargs):
# Zaříznutí nezadaných problémů # Zaříznutí nezadaných problémů
if self.nadproblem.stav != m.Problem.STAV_ZADANY: if self.nadproblem.stav != Problem.STAV_ZADANY:
raise PermissionDenied() raise PermissionDenied()
# Zaříznutí starých řešitelů: # Zaříznutí starých řešitelů:
# FIXME: Je to tady dost naprasené, mělo by to asi být jinde… # 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 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', { return render(request, 'universal.html', {
'title': 'Nelze odevzdat', 'title': 'Nelze odevzdat',
'error': 'Zdá se, že jsi již odmaturoval/a, a tedy nemůžeš odevzdat do našeho semináře řešení.', '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 nadproblem_id = self.nadproblem.id
return { return {
"nadproblem_id": nadproblem_id, "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['prilohy'] = f.ReseniSPrilohamiFormSet()
data["nadproblem_id"] = self.nadproblem.id 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 return data
# FIXME prepsat tak, aby form_valid se volalo jen tehdy, kdyz je form i formset validni # 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) return super().form_invalid(form)
with transaction.atomic(): with transaction.atomic():
self.object = form.save() 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.resitele.add(*form.cleaned_data["resitele"])
self.object.cas_doruceni = timezone.now() self.object.cas_doruceni = timezone.now()
self.object.forma = m.Reseni.FORMA_UPLOAD self.object.forma = Reseni.FORMA_UPLOAD
self.object.save() self.object.save()
prilohy.instance = self.object prilohy.instance = self.object
prilohy.save() prilohy.save()
for hodnoceni in self.object.hodnoceni_set.all(): 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() hodnoceni.save()
# Pošleme mail opravovatelům a garantovi # 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? # FIXME: Víc informativní obsah mailů, možná vč. příloh?
prijemci = map(lambda it: it.osoba.email, prijemci) 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 = "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 })") seznam_do_subjectu = "problému " + str(problemy[0]) + ("" if len(problemy) == 1 else f" (a dalším { len(problemy) - 1 })")

12
personalni/views.py

@ -16,10 +16,12 @@ from django.db import transaction
from django.http import HttpResponse from django.http import HttpResponse
from django.utils import timezone from django.utils import timezone
import seminar.models as s
import personalni.models as m import personalni.models as m
from soustredeni.models import Soustredeni from soustredeni.models import Soustredeni
from odevzdavatko.models import Hodnoceni from odevzdavatko.models import Hodnoceni
from tvorba.models import Clanek, Uloha, Tema
from various.models import Nastaveni
from .forms import PrihlaskaForm, ProfileEditForm, PoMaturiteProfileEditForm from .forms import PrihlaskaForm, ProfileEditForm, PoMaturiteProfileEditForm
from datetime import date from datetime import date
@ -94,7 +96,7 @@ class OrgoRozcestnikView(TemplateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['posledni_soustredeni'] = Soustredeni.objects.order_by('-datum_konce').first() context['posledni_soustredeni'] = Soustredeni.objects.order_by('-datum_konce').first()
nastaveni = s.Nastaveni.objects.first() nastaveni = Nastaveni.objects.first()
aktualni_rocnik = nastaveni.aktualni_rocnik aktualni_rocnik = nastaveni.aktualni_rocnik
context['posledni_cislo_url'] = nastaveni.aktualni_cislo.verejne_url() context['posledni_cislo_url'] = nastaveni.aktualni_cislo.verejne_url()
# TODO možná chceme odkazovat na právě rozpracované číslo, a ne to poslední vydané # TODO možná chceme odkazovat na právě rozpracované číslo, a ne to poslední vydané
@ -118,11 +120,11 @@ class OrgoRozcestnikView(TemplateView):
context["pocty_neopravenych_reseni"] = [(it['problem__nazev'], it['cas'].date) for it in pocty_neopravenych_reseni.all()] context["pocty_neopravenych_reseni"] = [(it['problem__nazev'], it['cas'].date) for it in pocty_neopravenych_reseni.all()]
#FIXME: přidat stav='STAV_ZADANY' #FIXME: přidat stav='STAV_ZADANY'
temata = s.Tema.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]), temata = Tema.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]),
rocnik=aktualni_rocnik).distinct() rocnik=aktualni_rocnik).distinct()
ulohy = s.Uloha.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]), ulohy = Uloha.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]),
cislo_zadani__rocnik=aktualni_rocnik).distinct() cislo_zadani__rocnik=aktualni_rocnik).distinct()
clanky = s.Clanek.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]), clanky = Clanek.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]),
cislo__rocnik=aktualni_rocnik).distinct() cislo__rocnik=aktualni_rocnik).distinct()
context['temata'] = temata context['temata'] = temata

8
soustredeni/models.py

@ -10,7 +10,7 @@ from django.conf import settings
from personalni.models import Resitel, Organizator from personalni.models import Resitel, Organizator
from seminar.models.base import SeminarModelBase from seminar.models.base import SeminarModelBase
import tvorba.models as am from tvorba.models import Rocnik, Problem, aux_generate_filename
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -27,7 +27,7 @@ class Soustredeni(SeminarModelBase):
# Interní ID # Interní ID
id = models.AutoField(primary_key = True) id = models.AutoField(primary_key = True)
rocnik = models.ForeignKey(am.Rocnik, verbose_name='ročník', related_name='soustredeni', rocnik = models.ForeignKey(Rocnik, verbose_name='ročník', related_name='soustredeni',
on_delete=models.PROTECT) on_delete=models.PROTECT)
datum_zacatku = models.DateField('datum začátku', blank=True, null=True, datum_zacatku = models.DateField('datum začátku', blank=True, null=True,
@ -143,13 +143,13 @@ class Soustredeni_Organizatori(SeminarModelBase):
def generate_filename_konfera(self, filename): def generate_filename_konfera(self, filename):
return os.path.join( return os.path.join(
settings.SEMINAR_KONFERY_DIR, settings.SEMINAR_KONFERY_DIR,
am.aux_generate_filename(self, filename) aux_generate_filename(self, filename)
) )
## ##
@reversion.register(ignore_duplicates=True) @reversion.register(ignore_duplicates=True)
class Konfera(am.Problem): class Konfera(Problem):
class Meta: class Meta:
db_table = 'seminar_konfera' db_table = 'seminar_konfera'
verbose_name = 'Konfera' verbose_name = 'Konfera'

4
soustredeni/testutils.py

@ -6,7 +6,7 @@ from typing import Sequence
import lorem import lorem
from .models import Soustredeni, Konfera from .models import Soustredeni, Konfera
import seminar.models as am # tvorba from tvorba.models import Rocnik
import personalni.models as pm import personalni.models as pm
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -25,7 +25,7 @@ def gen_soustredeni(
for _ in range(1, 10): # FIXME Tu range si změňte jak chcete, nevím, co přesně znamená size (asi Anet?) for _ in range(1, 10): # FIXME Tu range si změňte jak chcete, nevím, co přesně znamená size (asi Anet?)
datum_zacatku = datetime.date(rnd.randint(2000, 2020), rnd.randint(1, 12), rnd.randint(1, 28)) datum_zacatku = datetime.date(rnd.randint(2000, 2020), rnd.randint(1, 12), rnd.randint(1, 28))
working_sous = Soustredeni.objects.create( working_sous = Soustredeni.objects.create(
rocnik=am.Rocnik.objects.order_by('?').first(), rocnik=Rocnik.objects.order_by('?').first(),
verejne_db=rnd.choice([True, False]), verejne_db=rnd.choice([True, False]),
misto=rnd.choice(['Kremrolovice', 'Indiánov', 'U zmzliny', 'Vafláreň', 'Větrník', 'Horní Rakvička', 'Dolní cheesecake']), misto=rnd.choice(['Kremrolovice', 'Indiánov', 'U zmzliny', 'Vafláreň', 'Větrník', 'Horní Rakvička', 'Dolní cheesecake']),
typ=rnd.choice(['jarni', 'podzimni', 'vikend']), typ=rnd.choice(['jarni', 'podzimni', 'vikend']),

22
tvorba/templatetags/deadliny.py

@ -1,30 +1,30 @@
from django import template from django import template
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
register = template.Library() register = template.Library()
import seminar.models as m from tvorba.models import Deadline
@register.filter(name='deadline_kratseji') @register.filter(name='deadline_kratseji')
def deadline_kratsi_text(deadline: m.Deadline): def deadline_kratsi_text(deadline: Deadline):
if deadline is None: if deadline is None:
return 'NONE' return 'NONE'
strings = { strings = {
m.Deadline.TYP_PRVNI: f"{deadline.cislo}", Deadline.TYP_PRVNI: f"{deadline.cislo}",
m.Deadline.TYP_SOUS: f"{deadline.cislo}", Deadline.TYP_SOUS: f"{deadline.cislo}",
m.Deadline.TYP_PRVNI_A_SOUS: f"{deadline.cislo} ⭯Ⓢ", Deadline.TYP_PRVNI_A_SOUS: f"{deadline.cislo} ⭯Ⓢ",
m.Deadline.TYP_CISLA: f"{deadline.cislo}", Deadline.TYP_CISLA: f"{deadline.cislo}",
} }
return strings[deadline.typ] return strings[deadline.typ]
@register.filter(name='deadline_html') @register.filter(name='deadline_html')
def deadline_html(deadline: m.Deadline): def deadline_html(deadline: Deadline):
if deadline is None: if deadline is None:
return 'Neznámý deadline' return 'Neznámý deadline'
text = deadline_kratsi_text(deadline) text = deadline_kratsi_text(deadline)
classes = { classes = {
m.Deadline.TYP_PRVNI: 'preddeadline', Deadline.TYP_PRVNI: 'preddeadline',
m.Deadline.TYP_SOUS: 'sous_deadline', Deadline.TYP_SOUS: 'sous_deadline',
m.Deadline.TYP_PRVNI_A_SOUS: 'sous_deadline', Deadline.TYP_PRVNI_A_SOUS: 'sous_deadline',
m.Deadline.TYP_CISLA: 'final_deadline', Deadline.TYP_CISLA: 'final_deadline',
} }
return mark_safe(f'<span class="{classes[deadline.typ]}" title="{deadline}">{text}</span>') return mark_safe(f'<span class="{classes[deadline.typ]}" title="{deadline}">{text}</span>')

56
tvorba/testutils.py

@ -6,7 +6,9 @@ import lorem
import django.contrib.auth import django.contrib.auth
import logging import logging
from seminar.models import Rocnik, Cislo, Deadline, Problem, Tema, Uloha, TextNode, UlohaVzorakNode, RocnikNode, CisloNode, TemaVCisleNode, Text, UlohaZadaniNode from .models import Rocnik, Cislo, Deadline, Problem, Tema, Uloha
from odevzdavatko.models import Reseni, Hodnoceni
import seminar.models as m import seminar.models as m
from treenode.treelib import all_children, insert_last_child, all_children_of_type, create_node_after from treenode.treelib import all_children, insert_last_child, all_children_of_type, create_node_after
@ -54,12 +56,12 @@ def gen_zadani_ulohy(rnd, cisla, organizatori, pocet_oboru, poradi_cisla, poradi
rnd.choice(jmeno), rnd.choice(jmeno),
rnd.choice(kde)] rnd.choice(kde)]
) )
text_zadani = Text.objects.create( text_zadani = m.Text.objects.create(
na_web = text, na_web = text,
do_cisla = text, do_cisla = text,
) )
zad = TextNode.objects.create(text = text_zadani, root = p.cislo_zadani.rocnik.rocniknode) zad = m.TextNode.objects.create(text = text_zadani, root = p.cislo_zadani.rocnik.rocniknode)
uloha_zadani = UlohaZadaniNode.objects.create(uloha = p, first_child = zad, root = p.cislo_zadani.rocnik.rocniknode) uloha_zadani = m.UlohaZadaniNode.objects.create(uloha = p, first_child = zad, root = p.cislo_zadani.rocnik.rocniknode)
p.ulohazadaninode = uloha_zadani p.ulohazadaninode = uloha_zadani
otec_syn(cisla[poradi_cisla-2-1].cislonode, uloha_zadani) otec_syn(cisla[poradi_cisla-2-1].cislonode, uloha_zadani)
@ -76,12 +78,12 @@ def gen_vzoroveho_reseni_ulohy(rnd, organizatori, uloha, pocet_opravovatelu):
# Generování vzorového řešení. # Generování vzorového řešení.
obsah = rnd.choice(reseni) obsah = rnd.choice(reseni)
text_vzoraku = Text.objects.create( text_vzoraku = m.Text.objects.create(
na_web = obsah, na_web = obsah,
do_cisla = obsah do_cisla = obsah
) )
vzorak = TextNode.objects.create(text = text_vzoraku, root = uloha.cislo_zadani.rocnik.rocniknode) vzorak = m.TextNode.objects.create(text = text_vzoraku, root = uloha.cislo_zadani.rocnik.rocniknode)
uloha_vzorak = UlohaVzorakNode.objects.create(uloha = uloha, first_child = vzorak, root = uloha.cislo_zadani.rocnik.rocniknode) uloha_vzorak = m.UlohaVzorakNode.objects.create(uloha = uloha, first_child = vzorak, root = uloha.cislo_zadani.rocnik.rocniknode)
uloha.ulohavzoraknode = uloha_vzorak uloha.ulohavzoraknode = uloha_vzorak
uloha.opravovatele.set(rnd.sample(organizatori, pocet_opravovatelu)) uloha.opravovatele.set(rnd.sample(organizatori, pocet_opravovatelu))
@ -132,7 +134,7 @@ def gen_rocniky(last_rocnik, size):
node = None node = None
for ri in range(min(last_rocnik - size, 1), last_rocnik + 1): for ri in range(min(last_rocnik - size, 1), last_rocnik + 1):
rocnik = Rocnik.objects.create(prvni_rok = 1993 + ri, rocnik = ri) rocnik = Rocnik.objects.create(prvni_rok = 1993 + ri, rocnik = ri)
node2 = RocnikNode.objects.create(rocnik = rocnik, succ = node) node2 = m.RocnikNode.objects.create(rocnik = rocnik, succ = node)
rocnik.save() rocnik.save()
node = node2 node = node2
rocniky.append(rocnik) rocniky.append(rocnik)
@ -167,7 +169,7 @@ def gen_cisla(rnd, rocniky):
datum_vydani=vydano, datum_vydani=vydano,
verejne_db=True, verejne_db=True,
) )
node2 = CisloNode.objects.get(cislo = cislo) node2 = m.CisloNode.objects.get(cislo = cislo)
node2.succ = node node2.succ = node
node2.root = rocnik.rocniknode node2.root = rocnik.rocniknode
cislo.save() cislo.save()
@ -195,7 +197,7 @@ def add_first_child(node, child):
def get_text(): def get_text():
odstavec = lorem.paragraph() odstavec = lorem.paragraph()
return Text.objects.create(na_web = odstavec, do_cisla = odstavec) return m.Text.objects.create(na_web = odstavec, do_cisla = odstavec)
def gen_dlouhe_tema(rnd, organizatori, rocnik, nazev, obor, kod): def gen_dlouhe_tema(rnd, organizatori, rocnik, nazev, obor, kod):
tema = Tema.objects.create( tema = Tema.objects.create(
@ -215,32 +217,32 @@ def gen_dlouhe_tema(rnd, organizatori, rocnik, nazev, obor, kod):
for cislo in cisla: for cislo in cisla:
# Přidáme TemaVCisleNode do daného čísla # Přidáme TemaVCisleNode do daného čísla
cislo_node = cislo.cislonode cislo_node = cislo.cislonode
tema_cislo_node = TemaVCisleNode.objects.create(tema = tema, root = cislo_node.root) tema_cislo_node = m.TemaVCisleNode.objects.create(tema = tema, root = cislo_node.root)
insert_last_child(cislo_node, tema_cislo_node) insert_last_child(cislo_node, tema_cislo_node)
# Přidávání obsahu do čísla # Přidávání obsahu do čísla
cast_node = m.CastNode.objects.create(nadpis = "Příspěvek k číslu {}".format(cislo.kod), root=cislo_node.root) cast_node = m.CastNode.objects.create(nadpis = "Příspěvek k číslu {}".format(cislo.kod), root=cislo_node.root)
add_first_child(tema_cislo_node, cast_node) add_first_child(tema_cislo_node, cast_node)
text_node = TextNode.objects.create(text = get_text(), root=cislo_node.root) text_node = m.TextNode.objects.create(text = get_text(), root=cislo_node.root)
add_first_child(cast_node, text_node) add_first_child(cast_node, text_node)
cast_node2 = m.CastNode.objects.create(nadpis = "První podproblém", root=cislo_node.root) cast_node2 = m.CastNode.objects.create(nadpis = "První podproblém", root=cislo_node.root)
add_first_child(text_node, cast_node2) add_first_child(text_node, cast_node2)
text_node2 = TextNode.objects.create(text = get_text(), root=cislo_node.root) text_node2 = m.TextNode.objects.create(text = get_text(), root=cislo_node.root)
add_first_child(cast_node2, text_node2) add_first_child(cast_node2, text_node2)
cast_node3 = m.CastNode.objects.create(nadpis = "Druhý podproblém", root=cislo_node.root) cast_node3 = m.CastNode.objects.create(nadpis = "Druhý podproblém", root=cislo_node.root)
add_first_child(text_node2, cast_node3) add_first_child(text_node2, cast_node3)
text_node3 = TextNode.objects.create(text = get_text(), root=cislo_node.root) text_node3 = m.TextNode.objects.create(text = get_text(), root=cislo_node.root)
add_first_child(cast_node3, text_node3) add_first_child(cast_node3, text_node3)
cast_node4 = m.CastNode.objects.create(nadpis = "Třetí podproblém", root=cislo_node.root) cast_node4 = m.CastNode.objects.create(nadpis = "Třetí podproblém", root=cislo_node.root)
add_first_child(text_node3, cast_node4) add_first_child(text_node3, cast_node4)
text_node4 = TextNode.objects.create(text = get_text(), root=cislo_node.root) text_node4 = m.TextNode.objects.create(text = get_text(), root=cislo_node.root)
add_first_child(cast_node3, text_node4) add_first_child(cast_node3, text_node4)
cast_node3a = m.CastNode.objects.create(nadpis = "Podproblém paralelní s " cast_node3a = m.CastNode.objects.create(nadpis = "Podproblém paralelní s "
@ -248,7 +250,7 @@ def gen_dlouhe_tema(rnd, organizatori, rocnik, nazev, obor, kod):
cast_node3.succ = cast_node3a cast_node3.succ = cast_node3a
cast_node3.save() cast_node3.save()
text_node3a = TextNode.objects.create(text = get_text(), root=cislo_node.root) text_node3a = m.TextNode.objects.create(text = get_text(), root=cislo_node.root)
add_first_child(cast_node3a, text_node3a) add_first_child(cast_node3a, text_node3a)
# Občas přidáme mezičíslo # Občas přidáme mezičíslo
@ -261,8 +263,8 @@ def gen_dlouhe_tema(rnd, organizatori, rocnik, nazev, obor, kod):
add_first_child(mezicislo_node, cast_node_mezicislo) add_first_child(mezicislo_node, cast_node_mezicislo)
odstavec = lorem.paragraph() odstavec = lorem.paragraph()
text_mezicislo = Text.objects.create(na_web = odstavec, do_cisla = odstavec) text_mezicislo = m.Text.objects.create(na_web = odstavec, do_cisla = odstavec)
text_node_mezicislo = TextNode.objects.create(text = text_mezicislo, root=cislo_node.root) text_node_mezicislo = m.TextNode.objects.create(text = text_mezicislo, root=cislo_node.root)
add_first_child(cast_node_mezicislo, text_node_mezicislo) add_first_child(cast_node_mezicislo, text_node_mezicislo)
return tema return tema
@ -306,7 +308,7 @@ def gen_temata(rnd, rocniky, rocnik_cisla, organizatori):
# Vyrobíme TemaVCisleNody pro obsah # Vyrobíme TemaVCisleNody pro obsah
for i in range(zacatek_tematu, konec_tematu+1): for i in range(zacatek_tematu, konec_tematu+1):
node = TemaVCisleNode.objects.create(tema = t,root=rocnik.rocniknode) node = m.TemaVCisleNode.objects.create(tema = t,root=rocnik.rocniknode)
# FIXME: Není to off-by-one? # FIXME: Není to off-by-one?
otec = cisla[i-1].cislonode otec = cisla[i-1].cislonode
otec_syn(otec, node) otec_syn(otec, node)
@ -359,12 +361,12 @@ def gen_ulohy_tematu(rnd, organizatori, resitele, tema, kod, cislo, cislo_se_vzo
rnd.choice(jmeno), rnd.choice(jmeno),
rnd.choice(kde)] rnd.choice(kde)]
) )
text_zadani = Text.objects.create( text_zadani = m.Text.objects.create(
na_web = obsah, na_web = obsah,
do_cisla = obsah, do_cisla = obsah,
) )
zad = TextNode.objects.create(text = text_zadani, root=tema.temavcislenode_set.first().root) zad = m.TextNode.objects.create(text = text_zadani, root=tema.temavcislenode_set.first().root)
uloha_zadani = UlohaZadaniNode.objects.create(uloha=uloha, first_child = zad, root=tema.temavcislenode_set.first().root) uloha_zadani = m.UlohaZadaniNode.objects.create(uloha=uloha, first_child = zad, root=tema.temavcislenode_set.first().root)
uloha.ulohazadaninode = uloha_zadani uloha.ulohazadaninode = uloha_zadani
# Generování řešení a hodnocení k úloze # Generování řešení a hodnocení k úloze
@ -396,7 +398,7 @@ def gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori,
else: else:
cislo_se_vzorakem = cislo_se_vzorakem.first() cislo_se_vzorakem = cislo_se_vzorakem.first()
for tema_node in all_children_of_type(cislo.cislonode, TemaVCisleNode): for tema_node in all_children_of_type(cislo.cislonode, m.TemaVCisleNode):
tema = tema_node.tema tema = tema_node.tema
# Pokud už témátko skončilo, žádné úložky negenerujeme # Pokud už témátko skončilo, žádné úložky negenerujeme
@ -419,7 +421,7 @@ def gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori,
# Najdeme správný TemaVCisleNode pro vložení vzoráku # Najdeme správný TemaVCisleNode pro vložení vzoráku
res_tema_node = None; res_tema_node = None;
for node in all_children(cislo_se_vzorakem.cislonode): for node in all_children(cislo_se_vzorakem.cislonode):
if isinstance(node, TemaVCisleNode): if isinstance(node, m.TemaVCisleNode):
if node.tema == tema: if node.tema == tema:
res_tema_node = node res_tema_node = node
if res_tema_node is None: if res_tema_node is None:
@ -448,16 +450,16 @@ def gen_clanek(rnd, organizatori, resitele):
) )
clanek.save() clanek.save()
reseni = m.Reseni.objects.create( reseni = Reseni.objects.create(
zverejneno=True, zverejneno=True,
) )
reseni.resitele.add(rnd.choice(resitele)) reseni.resitele.add(rnd.choice(resitele))
reseni.save() reseni.save()
cislo = m.Cislo.objects.get(rocnik__rocnik=22, poradi=2) cislo = Cislo.objects.get(rocnik__rocnik=22, poradi=2)
cislonode = cislo.cislonode cislonode = cislo.cislonode
hodnoceni = m.Hodnoceni.objects.create( hodnoceni = Hodnoceni.objects.create(
body=15.0, body=15.0,
cislo_body=cislo, cislo_body=cislo,
reseni=reseni, reseni=reseni,

2
tvorba/utils.py

@ -2,7 +2,7 @@ from django.core.exceptions import ObjectDoesNotExist
import personalni.models import personalni.models
import seminar.models as m import tvorba.models as m
def resi_v_rocniku(rocnik, cislo=None): def resi_v_rocniku(rocnik, cislo=None):

55
tvorba/views/__init__.py

@ -14,12 +14,11 @@ from django.db.models import Q, Sum, Count
from django.views.generic.base import RedirectView from django.views.generic.base import RedirectView
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
import seminar.models as s
import seminar.models as m import seminar.models as m
from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, \ from personalni.models import Resitel
Resitel, Novinky, Tema, Clanek, \ from soustredeni.models import Konfera
Deadline # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci from tvorba.models import Problem, Cislo, Rocnik, Tema, Clanek, Deadline, Uloha
#from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva from various.models import Nastaveni
from treenode import treelib from treenode import treelib
import treenode.templatetags as tnltt import treenode.templatetags as tnltt
import treenode.serializers as vr import treenode.serializers as vr
@ -58,7 +57,7 @@ def get_problemy_k_tematu(tema):
# FIXME: Pozor, níž je ještě jeden ProblemView! # FIXME: Pozor, níž je ještě jeden ProblemView!
#class ProblemView(generic.DetailView): #class ProblemView(generic.DetailView):
# model = s.Problem # model = Problem
# # Zkopírujeme template_name od TreeNodeView, protože jsme prakticky jen trošku upravený TreeNodeView # # Zkopírujeme template_name od TreeNodeView, protože jsme prakticky jen trošku upravený TreeNodeView
# template_name = TreeNodeView.template_name # template_name = TreeNodeView.template_name
# #
@ -70,17 +69,17 @@ def get_problemy_k_tematu(tema):
# if False: # if False:
# # Hezčí formátování zbytku :-P # # Hezčí formátování zbytku :-P
# pass # pass
# elif isinstance(self.object, s.Clanek) or isinstance(self.object, s.Konfera): # elif isinstance(self.object, Clanek) or isinstance(self.object, Konfera):
# # Tyhle Problémy mají ŘešeníNode # # Tyhle Problémy mají ŘešeníNode
# context['tnldata'] = TNLData.from_treenode(self.object.reseninode,user) # context['tnldata'] = TNLData.from_treenode(self.object.reseninode,user)
# elif isinstance(self.object, s.Uloha): # elif isinstance(self.object, Uloha):
# # FIXME: Teď vždycky zobrazujeme i vzorák! Možná by bylo hezčí/lepší mít to stejně jako pro Téma: procházet jen dosažitelné z Ročníku / čísla / whatever # # FIXME: Teď vždycky zobrazujeme i vzorák! Možná by bylo hezčí/lepší mít to stejně jako pro Téma: procházet jen dosažitelné z Ročníku / čísla / whatever
# tnl_zadani = TNLData.from_treenode(self.object.ulohazadaninode,user) # tnl_zadani = TNLData.from_treenode(self.object.ulohazadaninode,user)
# tnl_vzorak = TNLData.from_treenode(self.object.ulohavzoraknode,user) # tnl_vzorak = TNLData.from_treenode(self.object.ulohavzoraknode,user)
# context['tnldata'] = TNLData.from_tnldata_list([tnl_zadani, tnl_vzorak]) # context['tnldata'] = TNLData.from_tnldata_list([tnl_zadani, tnl_vzorak])
# elif isinstance(self.object, s.Tema): # elif isinstance(self.object, Tema):
# rocniknode = self.object.rocnik.rocniknode # rocniknode = self.object.rocnik.rocniknode
# context['tnldata'] = TNLData.filter_treenode(rocniknode, lambda x: isinstance(x, s.TemaVCisleNode)) # context['tnldata'] = TNLData.filter_treenode(rocniknode, lambda x: isinstance(x, m.TemaVCisleNode))
# else: # else:
# raise ValueError("Obecný problém nejde zobrazit.") # raise ValueError("Obecný problém nejde zobrazit.")
# return context # return context
@ -115,7 +114,7 @@ def ZadaniTemataView(request):
nastaveni = get_object_or_404(Nastaveni) nastaveni = get_object_or_404(Nastaveni)
verejne = nastaveni.aktualni_cislo.verejne() verejne = nastaveni.aktualni_cislo.verejne()
akt_rocnik = nastaveni.aktualni_cislo.rocnik akt_rocnik = nastaveni.aktualni_cislo.rocnik
temata = s.Tema.objects.filter(rocnik=akt_rocnik, stav='zadany') temata = Tema.objects.filter(rocnik=akt_rocnik, stav='zadany')
return render(request, 'tvorba/tematka/rozcestnik.html', return render(request, 'tvorba/tematka/rozcestnik.html',
{ {
'tematka': temata, 'tematka': temata,
@ -140,14 +139,14 @@ def ZadaniTemataView(request):
# #
# #
#def TematkoView(request, rocnik, tematko): #def TematkoView(request, rocnik, tematko):
# nastaveni = s.Nastaveni.objects.first() # nastaveni = Nastaveni.objects.first()
# rocnik_object = s.Rocnik.objects.filter(rocnik=rocnik) # rocnik_object = Rocnik.objects.filter(rocnik=rocnik)
# tematko_object = s.Tema.objects.filter(rocnik=rocnik_object[0], kod=tematko) # tematko_object = Tema.objects.filter(rocnik=rocnik_object[0], kod=tematko)
# seznam = vytahniZLesaSeznam(tematko_object[0], nastaveni.aktualni_rocnik().rocniknode) # seznam = vytahniZLesaSeznam(tematko_object[0], nastaveni.aktualni_rocnik().rocniknode)
# for node, depth in seznam: # for node, depth in seznam:
# if node.isinstance(node, s.KonferaNode): # if node.isinstance(node, m.KonferaNode):
# raise Exception("Not implemented yet") # raise Exception("Not implemented yet")
# if node.isinstance(node, s.PohadkaNode): # Mohu ignorovat, má pod sebou # if node.isinstance(node, m.PohadkaNode): # Mohu ignorovat, má pod sebou
# pass # pass
# #
# return render(request, 'tvorba/tematka/toaletak.html', {}) # return render(request, 'tvorba/tematka/toaletak.html', {})
@ -155,8 +154,8 @@ def ZadaniTemataView(request):
# #
#def TemataRozcestnikView(request): #def TemataRozcestnikView(request):
# print("=============================================") # print("=============================================")
# nastaveni = s.Nastaveni.objects.first() # nastaveni = Nastaveni.objects.first()
# tematka_objects = s.Tema.objects.filter(rocnik=nastaveni.aktualni_rocnik()) # tematka_objects = Tema.objects.filter(rocnik=nastaveni.aktualni_rocnik())
# tematka = [] #List tematka obsahuje pro kazde tematko object a list vsech TemaVCisleNodu - implementované pomocí slovníku # tematka = [] #List tematka obsahuje pro kazde tematko object a list vsech TemaVCisleNodu - implementované pomocí slovníku
# for tematko_object in tematka_objects: # for tematko_object in tematka_objects:
# print("AKTUALNI TEMATKO") # print("AKTUALNI TEMATKO")
@ -278,7 +277,7 @@ def resiteleRocnikuCsvExportView(request, rocnik):
assert request.method in ('GET', 'HEAD') assert request.method in ('GET', 'HEAD')
return dataResiteluCsvResponse( return dataResiteluCsvResponse(
utils.resi_v_rocniku( utils.resi_v_rocniku(
get_object_or_404(m.Rocnik, rocnik=rocnik) get_object_or_404(Rocnik, rocnik=rocnik)
) )
) )
@ -291,10 +290,10 @@ def resiteleRocnikuCsvExportView(request, rocnik):
# def get_template_names(self, **kwargs): # def get_template_names(self, **kwargs):
# # FIXME: Switch podle typu není hezký, ale nechtělo se mi to přepisovat celé. Správně by se tohle mělo řešit polymorfismem. # # FIXME: Switch podle typu není hezký, ale nechtělo se mi to přepisovat celé. Správně by se tohle mělo řešit polymorfismem.
# spravne_templaty = { # spravne_templaty = {
# s.Uloha: "uloha", # Uloha: "uloha",
# s.Tema: "tema", # Tema: "tema",
# s.Konfera: "konfera", # Konfera: "konfera",
# s.Clanek: "clanek", # Clanek: "clanek",
# } # }
# context = super().get_context_data(**kwargs) # context = super().get_context_data(**kwargs)
# return ['tvorba/archiv/problem_' + spravne_templaty[context['object'].__class__] + '.html'] # return ['tvorba/archiv/problem_' + spravne_templaty[context['object'].__class__] + '.html']
@ -340,10 +339,10 @@ class CisloView(generic.DetailView):
deadliny_s_vysledkovkami = [] deadliny_s_vysledkovkami = []
nadpisy = { nadpisy = {
m.Deadline.TYP_CISLA: "Výsledkovka", Deadline.TYP_CISLA: "Výsledkovka",
m.Deadline.TYP_PRVNI: "Výsledkovka do prvního deadlinu", Deadline.TYP_PRVNI: "Výsledkovka do prvního deadlinu",
m.Deadline.TYP_PRVNI_A_SOUS: "Výsledkovka do prvního deadlinu a deadlinu pro účast na soustředění", Deadline.TYP_PRVNI_A_SOUS: "Výsledkovka do prvního deadlinu a deadlinu pro účast na soustředění",
m.Deadline.TYP_SOUS: "Výsledkovka do deadlinu pro účast na soustředění", Deadline.TYP_SOUS: "Výsledkovka do deadlinu pro účast na soustředění",
} }
for deadline in deadliny: for deadline in deadliny:
@ -580,5 +579,5 @@ class AktualniRocnikRedirectView(RedirectView):
pattern_name = 'seminar_rocnik' pattern_name = 'seminar_rocnik'
def get_redirect_url(self, *args, **kwargs): def get_redirect_url(self, *args, **kwargs):
aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik.rocnik aktualni_rocnik = Nastaveni.get_solo().aktualni_rocnik.rocnik
return super().get_redirect_url(rocnik=aktualni_rocnik, *args, **kwargs) return super().get_redirect_url(rocnik=aktualni_rocnik, *args, **kwargs)

14
tvorba/views/docasne.py

@ -5,13 +5,13 @@ from django.db import transaction
from django.forms import Form, CharField, IntegerField from django.forms import Form, CharField, IntegerField
from django.views.generic import FormView from django.views.generic import FormView
import seminar.models as m from tvorba.models import Cislo, Problem, Uloha, Tema
from django.shortcuts import render, get_object_or_404 from django.shortcuts import render, get_object_or_404
def problemView(request, pk): def problemView(request, pk):
# Pokud problém neexistuje, hodíme obyčejnou 404 # Pokud problém neexistuje, hodíme obyčejnou 404
# Taktéž v případě, že takový problém nemá být vidět # Taktéž v případě, že takový problém nemá být vidět
problem = get_object_or_404(m.Problem, id=pk, stav__in=[m.Problem.STAV_ZADANY, m.Problem.STAV_VYRESENY]) problem = get_object_or_404(Problem, id=pk, stav__in=[Problem.STAV_ZADANY, Problem.STAV_VYRESENY])
# Problém existuje, neumíme ho zobrazit, renderujeme nějakou haluz # Problém existuje, neumíme ho zobrazit, renderujeme nějakou haluz
template = 'universal.html' template = 'universal.html'
ctx = { ctx = {
@ -32,7 +32,7 @@ class HromadnePridaniForm(Form):
def clean_tema(self): def clean_tema(self):
""" Kontrola, že `tema` je název právě jednoho tématu """ """ Kontrola, že `tema` je název právě jednoho tématu """
if m.Tema.objects.filter( if Tema.objects.filter(
nazev__exact=self.cleaned_data['tema'], nazev__exact=self.cleaned_data['tema'],
nadproblem=None).count() != 1: nadproblem=None).count() != 1:
raise ValidationError("Špatný nebo nepřesně zadaný název témátka") raise ValidationError("Špatný nebo nepřesně zadaný název témátka")
@ -67,20 +67,20 @@ class HromadnePridaniView(FormView):
dil = cd["dil"] dil = cd["dil"]
body = list(map(int, cd["body"].split(","))) body = list(map(int, cd["body"].split(",")))
t = m.Problem.objects.get(nazev__exact=tema, nadproblem=None) t = Problem.objects.get(nazev__exact=tema, nadproblem=None)
with transaction.atomic(): with transaction.atomic():
pfx = f"{t.nazev}, díl {dil}, " pfx = f"{t.nazev}, díl {dil}, "
for k, b in enumerate(body, 1): for k, b in enumerate(body, 1):
u = m.Uloha.objects.create( u = Uloha.objects.create(
nadproblem=t, nadproblem=t,
nazev=pfx + f"{'úloha' if b > 0 else 'problém'} {k}", nazev=pfx + f"{'úloha' if b > 0 else 'problém'} {k}",
autor=t.autor, autor=t.autor,
garant=t.garant, garant=t.garant,
max_body=b, max_body=b,
cislo_zadani=m.Cislo.get(t.rocnik.rocnik, dil), cislo_zadani=Cislo.get(t.rocnik.rocnik, dil),
kod=k, kod=k,
stav=m.Problem.STAV_ZADANY, stav=Problem.STAV_ZADANY,
) )
u.opravovatele.set(t.opravovatele.all()) u.opravovatele.set(t.opravovatele.all())
return super().form_valid(form) return super().form_valid(form)

1
various/autentizace/views.py

@ -55,5 +55,4 @@ class PasswordResetCompleteView(auth_views.PasswordResetCompleteView):
class PasswordChangeView(auth_views.PasswordChangeView): class PasswordChangeView(auth_views.PasswordChangeView):
# template_name = 'seminar/password_change.html'
success_url = reverse_lazy('titulni_strana') success_url = reverse_lazy('titulni_strana')

2
various/management/commands/generate_thumbnails.py

@ -1,6 +1,6 @@
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from seminar.models import Cislo from tvorba.models import Cislo
from subprocess import CalledProcessError from subprocess import CalledProcessError
import logging import logging

4
various/management/commands/pregeneruj_zmrazene_vysledkovky.py

@ -1,11 +1,11 @@
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
import seminar.models as m from tvorba.models import Deadline
class Command(BaseCommand): class Command(BaseCommand):
help = "Všem deadlinům se zveřejněnou výsledkovkou vygeneruj výsledkovku" help = "Všem deadlinům se zveřejněnou výsledkovkou vygeneruj výsledkovku"
def handle(self, *args, **options): def handle(self, *args, **options):
for deadline in m.Deadline.objects.filter(verejna_vysledkovka=True): for deadline in Deadline.objects.filter(verejna_vysledkovka=True):
deadline.vygeneruj_vysledkovku() deadline.vygeneruj_vysledkovku()

4
various/management/commands/testdata.py

@ -4,7 +4,9 @@ from django.core.management.base import BaseCommand
from django.core.management import call_command from django.core.management import call_command
from django.conf import settings from django.conf import settings
from seminar.models import Skola, Resitel, Rocnik, Cislo, Problem, Reseni, PrilohaReseni, Nastaveni from odevzdavatko.models import Reseni
from personalni.models import Skola, Resitel
from tvorba.models import Rocnik, Cislo, Problem
from various.testutils import create_test_data from various.testutils import create_test_data
import django.contrib.auth import django.contrib.auth
User = django.contrib.auth.get_user_model() User = django.contrib.auth.get_user_model()

4
various/testutils.py

@ -7,7 +7,9 @@ from django.contrib.flatpages.models import FlatPage
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.db import transaction from django.db import transaction
from seminar.models import Rocnik, Cislo, Nastaveni, Osoba, Organizator from personalni.models import Osoba, Organizator
from tvorba.models import Rocnik, Cislo
from various.models import Nastaveni
from korektury.testutils import create_test_pdf from korektury.testutils import create_test_pdf
from novinky.testutils import gen_novinky from novinky.testutils import gen_novinky

27
various/views/final.py

@ -14,8 +14,9 @@ from django.views import generic
import novinky.views import novinky.views
import treenode.treelib as t import treenode.treelib as t
import tvorba.views import tvorba.views
from personalni.models import Resitel import seminar.models as m
from seminar import models as m from personalni.models import Resitel, Osoba
from tvorba.models import Clanek, Deadline
from ..models import Nastaveni from ..models import Nastaveni
@ -30,7 +31,7 @@ class TitulniStranaView(generic.ListView):
context = super(TitulniStranaView, self).get_context_data(**kwargs) context = super(TitulniStranaView, self).get_context_data(**kwargs)
nastaveni = get_object_or_404(Nastaveni) nastaveni = get_object_or_404(Nastaveni)
deadline = m.Deadline.objects.filter( deadline = Deadline.objects.filter(
deadline__gte=timezone.now()).order_by("deadline").first() deadline__gte=timezone.now()).order_by("deadline").first()
context['nejblizsi_deadline'] = deadline context['nejblizsi_deadline'] = deadline
@ -93,31 +94,31 @@ def seznam_problemu():
# Duplicita jmen # Duplicita jmen
jmena = {} jmena = {}
for r in m.Resitel.objects.all(): for r in Resitel.objects.all():
j = r.osoba.plne_jmeno() j = r.osoba.plne_jmeno()
if j not in jmena: if j not in jmena:
jmena[j] = [] jmena[j] = []
jmena[j].append(r) jmena[j].append(r)
for j in jmena: for j in jmena:
if len(jmena[j]) > 1: if len(jmena[j]) > 1:
prb(m.Resitel, 'Duplicitní jméno "%s"' % (j,), jmena[j]) prb(Resitel, 'Duplicitní jméno "%s"' % (j,), jmena[j])
# Data maturity a narození # Data maturity a narození
for r in m.Resitel.objects.all(): for r in Resitel.objects.all():
if not r.rok_maturity: if not r.rok_maturity:
prb(m.Resitel, 'Neznámý rok maturity', [r]) prb(Resitel, 'Neznámý rok maturity', [r])
if r.rok_maturity and (r.rok_maturity < 1990 or r.rok_maturity > datetime.date.today().year + 10): if r.rok_maturity and (r.rok_maturity < 1990 or r.rok_maturity > datetime.date.today().year + 10):
prb(m.Resitel, 'Podezřelé datum maturity', [r]) prb(Resitel, 'Podezřelé datum maturity', [r])
if r.osoba.datum_narozeni and ( if r.osoba.datum_narozeni and (
r.osoba.datum_narozeni.year < 1970 or r.osoba.datum_narozeni.year > datetime.date.today().year - 12): r.osoba.datum_narozeni.year < 1970 or r.osoba.datum_narozeni.year > datetime.date.today().year - 12):
prb(m.Resitel, 'Podezřelé datum narození', [r]) prb(Resitel, 'Podezřelé datum narození', [r])
# if not r.email: # if not r.email:
# prb(Resitel, u'Neznámý email', [r]) # prb(Resitel, u'Neznámý email', [r])
## Kontroly konzistence databáze a TreeNodů ## Kontroly konzistence databáze a TreeNodů
# Články # Články
for clanek in m.Clanek.objects.all(): for clanek in Clanek.objects.all():
# získáme řešení svázané se článkem a z něj node ve stromě # získáme řešení svázané se článkem a z něj node ve stromě
reseni = clanek.reseni_set reseni = clanek.reseni_set
if (reseni.count() != 1): if (reseni.count() != 1):
@ -136,7 +137,7 @@ def seznam_problemu():
# .cislonode je opačná vazba k treenode_ptr, abychom z TreeNode dostali # .cislonode je opačná vazba k treenode_ptr, abychom z TreeNode dostali
# CisloNode # CisloNode
if clanek.cislo != node.cislonode.cislo: if clanek.cislo != node.cislonode.cislo:
prb(m.Clanek, "Číslo otištění uložené u článku nesedí s " prb(Clanek, "Číslo otištění uložené u článku nesedí s "
"číslem otištění podle struktury treenodů.", [clanek]) "číslem otištění podle struktury treenodů.", [clanek])
break break
node = t.get_parent(node) node = t.get_parent(node)
@ -146,8 +147,8 @@ def seznam_problemu():
def StavDatabazeView(request): def StavDatabazeView(request):
# nastaveni = Nastaveni.objects.get() # nastaveni = Nastaveni.objects.get()
problemy = seznam_problemu() problemy = seznam_problemu()
muzi = Resitel.objects.filter(osoba__osloveni=m.Osoba.OSLOVENI_MUZSKE) muzi = Resitel.objects.filter(osoba__osloveni=Osoba.OSLOVENI_MUZSKE)
zeny = Resitel.objects.filter(osoba__osloveni=m.Osoba.OSLOVENI_ZENSKE) zeny = Resitel.objects.filter(osoba__osloveni=Osoba.OSLOVENI_ZENSKE)
return render(request, 'various/stav_databaze.html', { return render(request, 'various/stav_databaze.html', {
# 'nastaveni': nastaveni, # 'nastaveni': nastaveni,
'problemy': problemy, 'problemy': problemy,

77
vysledkovky/utils.py

@ -2,7 +2,10 @@ import abc
from functools import cached_property from functools import cached_property
from typing import Union, Iterable # TODO: s pythonem 3.10 přepsat na '|' from typing import Union, Iterable # TODO: s pythonem 3.10 přepsat na '|'
import seminar.models as m from odevzdavatko.models import Hodnoceni
from personalni.models import Resitel
from soustredeni.models import Konfera
from tvorba.models import Cislo, Rocnik, Deadline, Problem, Clanek
from django.db.models import Q, Sum from django.db.models import Q, Sum
from tvorba.utils import resi_v_rocniku from tvorba.utils import resi_v_rocniku
@ -18,11 +21,11 @@ class FixedIterator:
def body_resitelu( def body_resitelu(
za: Union[m.Cislo, m.Rocnik, None] = None, za: Union[Cislo, Rocnik, None] = None,
do: m.Deadline = None, do: Deadline = None,
od: m.Deadline = None, od: Deadline = None,
jen_verejne: bool = True, jen_verejne: bool = True,
resitele: Iterable[m.Resitel] = None, resitele: Iterable[Resitel] = None,
null=0 # Výchozí hodnota, pokud pro daného řešitele nejsou body null=0 # Výchozí hodnota, pokud pro daného řešitele nejsou body
) -> dict[int, int]: ) -> dict[int, int]:
filtr = Q() filtr = Q()
@ -31,9 +34,9 @@ def body_resitelu(
filtr &= Q(reseni__hodnoceni__deadline_body__verejna_vysledkovka=True) filtr &= Q(reseni__hodnoceni__deadline_body__verejna_vysledkovka=True)
# Zjistíme, typ objektu v parametru "za" # Zjistíme, typ objektu v parametru "za"
if isinstance(za, m.Rocnik): if isinstance(za, Rocnik):
filtr &= Q(reseni__hodnoceni__deadline_body__cislo__rocnik=za) filtr &= Q(reseni__hodnoceni__deadline_body__cislo__rocnik=za)
elif isinstance(za, m.Cislo): elif isinstance(za, Cislo):
filtr &= Q(reseni__hodnoceni__deadline_body__cislo=za) filtr &= Q(reseni__hodnoceni__deadline_body__cislo=za)
if do: if do:
@ -42,7 +45,7 @@ def body_resitelu(
if od: if od:
filtr &= Q(reseni__hodnoceni__deadline_body__deadline__gte=od.deadline) filtr &= Q(reseni__hodnoceni__deadline_body__deadline__gte=od.deadline)
resiteleQuery = m.Resitel.objects.all() resiteleQuery = Resitel.objects.all()
if resitele is not None: if resitele is not None:
resitele_id = [r.id for r in resitele] resitele_id = [r.id for r in resitele]
@ -63,12 +66,12 @@ def body_resitelu(
class Vysledkovka(abc.ABC): class Vysledkovka(abc.ABC):
jen_verejne: bool jen_verejne: bool
rocnik: m.Rocnik rocnik: Rocnik
do_deadlinu: m.Deadline do_deadlinu: Deadline
@property @property
@abc.abstractmethod @abc.abstractmethod
def aktivni_resitele(self) -> list[m.Resitel]: def aktivni_resitele(self) -> list[Resitel]:
... ...
@cached_property @cached_property
@ -143,20 +146,20 @@ class Vysledkovka(abc.ABC):
class VysledkovkaRocniku(Vysledkovka): class VysledkovkaRocniku(Vysledkovka):
def __init__(self, rocnik: m.Rocnik, jen_verejne: bool = True): def __init__(self, rocnik: Rocnik, jen_verejne: bool = True):
self.rocnik = rocnik self.rocnik = rocnik
self.jen_verejne = jen_verejne self.jen_verejne = jen_verejne
deadliny = m.Deadline.objects.filter(cislo__rocnik=rocnik) deadliny = Deadline.objects.filter(cislo__rocnik=rocnik)
if jen_verejne: if jen_verejne:
deadliny = deadliny.filter(verejna_vysledkovka=True) deadliny = deadliny.filter(verejna_vysledkovka=True)
self.do_deadlinu = deadliny.order_by("deadline").last() self.do_deadlinu = deadliny.order_by("deadline").last()
@cached_property @cached_property
def aktivni_resitele(self) -> list[m.Resitel]: def aktivni_resitele(self) -> list[Resitel]:
return list(resi_v_rocniku(self.rocnik)) return list(resi_v_rocniku(self.rocnik))
@cached_property @cached_property
def cisla_rocniku(self) -> list[m.Cislo]: def cisla_rocniku(self) -> list[Cislo]:
""" Vrátí všechna čísla daného ročníku. """ """ Vrátí všechna čísla daného ročníku. """
if self.jen_verejne: if self.jen_verejne:
return self.rocnik.verejne_vysledkovky_cisla() return self.rocnik.verejne_vysledkovky_cisla()
@ -164,7 +167,7 @@ class VysledkovkaRocniku(Vysledkovka):
return self.rocnik.cisla.all().order_by('poradi') return self.rocnik.cisla.all().order_by('poradi')
@cached_property @cached_property
def body_za_cisla_slovnik(self) -> dict[int, dict[int, int]]: # Výstup: m.Cislo.id → ( m.Resitel.id → body ) def body_za_cisla_slovnik(self) -> dict[int, dict[int, int]]: # Výstup: Cislo.id → ( Resitel.id → body )
# TODO: Body jsou decimal! # TODO: Body jsou decimal!
body_cisla_slovnik = dict() body_cisla_slovnik = dict()
for cislo in self.cisla_rocniku: for cislo in self.cisla_rocniku:
@ -197,7 +200,7 @@ class VysledkovkaRocniku(Vysledkovka):
radky_vysledkovky = [] radky_vysledkovky = []
setrizeni_resitele_dict = dict() setrizeni_resitele_dict = dict()
for r in m.Resitel.objects.filter( for r in Resitel.objects.filter(
id__in=self.setrizeni_resitele_id id__in=self.setrizeni_resitele_id
).select_related('osoba'): ).select_related('osoba'):
setrizeni_resitele_dict[r.id] = r setrizeni_resitele_dict[r.id] = r
@ -227,31 +230,31 @@ class VysledkovkaRocniku(Vysledkovka):
class VysledkovkaCisla(Vysledkovka): class VysledkovkaCisla(Vysledkovka):
def __init__( def __init__(
self, self,
cislo: m.Cislo, cislo: Cislo,
jen_verejne: bool = True, jen_verejne: bool = True,
do_deadlinu: m.Deadline = None do_deadlinu: Deadline = None
): ):
self.cislo = cislo self.cislo = cislo
self.rocnik = cislo.rocnik self.rocnik = cislo.rocnik
self.jen_verejne = jen_verejne self.jen_verejne = jen_verejne
if do_deadlinu is None: if do_deadlinu is None:
do_deadlinu = m.Deadline.objects.filter(cislo=cislo).last() do_deadlinu = Deadline.objects.filter(cislo=cislo).last()
self.do_deadlinu = do_deadlinu self.do_deadlinu = do_deadlinu
@cached_property @cached_property
def aktivni_resitele(self) -> list[m.Resitel]: def aktivni_resitele(self) -> list[Resitel]:
# TODO možná chytřeji vybírat aktivní řešitele # TODO možná chytřeji vybírat aktivní řešitele
return list(resi_v_rocniku(self.rocnik)) return list(resi_v_rocniku(self.rocnik))
@cached_property @cached_property
def problemy(self) -> list[m.Problem]: def problemy(self) -> list[Problem]:
""" Vrátí seznam všech problémů s body v daném čísle. """ """ Vrátí seznam všech problémů s body v daném čísle. """
return m.Problem.objects.filter( return Problem.objects.filter(
hodnoceni__in=m.Hodnoceni.objects.filter(deadline_body__cislo=self.cislo) hodnoceni__in=Hodnoceni.objects.filter(deadline_body__cislo=self.cislo)
).distinct().non_polymorphic().select_related('nadproblem').select_related('nadproblem__nadproblem') ).distinct().non_polymorphic().select_related('nadproblem').select_related('nadproblem__nadproblem')
@cached_property @cached_property
def hlavni_problemy(self) -> list[m.Problem]: def hlavni_problemy(self) -> list[Problem]:
""" Vrátí seznam všech problémů, které již nemají nadproblém. """ """ Vrátí seznam všech problémů, které již nemají nadproblém. """
# hlavní problémy čísla # hlavní problémy čísla
# (mají vlastní sloupeček ve výsledkovce, nemají nadproblém) # (mají vlastní sloupeček ve výsledkovce, nemají nadproblém)
@ -269,7 +272,7 @@ class VysledkovkaCisla(Vysledkovka):
# Není cached, protože si myslím, že queryset lze použít ve for jen jednou. # Není cached, protože si myslím, že queryset lze použít ve for jen jednou.
@property @property
def hodnoceni_do_cisla(self): def hodnoceni_do_cisla(self):
hodnoceni = m.Hodnoceni.objects.prefetch_related('reseni__resitele').select_related('problem', 'reseni') hodnoceni = Hodnoceni.objects.prefetch_related('reseni__resitele').select_related('problem', 'reseni')
if self.jen_verejne: if self.jen_verejne:
hodnoceni = hodnoceni.filter(deadline_body__verejna_vysledkovka=True) hodnoceni = hodnoceni.filter(deadline_body__verejna_vysledkovka=True)
return hodnoceni.filter( return hodnoceni.filter(
@ -347,7 +350,7 @@ class VysledkovkaCisla(Vysledkovka):
return self.sectene_body[2] return self.sectene_body[2]
@cached_property @cached_property
def temata_a_spol(self) -> list[m.Problem]: def temata_a_spol(self) -> list[Problem]:
if self.rocnik.rocnik < ROCNIK_ZRUSENI_TEMAT: if self.rocnik.rocnik < ROCNIK_ZRUSENI_TEMAT:
return self.hlavni_problemy return self.hlavni_problemy
else: else:
@ -358,7 +361,7 @@ class VysledkovkaCisla(Vysledkovka):
return len(self.hlavni_problemy) - len(self.temata_a_spol) > 0 return len(self.hlavni_problemy) - len(self.temata_a_spol) > 0
@cached_property @cached_property
def podproblemy(self) -> dict[int, list[m.Problem]]: def podproblemy(self) -> dict[int, list[Problem]]:
podproblemy = {hp.id: [] for hp in self.temata_a_spol} podproblemy = {hp.id: [] for hp in self.temata_a_spol}
temata_a_spol = set(self.temata_a_spol) temata_a_spol = set(self.temata_a_spol)
podproblemy[-1] = [] podproblemy[-1] = []
@ -375,7 +378,7 @@ class VysledkovkaCisla(Vysledkovka):
return podproblemy return podproblemy
@cached_property @cached_property
def podproblemy_seznam(self) -> list[list[m.Problem]]: def podproblemy_seznam(self) -> list[list[Problem]]:
return [self.podproblemy[it.id] for it in self.temata_a_spol] + [self.podproblemy[-1]] return [self.podproblemy[it.id] for it in self.temata_a_spol] + [self.podproblemy[-1]]
@cached_property @cached_property
@ -405,7 +408,7 @@ class VysledkovkaCisla(Vysledkovka):
radky_vysledkovky = [] radky_vysledkovky = []
setrizeni_resitele_slovnik = {} setrizeni_resitele_slovnik = {}
setrizeni_resitele = m.Resitel.objects.filter(id__in=self.setrizeni_resitele_id).select_related('osoba') setrizeni_resitele = Resitel.objects.filter(id__in=self.setrizeni_resitele_id).select_related('osoba')
for r in setrizeni_resitele: for r in setrizeni_resitele:
setrizeni_resitele_slovnik[r.id] = r setrizeni_resitele_slovnik[r.id] = r
@ -456,29 +459,29 @@ class VysledkovkaCisla(Vysledkovka):
@staticmethod @staticmethod
def ne_clanek_ne_konfera(problem): def ne_clanek_ne_konfera(problem):
inst = problem.get_real_instance() inst = problem.get_real_instance()
return not (isinstance(inst, m.Clanek) or isinstance(inst, m.Konfera)) return not (isinstance(inst, Clanek) or isinstance(inst, Konfera))
class VysledkovkaDoTeXu(VysledkovkaCisla): class VysledkovkaDoTeXu(VysledkovkaCisla):
def __init__( def __init__(
self, self,
nejake_cislo: m.Cislo, nejake_cislo: Cislo,
od_vyjma: m.Deadline, od_vyjma: Deadline,
do_vcetne: m.Deadline do_vcetne: Deadline
): ):
super().__init__(nejake_cislo, False, do_vcetne) super().__init__(nejake_cislo, False, do_vcetne)
self.od_deadlinu = od_vyjma self.od_deadlinu = od_vyjma
@cached_property @cached_property
def problemy(self) -> list[m.Problem]: def problemy(self) -> list[Problem]:
return m.Problem.objects.filter(hodnoceni__in=m.Hodnoceni.objects.filter( return Problem.objects.filter(hodnoceni__in=Hodnoceni.objects.filter(
deadline_body__deadline__gt=self.od_deadlinu.deadline, deadline_body__deadline__gt=self.od_deadlinu.deadline,
deadline_body__deadline__lte=self.do_deadlinu.deadline, deadline_body__deadline__lte=self.do_deadlinu.deadline,
)).distinct().non_polymorphic().select_related('nadproblem').select_related('nadproblem__nadproblem') )).distinct().non_polymorphic().select_related('nadproblem').select_related('nadproblem__nadproblem')
@property @property
def hodnoceni_do_cisla(self): def hodnoceni_do_cisla(self):
hodnoceni = m.Hodnoceni.objects.prefetch_related( hodnoceni = Hodnoceni.objects.prefetch_related(
'problem', 'reseni', 'reseni__resitele') 'problem', 'reseni', 'reseni__resitele')
if self.jen_verejne: if self.jen_verejne:
hodnoceni = hodnoceni.filter(deadline_body__verejna_vysledkovka=True) hodnoceni = hodnoceni.filter(deadline_body__verejna_vysledkovka=True)

Loading…
Cancel
Save