{% include "seminar/profil/prihlaska_field.html" with field=form.zasilat %}
+ {% include "seminar/profil/prihlaska_field.html" with field=form.zasilat_cislo_emailem %}
{% include "seminar/profil/prihlaska_field.html" with field=form.zasilat %}
+ {% include "seminar/profil/prihlaska_field.html" with field=form.zasilat_cislo_emailem %}
diff --git a/seminar/templatetags/utils.py b/seminar/templatetags/utils.py
new file mode 100644
index 00000000..1cb92d6a
--- /dev/null
+++ b/seminar/templatetags/utils.py
@@ -0,0 +1,27 @@
+from django import template
+from datetime import datetime, timedelta
+from pytz import timezone
+from mamweb.settings import TIME_ZONE
+import logging
+register = template.Library()
+
+logger = logging.getLogger(__name__)
+
+@register.filter(name='kratke_datum', expects_localtime=True)
+def kratke_datum(dt):
+ # None dává None, ne-datum dává False, aby se daly použít filtry typu "default".
+ if dt is None:
+ return None
+ if not isinstance(dt, datetime):
+ logger.warning(f"Špatné volání filtru {__name__}: {dt}")
+ return False
+ naive_now = datetime.now()
+ tz = timezone(TIME_ZONE)
+ now = tz.localize(naive_now)
+ delta = now - dt
+ if delta <= timedelta(days=1):
+ return dt.strftime("%k:%M")
+ if delta <= timedelta(days=365): # Timedelta neumí vyjádřit 1 rok
+ return dt.strftime("%d. %m.")
+ return dt.strftime("%d. %m. %Y")
+
diff --git a/seminar/testutils.py b/seminar/testutils.py
index 94fa78b6..913e6abb 100644
--- a/seminar/testutils.py
+++ b/seminar/testutils.py
@@ -200,6 +200,8 @@ def gen_organizatori(rnd, osoby, last_rocnik):
os.user = user
os.save()
os.user.user_permissions.add(org_perm)
+ os.user.is_staff = True
+ os.user.save()
organizatori.append(Organizator.objects.create(osoba=os,
organizuje_od=od, organizuje_do=do, strucny_popis_organizatora = popis_orga))
return organizatori
diff --git a/seminar/urls.py b/seminar/urls.py
index 91e0ce15..7fa7b0a6 100644
--- a/seminar/urls.py
+++ b/seminar/urls.py
@@ -13,8 +13,8 @@ urlpatterns = [
path('co-je-MaM/organizatori/organizovali/', views.CojemamOrganizatoriStariView.as_view(), name='stari_organizatori'),
# Archiv
- path('archiv/rocniky/', views.ArchivView.as_view()),
- path('archiv/temata/', views.ArchivTemataView.as_view()),
+ path('archiv/rocniky/', views.ArchivView.as_view(), name="seninar_archiv_rocniky"),
+ path('archiv/temata/', views.ArchivTemataView.as_view(), name="seninar_archiv_temata"),
path('rocnik//', views.RocnikView.as_view(), name='seminar_rocnik'),
path('cislo/./', views.CisloView.as_view(), name='seminar_cislo'),
@@ -90,17 +90,17 @@ urlpatterns = [
name='seminar_rocnik_vysledkovka'
),
path(
- 'cislo/./vysledkovka.tex',
+ 'cislo/./vysledkovka.tex',
org_required(views.CisloVysledkovkaView.as_view()),
name='seminar_cislo_vysledkovka'
),
path(
- 'cislo/./obalky.pdf',
+ 'cislo/./obalky.pdf',
org_required(views.cisloObalkyView),
name='seminar_cislo_obalky'
),
path(
- 'cislo/./tituly.tex',
+ 'cislo/./tituly.tex',
org_required(views.TitulyView),
name='seminar_cislo_titul'
),
@@ -110,10 +110,14 @@ urlpatterns = [
name='stav_databaze'
),
path(
- 'cislo/./obalkovani',
+ 'cislo/./obalkovani',
org_required(views.ObalkovaniView.as_view()),
name='seminar_cislo_resitel_obalkovani'
),
+ path(
+ 'cislo/./odmeny/./',
+ org_required(views.OdmenyView.as_view()),
+ name="seminar_archiv_odmeny"),
path(
'soustredeni//obalky.pdf',
org_required(views.soustredeniObalkyView),
@@ -168,5 +172,10 @@ urlpatterns = [
# org_member_required(views.OrganizatorAutocomplete.as_view()),
# name='seminar_autocomplete_organizator')
+ path('temp/reseni/', org_required(views.TabulkaOdevzdanychReseniView.as_view()), name='odevzdavatko_tabulka'),
+ path('temp/reseni///', org_required(views.ReseniProblemuView.as_view()), name='odevzdavatko_reseni_resitele_k_problemu'),
+ path('temp/reseni/', org_required(views.DetailReseniView.as_view()), name='odevzdavatko_detail_reseni'),
+ path('temp/reseni/all', org_required(views.SeznamReseniView.as_view())),
+ path('temp/reseni/akt', org_required(views.SeznamAktualnichReseniView.as_view())),
]
diff --git a/seminar/utils.py b/seminar/utils.py
index ad9be95e..bcc67013 100644
--- a/seminar/utils.py
+++ b/seminar/utils.py
@@ -148,16 +148,12 @@ def resi_v_rocniku(rocnik, cislo=None):
if cislo is None:
# filtrujeme pouze podle ročníku
- letosni_reseni = m.Reseni.objects.filter(hodnoceni__cislo_body__rocnik=rocnik)
+ return m.Resitel.objects.filter(rok_maturity__gte=rocnik.druhy_rok(),
+ reseni__hodnoceni__cislo_body__rocnik=rocnik).distinct()
else: # filtrujeme podle ročníku i čísla
- letosni_reseni = m.Reseni.objects.filter(hodnoceni__cislo_body__rocnik=rocnik,
- hodnoceni__cislo_body__poradi__lte=cislo.poradi)
-
- # vygenerujeme queryset řešitelů, co letos něco poslali
- letosni_resitele = m.Resitel.objects.none()
- for reseni in letosni_reseni:
- letosni_resitele = letosni_resitele | reseni.resitele.filter(rok_maturity__gte=rocnik.druhy_rok())
- return letosni_resitele.distinct()
+ return m.Resitel.objects.filter(rok_maturity__gte=rocnik.druhy_rok(),
+ reseni__hodnoceni__cislo_body__rocnik=rocnik,
+ reseni__hodnoceni__cislo_body__poradi__lte=cislo.poradi).distinct()
def aktivniResitele(cislo, pouze_letosni=False):
diff --git a/seminar/views/__init__.py b/seminar/views/__init__.py
index 222d19bb..a9eb3ea9 100644
--- a/seminar/views/__init__.py
+++ b/seminar/views/__init__.py
@@ -1,3 +1,4 @@
from .views_all import *
from .autocomplete import *
from .views_rest import *
+from .odevzdavatko import *
diff --git a/seminar/views/autocomplete.py b/seminar/views/autocomplete.py
index 04ddca83..32c634bc 100644
--- a/seminar/views/autocomplete.py
+++ b/seminar/views/autocomplete.py
@@ -34,7 +34,9 @@ class OdevzdatelnyProblemAutocomplete(autocomplete.Select2QuerySetView):
rocnik = nastaveni.aktualni_rocnik
temata = m.Tema.objects.filter(rocnik=rocnik, stav=m.Problem.STAV_ZADANY)
ulohy = m.Uloha.objects.filter(cislo_deadline__rocnik = rocnik)
+ clanky = m.Clanek.objects.filter(cislo__rocnik = rocnik, stav=m.Problem.STAV_ZADANY) # FIXME: Je tohle to, co chceme?
ulohy.union(temata)
+ ulohy.union(clanky)
qs = ulohy
if self.q:
qs = qs.filter(
diff --git a/seminar/views/odevzdavatko.py b/seminar/views/odevzdavatko.py
new file mode 100644
index 00000000..de8ceec3
--- /dev/null
+++ b/seminar/views/odevzdavatko.py
@@ -0,0 +1,129 @@
+from django.views.generic import ListView, DetailView
+from django.views.generic.base import TemplateView
+
+from dataclasses import dataclass
+import datetime
+
+import seminar.models as m
+from seminar.utils import aktivniResitele, resi_v_rocniku
+
+# 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
+#
+# Taky se může hodit:
+# - Tabulka všech řešitelů x všech problémů?
+
+@dataclass
+class SouhrnReseni:
+ """Dataclass reprezentující data o odevzdaných řešeních pro zobrazení v tabulce."""
+ pocet_reseni : int
+ posledni_odevzdani : datetime.datetime
+ body : float
+
+
+class TabulkaOdevzdanychReseniView(ListView):
+ template_name = 'seminar/odevzdavatko/tabulka.html'
+ model = m.Hodnoceni
+
+ def get_queryset(self):
+ # FIXME: Tenhle blok nemůže být přímo ve třídě, protože před vyrobením databáze neexistuje Nastavení.
+ self.akt_rocnik = m.Nastaveni.get_solo().aktualni_rocnik # .get_solo() vrátí tu jedinou instanci, asi...
+ self.resitele = resi_v_rocniku(self.akt_rocnik)
+ # NOTE: Protože řešení odkazuje přímo na Problém a QuerySet na Hodnocení je nepolymorfní, musíme porovnávat taky s nepolymorfními Problémy.
+ self.zadane_problemy = m.Problem.objects.filter(stav=m.Problem.STAV_ZADANY).non_polymorphic()
+
+ qs = super().get_queryset()
+ qs = qs.filter(problem__in=self.zadane_problemy).select_related('reseni', 'problem').prefetch_related('reseni__resitele__osoba')
+ return qs
+
+ def get_context_data(self, *args, **kwargs):
+ # FIXME: Tenhle blok nemůže být přímo ve třídě, protože před vyrobením databáze neexistuje Nastavení.
+ self.akt_rocnik = m.Nastaveni.get_solo().aktualni_rocnik # .get_solo() vrátí tu jedinou instanci, asi...
+ self.resitele = resi_v_rocniku(self.akt_rocnik)
+ # NOTE: Protože řešení odkazuje přímo na Problém a QuerySet na Hodnocení je nepolymorfní, musíme porovnávat taky s nepolymorfními Problémy.
+ self.zadane_problemy = m.Problem.objects.filter(stav=m.Problem.STAV_ZADANY).non_polymorphic()
+
+ ctx = super().get_context_data(*args, **kwargs)
+ ctx['problemy'] = self.zadane_problemy
+ ctx['resitele'] = self.resitele
+ tabulka = dict()
+
+ def pridej_reseni(problem, resitel, body, cas):
+ if problem not in tabulka:
+ tabulka[problem] = dict()
+ if resitel not in tabulka[problem]:
+ tabulka[problem][resitel] = SouhrnReseni(pocet_reseni=1, posledni_odevzdani=cas, body=body)
+ else:
+ tabulka[problem][resitel].posledni_odevzdani = max(tabulka[problem][resitel].posledni_odevzdani, cas)
+ tabulka[problem][resitel].body = max(tabulka[problem][resitel].body, body,
+ key=lambda x: x if x is not None else -1 # None je malé číslo
+ # FIXME: Možná dává smysl i mít None jako velké číslo -- jakože "TODO: zadat body"
+ )
+ tabulka[problem][resitel].pocet_reseni += 1
+ # Pro jednoduchost template si ještě poznamenáme ID problému a řešitele
+ tabulka[problem][resitel].problem_id = problem.id
+ tabulka[problem][resitel].resitel_id = resitel.id
+
+ for hodnoceni in self.get_queryset():
+ for resitel in hodnoceni.reseni.resitele.all():
+ pridej_reseni(hodnoceni.problem, resitel, hodnoceni.body, hodnoceni.reseni.cas_doruceni)
+
+ hodnoty = []
+ for resitel in self.resitele:
+ resiteluv_radek = []
+ for problem in self.zadane_problemy:
+ if problem in tabulka and resitel in tabulka[problem]:
+ resiteluv_radek.append(tabulka[problem][resitel])
+ else:
+ resiteluv_radek.append(None)
+ hodnoty.append(resiteluv_radek)
+ ctx['radky'] = list(zip(self.resitele, hodnoty))
+
+ return ctx
+
+class ReseniProblemuView(ListView):
+ model = m.Reseni
+ template_name = 'seminar/odevzdavatko/seznam.html'
+
+ 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 = m.Resitel.objects.get(id=resitel_id)
+ problem = m.Problem.objects.get(id=problem_id)
+ qs = qs.filter(
+ problem__in=[problem],
+ resitele__in=[resitel],
+ )
+ return qs
+
+ # Kontext automaticky?
+
+class DetailReseniView(DetailView):
+ model = m.Reseni
+ template_name = 'seminar/odevzdavatko/detail.html'
+ # To je všechno? Najde se to podle pk...
+
+# Přehled všech řešení kvůli debugování
+
+class SeznamReseniView(ListView):
+ model = m.Reseni
+ template_name = 'seminar/odevzdavatko/seznam.html'
+
+class SeznamAktualnichReseniView(SeznamReseniView):
+ def get_queryset(self):
+ qs = super().get_queryset()
+ akt_rocnik = m.Nastaveni.get_solo().aktualni_rocnik # .get_solo() vrátí tu jedinou instanci, asi...
+ resitele = resi_v_rocniku(akt_rocnik)
+ qs = qs.filter(resitele__in=resitele) # FIXME: Najde řešení i ze starých ročníků, která odevzdal alespoň jeden aktuální řešitel
+ return qs
diff --git a/seminar/views/views_all.py b/seminar/views/views_all.py
index a550c40a..1aca98f6 100644
--- a/seminar/views/views_all.py
+++ b/seminar/views/views_all.py
@@ -1,4 +1,4 @@
-# coding:utf-8
+
from django.shortcuts import get_object_or_404, render, redirect
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, JsonResponse
@@ -17,6 +17,7 @@ from django.contrib.auth.models import User, Permission
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db import transaction
from django.core import serializers
+from django.core.exceptions import PermissionDenied
from django.forms.models import model_to_dict
import seminar.models as s
@@ -120,15 +121,57 @@ class TNLData(object):
self.appendable_siblings = tnltt.appendableChildren(self.parent)
else:
self.appendable_siblings = []
-
-
+ @classmethod
+ def public_above(cls, anode):
+ """ Returns output of verejne for closest Rocnik, Cislo or Problem above.
+ (All of them have method verejne.)"""
+ parent = anode # chceme začít už od konkrétního node včetně
+ while True:
+ rocnik = isinstance(parent, s.RocnikNode)
+ cislo = isinstance(parent, s.CisloNode)
+ uloha = (isinstance(parent, s.UlohaVzorakNode) or
+ isinstance(parent, s.UlohaZadaniNode))
+ tema = isinstance(parent, s.TemaVCisleNode)
+
+ if (rocnik or cislo or uloha or tema) or parent==None:
+ break
+ else:
+ parent = treelib.get_parent(parent)
+ if rocnik:
+ return parent.rocnik.verejne()
+ elif cislo:
+ return parent.cislo.verejne()
+ elif uloha:
+ return parent.uloha.verejne()
+ elif tema:
+ return parent.tema.verejne()
+ elif None:
+ print("Existuje TreeNode, který není pod číslem, ročníkem, úlohou"
+ "ani tématem. {}".format(anode))
+ return False
+
+ @classmethod
+ def all_public_children(cls, anode):
+ for ch in treelib.all_children(anode):
+ if TNLData.public_above(ch):
+ yield ch
+ else:
+ continue
@classmethod
- def from_treenode(cls,anode,parent=None,index=None):
- out = cls(anode,parent,index)
- for (idx,ch) in enumerate(treelib.all_children(anode)):
- # FIXME přidat filtrování na veřejnost
- outitem = cls.from_treenode(ch,out,idx)
+ def from_treenode(cls, anode, user, parent=None, index=None):
+ if TNLData.public_above(anode) or user.has_perm('auth.org'):
+ out = cls(anode,parent,index)
+ else:
+ raise PermissionDenied()
+
+ if user.has_perm('auth.org'):
+ enum_children = enumerate(treelib.all_children(anode))
+ else:
+ enum_children = enumerate(TNLData.all_public_children(anode))
+
+ for (idx,ch) in enum_children:
+ outitem = cls.from_treenode(ch, user, out, idx)
out.children.append(outitem)
out.add_edit_options()
return out
@@ -195,7 +238,7 @@ class TreeNodeView(generic.DetailView):
def get_context_data(self,**kwargs):
context = super().get_context_data(**kwargs)
- context['tnldata'] = TNLData.from_treenode(self.object)
+ context['tnldata'] = TNLData.from_treenode(self.object,self.request.user)
return context
class TreeNodeJSONView(generic.DetailView):
@@ -203,7 +246,7 @@ class TreeNodeJSONView(generic.DetailView):
def get(self,request,*args, **kwargs):
self.object = self.get_object()
- data = TNLData.from_treenode(self.object).to_json()
+ data = TNLData.from_treenode(self.object,self.request.user).to_json()
return JsonResponse(data)
@@ -332,6 +375,7 @@ class ProblemView(generic.DetailView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
+ user = self.request.user
# Teď potřebujeme doplnit tnldata do kontextu.
# Ošklivý type switch, hezčí by bylo udělat to polymorfni. FIXME.
if False:
@@ -339,11 +383,11 @@ class ProblemView(generic.DetailView):
pass
elif isinstance(self.object, s.Clanek) or isinstance(self.object, s.Konfera):
# Tyhle Problémy mají ŘešeníNode
- context['tnldata'] = TNLData.from_treenode(self.object.reseninode)
+ context['tnldata'] = TNLData.from_treenode(self.object.reseninode,user)
elif isinstance(self.object, s.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
- tnl_zadani = TNLData.from_treenode(self.object.ulohazadaninode)
- tnl_vzorak = TNLData.from_treenode(self.object.ulohavzoraknode)
+ tnl_zadani = TNLData.from_treenode(self.object.ulohazadaninode,user)
+ tnl_vzorak = TNLData.from_treenode(self.object.ulohavzoraknode,user)
context['tnldata'] = TNLData.from_tnldata_list([tnl_zadani, tnl_vzorak])
elif isinstance(self.object, s.Tema):
rocniknode = self.object.rocnik.rocniknode
@@ -385,16 +429,16 @@ class AktualniZadaniView(generic.TemplateView):
# )
#
def ZadaniTemataView(request):
- nastaveni = get_object_or_404(Nastaveni)
- verejne = nastaveni.aktualni_cislo.verejne()
- akt_rocnik = nastaveni.aktualni_cislo.rocnik
- temata = s.Tema.objects.filter(rocnik=akt_rocnik, stav='zadany')
- return render(request, 'seminar/tematka/rozcestnik.html',
- {
- 'tematka': temata,
- 'verejne': verejne,
- },
- )
+ nastaveni = get_object_or_404(Nastaveni)
+ verejne = nastaveni.aktualni_cislo.verejne()
+ akt_rocnik = nastaveni.aktualni_cislo.rocnik
+ temata = s.Tema.objects.filter(rocnik=akt_rocnik, stav='zadany')
+ return render(request, 'seminar/tematka/rozcestnik.html',
+ {
+ 'tematka': temata,
+ 'verejne': verejne,
+ },
+ )
# nastaveni = get_object_or_404(Nastaveni)
@@ -945,18 +989,30 @@ def secti_body_za_cislo(cislo, aktivni_resitele, hlavni_problemy=None):
if hlavni_problemy is None:
hlavni_problemy = hlavni_problemy_cisla(cislo)
+ def ne_clanek_ne_konfera(problem):
+ return not(isinstance(problem.get_real_instance(), m.Clanek) or isinstance(problem.get_real_instance(), m.Konfera))
+
+ temata_a_spol = list(filter(ne_clanek_ne_konfera, hlavni_problemy))
+
+ def cosi(problem):
+ return problem.id
+
hlavni_problemy_slovnik = {}
- for hp in hlavni_problemy:
+ for hp in temata_a_spol:
hlavni_problemy_slovnik[hp.id] = {}
+ hlavni_problemy_slovnik[-1] = {}
+
# zakládání prázdných záznamů pro řešitele
cislobody = {}
for ar in aktivni_resitele:
# řešitele převedeme na řetězec pomocí unikátního id
cislobody[ar.id] = ""
- for hp in hlavni_problemy:
+ for hp in temata_a_spol:
slovnik = hlavni_problemy_slovnik[hp.id]
slovnik[ar.id] = ""
+
+ hlavni_problemy_slovnik[-1][ar.id] = ""
# vezmeme všechna řešení s body do daného čísla
reseni_do_cisla = Reseni.objects.prefetch_related('problem', 'resitele',
@@ -969,7 +1025,10 @@ def secti_body_za_cislo(cislo, aktivni_resitele, hlavni_problemy=None):
# řešení může řešit více problémů
for prob in list(reseni.problem.all()):
nadproblem = hlavni_problem(prob)
- nadproblem_slovnik = hlavni_problemy_slovnik[nadproblem.id]
+ if ne_clanek_ne_konfera(nadproblem):
+ nadproblem_slovnik = hlavni_problemy_slovnik[nadproblem.id]
+ else:
+ nadproblem_slovnik = hlavni_problemy_slovnik[-1]
# a mít více hodnocení
for hodn in list(reseni.hodnoceni_set.all()):
@@ -1014,11 +1073,26 @@ def vysledkovka_cisla(cislo, context=None):
# vytvoříme jednotlivé sloupce výsledkovky
radky_vysledkovky = []
i = 0
+
+ def ne_clanek_ne_konfera(problem):
+ return not(isinstance(problem.get_real_instance(), m.Clanek) or isinstance(problem.get_real_instance(), m.Konfera))
+
+ temata_a_spol = list(filter(ne_clanek_ne_konfera, hlavni_problemy))
+
+ # def not_empty(value):
+ # return value != ''
+ #
+ # je_nejake_ostatni = any(filter(not_empty, hlavni_problemy_slovnik[-1].values())) > 0
+
+ je_nejake_ostatni = len(hlavni_problemy) - len(temata_a_spol) > 0
+
for ar_id in setrizeni_resitele_id:
# získáme seznam bodů za problémy pro daného řešitele
problemy = []
- for hp in hlavni_problemy:
+ for hp in temata_a_spol:
problemy.append(hlavni_problemy_slovnik[hp.id][ar_id])
+ if je_nejake_ostatni:
+ problemy.append(hlavni_problemy_slovnik[-1][ar_id])
# vytáhneme informace pro daného řešitele
radek = RadekVysledkovkyCisla(
poradi[i], # pořadí
@@ -1034,7 +1108,8 @@ def vysledkovka_cisla(cislo, context=None):
# vytahané informace předáváme do kontextu
context['cislo'] = cislo
context['radky_vysledkovky'] = radky_vysledkovky
- context['problemy'] = hlavni_problemy
+ context['problemy'] = temata_a_spol
+ context['ostatni'] = je_nejake_ostatni
#context['v_cisle_zadane'] = TODO
#context['resene_problemy'] = resene_problemy
return context
@@ -1063,6 +1138,7 @@ class CisloView(generic.DetailView):
context = super(CisloView, self).get_context_data(**kwargs)
cislo = context['cislo']
+ context['prevcislo'] = Cislo.objects.filter((Q(rocnik__lt=self.object.rocnik) | Q(poradi__lt=self.object.poradi))&Q(rocnik__lte=self.object.rocnik)).first()
# vrátíme context (aktuálně obsahuje jen věci ohledně výsledkovky
return vysledkovka_cisla(cislo, context)
@@ -1079,6 +1155,30 @@ class ArchivTemataView(generic.ListView):
ctx['rocniky'][rocnik] = list(temata)
return ctx
+class OdmenyView(generic.TemplateView):
+ template_name = 'seminar/archiv/odmeny.html'
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ fromcislo = Cislo.objects.get(rocnik=self.kwargs.get('frocnik'), poradi=self.kwargs.get('fcislo'))
+ tocislo = Cislo.objects.get(rocnik=self.kwargs.get('trocnik'), poradi=self.kwargs.get('tcislo'))
+ resitele = aktivniResitele(tocislo)
+ frombody = body_resitelu(resitele, fromcislo)
+ tobody = body_resitelu(resitele, tocislo)
+ outlist = []
+ for (aid, tbody) in tobody.items():
+ fbody = frombody.get(aid,0)
+ resitel = Resitel.objects.get(pk=aid)
+ ftitul = resitel.get_titul(fbody)
+ ttitul = resitel.get_titul(tbody)
+ if ftitul != ttitul:
+ outlist.append({'jmeno': resitel.osoba.plne_jmeno(), 'ftitul': ftitul, 'ttitul': ttitul})
+ context['zmeny'] = outlist
+ return context
+
+
+
+
### Generovani vysledkovky
class CisloVysledkovkaView(CisloView):
@@ -1332,6 +1432,12 @@ class ResitelView(LoginRequiredMixin,generic.DetailView):
### Formulare
+# pro přidání políčka do formuláře je potřeba
+# - mít v modelu tu položku, kterou chci upravovat
+# - přidat do views (prihlaskaView, resitelEditView)
+# - přidat do forms
+# - includovat do html
+
class AddSolutionView(LoginRequiredMixin, FormView):
template_name = 'seminar/org/vloz_reseni.html'
form_class = f.VlozReseniForm
@@ -1409,7 +1515,7 @@ def prihlaska_log_gdpr_safe(logger, gdpr_logger, msg, form_data):
from django.forms.models import model_to_dict
def resitelEditView(request):
err_logger = logging.getLogger('seminar.prihlaska.problem')
- ## Načtení objektu Osoba a Resitel, patrici k aktuálně přihlášenému uživately
+ ## Načtení objektů Osoba a Resitel patřících k aktuálně přihlášenému uživateli
u = request.user
osoba_edit = Osoba.objects.get(user=u)
resitel_edit = osoba_edit.resitel
@@ -1448,6 +1554,7 @@ def resitelEditView(request):
resitel_edit.skola = fcd['skola']
resitel_edit.rok_maturity = fcd['rok_maturity']
resitel_edit.zasilat = fcd['zasilat']
+ resitel_edit.zasilat_cislo_emailem = fcd['zasilat_cislo_emailem']
if fcd.get('skola'):
resitel_edit.skola = fcd['skola']
else:
@@ -1511,7 +1618,8 @@ def prihlaskaView(request):
r = Resitel(
rok_maturity = fcd['rok_maturity'],
- zasilat = fcd['zasilat']
+ zasilat = fcd['zasilat'],
+ zasilat_cislo_emailem = fcd['zasilat_cislo_emailem']
)
r.save()
diff --git a/seminar/views/views_rest.py b/seminar/views/views_rest.py
index aa3fca55..ef49b3cc 100644
--- a/seminar/views/views_rest.py
+++ b/seminar/views/views_rest.py
@@ -6,25 +6,27 @@ from seminar import treelib
DEFAULT_NODE_DEPTH = 2
+
class TextSerializer(serializers.ModelSerializer):
class Meta:
model = m.Text
fields = '__all__'
-
-
-class UlohaVzorakNodeSerializer(serializers.ModelSerializer):
+class ProblemSerializer(serializers.ModelSerializer):
class Meta:
- model = m.UlohaVzorakNode
+ model = m.Problem
fields = '__all__'
- depth = DEFAULT_NODE_DEPTH
-
-class UlohaZadaniNodeSerializer(serializers.ModelSerializer):
+
+class UlohaSerializer(serializers.ModelSerializer):
class Meta:
- model = m.UlohaZadaniNode
+ model = m.Uloha
fields = '__all__'
- depth = DEFAULT_NODE_DEPTH
+class ReseniSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = m.Reseni
+ fields = '__all__'
+
class RocnikNodeSerializer(serializers.ModelSerializer):
class Meta:
model = m.RocnikNode
@@ -138,12 +140,161 @@ class CastNodeCreateSerializer(serializers.ModelSerializer):
fields = ('nadpis','where','refnode')
depth = DEFAULT_NODE_DEPTH
+class UlohaZadaniNodeSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = m.UlohaZadaniNode
+ fields = '__all__'
+ depth = DEFAULT_NODE_DEPTH
+
+class UlohaZadaniNodeWriteSerializer(serializers.ModelSerializer):
+ uloha = UlohaSerializer()
+
+ def update(self,node,validated_data):
+ node.uloha.max_body = validated_data.get('uloha').get('max_body')
+ node.uloha.kod = validated_data.get('uloha').get('kod')
+ node.uloha.nazev = validated_data.get('uloha').get('nazev')
+ node.uloha.save()
+ return node
+
+ class Meta:
+ model = m.TextNode
+ fields = ('id','uloha')
+ depth = DEFAULT_NODE_DEPTH
+
+class UlohaZadaniNodeCreateSerializer(serializers.ModelSerializer):
+ uloha = UlohaSerializer()
+ refnode = serializers.IntegerField()
+ where = serializers.CharField()
+
+ def create(self,validated_data):
+ # text_zadani = validated_data.pop('text_zadani')
+ temp_uloha = validated_data.pop('uloha')
+ where = validated_data.pop('where')
+ refnode_id = validated_data.pop('refnode')
+ refnode = m.TreeNode.objects.get(pk=refnode_id)
+
+ # Z cesty ke koreni stromu zjistime, v jakem jsme tematu a v jakem cisle
+ cislo = None
+ tema = None
+ travelnode = refnode
+ while travelnode is not None:
+ if isinstance(travelnode, m.TemaVCisleNode):
+ tema = travelnode.tema
+ if isinstance(travelnode, m.CisloNode):
+ cislo = travelnode.cislo
+ travelnode = treelib.get_parent(travelnode)
+ # Vyrobime ulohu
+ uloha = m.Uloha.objects.create(cislo_zadani=cislo, nadproblem = tema, **temp_uloha)
+
+ # A vyrobime UlohaZadaniNode
+ if where == 'syn':
+ node = treelib.create_child(refnode,m.UlohaZadaniNode,uloha = uloha)
+ elif where == 'za':
+ node = treelib.create_node_after(refnode,m.UlohaZadaniNode,uloha = uloha)
+ elif where == 'pred':
+ node = treelib.create_node_before(refnode,m.UlohaZadaniNode,uloha = uloha)
+ node.where = None
+ node.refnode = None
+ node.max_body = None
+ node.kod = None
+ return node
+
+ class Meta:
+ model = m.UlohaZadaniNode
+ fields = ('uloha','where','refnode')
+ depth = DEFAULT_NODE_DEPTH
+
+class UlohaVzorakNodeSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = m.UlohaVzorakNode
+ fields = '__all__'
+ depth = DEFAULT_NODE_DEPTH
+
+class UlohaVzorakNodeWriteSerializer(serializers.ModelSerializer):
+ uloha = serializers.PrimaryKeyRelatedField(queryset=m.Uloha.objects.all(), many=False, read_only=False)
+
+ class Meta:
+ model = m.UlohaVzorakNode
+ fields = ('id','uloha')
+ depth = DEFAULT_NODE_DEPTH
+
+
+class UlohaVzorakNodeCreateSerializer(serializers.ModelSerializer):
+ uloha_id = serializers.IntegerField()
+ refnode = serializers.IntegerField()
+ where = serializers.CharField()
+
+ def create(self, validated_data):
+ uloha_id = validated_data.pop('uloha_id')
+ uloha = m.Uloha.objects.get(pk=uloha_id)
+ where = validated_data.pop('where')
+ refnode_id = validated_data.pop('refnode')
+ refnode = m.TreeNode.objects.get(pk=refnode_id)
+
+ if where == 'syn':
+ node = treelib.create_child(refnode,m.UlohaVzorakNode,uloha = uloha)
+ elif where == 'za':
+ node = treelib.create_node_after(refnode,m.UlohaVzorakNode,uloha = uloha)
+ elif where == 'pred':
+ node = treelib.create_node_before(refnode,m.UlohaVzorakNode,uloha = uloha)
+ node.refnode = None
+ node.where = None
+ node.uloha_id = None
+
+ return node
+
+ class Meta:
+ model = m.UlohaVzorakNode
+ fields = ('refnode', 'uloha_id', 'where')
+ depth = DEFAULT_NODE_DEPTH
+
+
+
+
class ReseniNodeSerializer(serializers.ModelSerializer):
class Meta:
model = m.ReseniNode
fields = '__all__'
depth = DEFAULT_NODE_DEPTH
+class ReseniNodeWriteSerializer(serializers.ModelSerializer):
+ reseni = serializers.PrimaryKeyRelatedField(queryset=m.Reseni.objects.all(), many=False, read_only=False)
+
+ class Meta:
+ model = m.ReseniNode
+ fields = ('id','reseni')
+ depth = DEFAULT_NODE_DEPTH
+
+class ReseniNodeCreateSerializer(serializers.ModelSerializer):
+ reseni_id = serializers.IntegerField()
+ refnode = serializers.IntegerField()
+ where = serializers.CharField()
+
+ def create(self,validated_data):
+ # text_zadani = validated_data.pop('text_zadani')
+ reseni_id = validated_data.pop('reseni_id')
+ reseni = m.Reseni.objects.get(pk=reseni_id)
+ where = validated_data.pop('where')
+ refnode_id = validated_data.pop('refnode')
+ refnode = m.TreeNode.objects.get(pk=refnode_id)
+
+ # A vyrobime UlohaZadaniNode
+ if where == 'syn':
+ node = treelib.create_child(refnode,m.ReseniNode,reseni = reseni)
+ elif where == 'za':
+ node = treelib.create_node_after(refnode,m.ReseniNode,reseni = reseni)
+ elif where == 'pred':
+ node = treelib.create_node_before(refnode,m.ReseniNode,reseni = reseni)
+ node.where = None
+ node.refnode = None
+ node.reseni_id = None
+ return node
+
+ class Meta:
+ model = m.ReseniNode
+ fields = ('reseni_id','where','refnode')
+ depth = DEFAULT_NODE_DEPTH
+
class TreeNodeSerializer(PolymorphicSerializer):
model_serializer_mapping = {
diff --git a/seminar/viewsets.py b/seminar/viewsets.py
index cc59f7d7..7e2ea63a 100644
--- a/seminar/viewsets.py
+++ b/seminar/viewsets.py
@@ -1,4 +1,7 @@
from rest_framework import viewsets,filters
+from rest_framework import status
+from rest_framework.response import Response
+from django.core.exceptions import PermissionDenied
from rest_framework.permissions import BasePermission, AllowAny
from . import models as m
from . import views
@@ -18,31 +21,6 @@ class PermissionMixin(object):
# návštěvník nemusí být zalogován, aby si prohlížel obsah
return [permission() for permission in permission_classes]
- def verejne_nad(self, node):
- """ Returns output of verejne for closest Rocnik, Cislo or Problem above.
- (All of them have method verejne.)"""
- parent = get_parent(node)
- while True:
- rocnik = isinstance(parent, RocnikNode)
- cislo = isinstance(parent, CisloNode)
- problem = isinstance(parent, ProblemNode)
-
- if (rocnik or cislo or problem):
- break
- else:
- parent = get_parent(parent)
- if rocnik:
- return parent.rocnik.verejne()
- elif cislo:
- return parent.cislo.verejne()
- elif problem:
- return parent.problem.verjne()
-
- def has_object_permission(self, request, view, obj):
- # test that obj is Node
- assert isinstance(obj, Node)
- return verejne_nad(node)
-
class ReadWriteSerializerMixin(object):
"""
Overrides get_serializer_class to choose the read serializer
@@ -87,10 +65,6 @@ class ReadWriteSerializerMixin(object):
)
return self.create_serializer_class
-class UlohaVzorakNodeViewSet(PermissionMixin, viewsets.ModelViewSet):
- queryset = m.UlohaVzorakNode.objects.all()
- serializer_class = views.UlohaVzorakNodeSerializer
-
class TextViewSet(PermissionMixin, viewsets.ModelViewSet):
queryset = m.Text.objects.all()
serializer_class = views.TextSerializer
@@ -107,12 +81,91 @@ class CastNodeViewSet(PermissionMixin, ReadWriteSerializerMixin,viewsets.ModelVi
write_serializer_class = views.CastNodeSerializer
create_serializer_class = views.CastNodeCreateSerializer
-class UlohaVzorakNodeViewSet(PermissionMixin, viewsets.ModelViewSet):
- serializer_class = views.UlohaVzorakNodeSerializer
+ def destroy(self, request, *args, **kwargs):
+ obj = self.get_object()
+ print(obj)
+ if obj.first_child is None:
+ return super().destroy(request,*args,**kwargs)
+ raise PermissionDenied('Nelze smazat CastNode, který má děti!')
+
+
+class UlohaVzorakNodeViewSet(PermissionMixin, ReadWriteSerializerMixin, viewsets.ModelViewSet):
+ read_serializer_class = views.UlohaVzorakNodeSerializer
+ write_serializer_class = views.UlohaVzorakNodeWriteSerializer
+ create_serializer_class = views.UlohaVzorakNodeCreateSerializer
def get_queryset(self):
queryset = m.UlohaVzorakNode.objects.all()
nazev = self.request.query_params.get('nazev',None)
if nazev is not None:
queryset = queryset.filter(nazev__contains=nazev)
+
+ if self.request.user.has_perm('auth.org'):
+ return queryset
+ else: # pro neorgy jen zveřejněné vzoráky
+ return queryset.filter(uloha__cislo_reseni__verejne_db=True)
+
+ nadproblem = self.request.query_params.get('nadproblem',None)
+ if nadproblem is not None:
+ queryset = queryset.filter(nadproblem__pk = nadproblem)
+ return queryset
+
+class ReseniViewSet(viewsets.ModelViewSet):
+ serializer_class = views.ReseniSerializer
+
+ def get_queryset(self):
+ queryset = m.Reseni.objects.all()
+ #FIXME upravit nazvy dle skutecnych polozek reseni
+ nazev = self.request.query_params.get('nazev',None)
+ if nazev is not None:
+ queryset = queryset.filter(nazev__contains=nazev)
+ nadproblem = self.request.query_params.get('nadproblem',None)
+ if nadproblem is not None:
+ queryset = queryset.filter(nadproblem__pk = nadproblem)
+ return queryset
+
+class UlohaViewSet(viewsets.ModelViewSet):
+ serializer_class = views.UlohaSerializer
+
+ def get_queryset(self):
+ queryset = m.Uloha.objects.all()
+ nazev = self.request.query_params.get('nazev',None)
+ if nazev is not None:
+ queryset = queryset.filter(nazev__contains=nazev)
+ nadproblem = self.request.query_params.get('nadproblem',None)
+ if nadproblem is not None:
+ queryset = queryset.filter(nadproblem__pk = nadproblem)
return queryset
+
+class UlohaZadaniNodeViewSet(ReadWriteSerializerMixin, viewsets.ModelViewSet):
+ queryset = m.UlohaZadaniNode.objects.all()
+ read_serializer_class = views.UlohaZadaniNodeSerializer
+ write_serializer_class = views.UlohaZadaniNodeWriteSerializer
+ create_serializer_class = views.UlohaZadaniNodeCreateSerializer
+
+class ReseniNodeViewSet(ReadWriteSerializerMixin, viewsets.ModelViewSet):
+ queryset = m.ReseniNode.objects.all()
+ read_serializer_class = views.ReseniNodeSerializer
+ write_serializer_class = views.ReseniNodeWriteSerializer
+ create_serializer_class = views.ReseniNodeCreateSerializer
+
+
+
+class ProblemViewSet(viewsets.ModelViewSet):
+ serializer_class = views.ProblemSerializer
+
+ def get_queryset(self):
+ queryset = m.Problem.objects.all()
+ ucel = self.request.query_params.get('ucel',None)
+ rocnik = self.request.query_params.get('rocnik',None)
+ tema = self.request.query_params.get('tema',None)
+
+ if rocnik is not None:
+ queryset = queryset.filter(rocnik=rocnik)
+
+ #if tema is not None:
+
+
+
+ return queryset
+
diff --git a/vue_frontend/src/components/AddNewNode.vue b/vue_frontend/src/components/AddNewNode.vue
index 2d837fb3..e2b4885a 100644
--- a/vue_frontend/src/components/AddNewNode.vue
+++ b/vue_frontend/src/components/AddNewNode.vue
@@ -1,12 +1,12 @@