diff --git a/seminar/models.py b/mamweb/vsechno.py
similarity index 100%
rename from seminar/models.py
rename to mamweb/vsechno.py
diff --git a/odevzdavatko/templates/odevzdavatko/prehled_reseni.html b/odevzdavatko/templates/odevzdavatko/prehled_reseni.html
index 0700e3fe..f58acc80 100644
--- a/odevzdavatko/templates/odevzdavatko/prehled_reseni.html
+++ b/odevzdavatko/templates/odevzdavatko/prehled_reseni.html
@@ -12,7 +12,7 @@
-{% for rocnik, hodnoceni in podle_rocniku %}
+{% for rocnik, hodnoceni, suma_bodu in podle_rocniku %}
Ročník {{ rocnik }}
@@ -33,7 +33,7 @@
{% endfor %}
-
+Celkový počet bodů {{suma_bodu}}
{% endfor %}
diff --git a/odevzdavatko/views.py b/odevzdavatko/views.py
index 652570e0..2a213a2c 100644
--- a/odevzdavatko/views.py
+++ b/odevzdavatko/views.py
@@ -356,7 +356,12 @@ class PrehledOdevzdanychReseni(ListView):
# Chceme to mít seřazené, takže místo comphrerehsion ručně postavíme pole polí. Django templates neumí použít OrderedDict :-/
podle_rocniku = []
for rocnik, hodnoceni in groupby(ctx['object_list'], lambda ho: ho.deadline_body.cislo.rocnik if ho.deadline_body is not None else None):
- podle_rocniku.append((rocnik, list(hodnoceni)))
+ suma_bodu = 0
+ hodnoceni = list(hodnoceni)
+ for i in hodnoceni :
+ if i.body != None : suma_bodu += i.body
+ podle_rocniku.append((rocnik, hodnoceni, suma_bodu))
+
ctx['podle_rocniku'] = reversed(podle_rocniku) # Od nejnovějšího ročníku
# TODO: Umožnit stažení / zobrazení řešení
return ctx
diff --git a/soustredeni/templates/soustredeni/stvrzenky.tex b/soustredeni/templates/soustredeni/stvrzenky.tex
index 78752866..6a22fbaa 100644
--- a/soustredeni/templates/soustredeni/stvrzenky.tex
+++ b/soustredeni/templates/soustredeni/stvrzenky.tex
@@ -29,7 +29,7 @@
}
{% for u in ucastnici %}
- {% with o=u.osoba %}
+ {% with o=u.resitel.osoba %}
\stvrzenka{{o.jmeno|sloz}}{{o.prijmeni|sloz}}
{% endwith %}
{% endfor %}
diff --git a/soustredeni/urls.py b/soustredeni/urls.py
index 6e7f25e5..78fc2f12 100644
--- a/soustredeni/urls.py
+++ b/soustredeni/urls.py
@@ -26,12 +26,12 @@ urlpatterns = [
),
path(
'export_ucastniku',
- org_required(views.soustredeniUcastniciExportView),
+ org_required(views.SoustredeniUcastniciExportView.as_view()),
name='soustredeni_ucastnici_export'
),
path(
'stvrzenky.pdf',
- org_required(views.soustredeniStvrzenkyView),
+ org_required(views.SoustredeniStvrzenkyView.as_view()),
name='soustredeni_ucastnici_stvrzenky'
),
path(
diff --git a/soustredeni/views.py b/soustredeni/views.py
index fcd2bf01..3cb129f7 100644
--- a/soustredeni/views.py
+++ b/soustredeni/views.py
@@ -1,4 +1,4 @@
-from django.shortcuts import get_object_or_404, render
+from django.shortcuts import get_object_or_404
from django.http import HttpResponse
from django.views import generic
from django.contrib.staticfiles.finders import find
@@ -6,13 +6,9 @@ from django.http import Http404
from django.core.exceptions import PermissionDenied
import csv
-import tempfile
-import shutil
-import subprocess
-from pathlib import Path
-import http
-import personalni.views
+import various.views.generic
+from personalni.views import obalkyView
from .models import Soustredeni, Soustredeni_Ucastnici
from various.models import Nastaveni
@@ -36,73 +32,78 @@ class SoustredeniListView(generic.ListView):
)
-def soustredeniObalkyView(request, soustredeni):
- soustredeni = get_object_or_404(Soustredeni, id=soustredeni)
- return personalni.views.obalkyView(request, soustredeni.ucastnici.all())
+class KonkretniSoustredeniMixin:
+ """ Přidá k View s parametrem `soustredeni` atribut `self.soustredeni` """
+ def setup(self, request, *args, **kwargs):
+ super().setup(request, *args, **kwargs)
+ soustredeni_id = self.kwargs["soustredeni"]
+ self.soustredeni = get_object_or_404(Soustredeni, id=soustredeni_id)
-class SoustredeniUcastniciBaseView(generic.ListView):
+class SoustredeniUcastniciBaseView(
+ KonkretniSoustredeniMixin,
+ various.views.generic.NeprazdnyListView,
+):
+ """
+ Slouží jako ListView účastníků soustředění
+ + háže inteligentní chybu při soustředění bez účastníků
+ """
model = Soustredeni_Ucastnici
+ if_prazdny_title = "K soustředění nejsou přidaní žádní účastníci"
+ if_prazdny_text = "K tebou zvolenému soustředění nejsou přidaní žádní účastníci, tedy není co zobrazit. Můžeš to zkusit změnit v adminu, případně se zeptej webařů :-)"
def get_queryset(self):
- soustredeni = get_object_or_404(
- Soustredeni,
- pk=self.kwargs["soustredeni"]
- )
return Soustredeni_Ucastnici.objects.filter(
- soustredeni=soustredeni).select_related('resitel')
+ soustredeni=self.soustredeni).select_related('resitel', 'resitel__osoba')
+
+
+# FIXME předělat jako ostatní (vyžaduje předělání `obalkyView`)
+def soustredeniObalkyView(request, soustredeni):
+ soustredeni = get_object_or_404(Soustredeni, id=soustredeni)
+ return obalkyView(request, soustredeni.ucastnici.all())
class SoustredeniMailyUcastnikuView(SoustredeniUcastniciBaseView):
""" Seznam e-mailů řešitelů oddělených čárkami. """
- model = Soustredeni_Ucastnici
template_name = 'soustredeni/maily_ucastniku.txt'
class SoustredeniUcastniciView(SoustredeniUcastniciBaseView):
""" HTML tabulka účastníků pro tisk. """
- model = Soustredeni_Ucastnici
template_name = 'soustredeni/seznam_ucastniku.html'
-def soustredeniUcastniciExportView(request, soustredeni):
- soustredeni = get_object_or_404(Soustredeni, id=soustredeni)
- ucastnici = soustredeni.ucastnici.all()
- response = HttpResponse(content_type='text/csv')
- response['Content-Disposition'] = 'attachment; filename="ucastnici.csv"'
-
- writer = csv.writer(response)
- writer.writerow(["jmeno", "prijmeni", "rok_maturity", "telefon", "email", "ulice", "mesto", "psc","stat"])
- for u in ucastnici:
- o = u.osoba
- writer.writerow([o.jmeno, o.prijmeni, str(u.rok_maturity), o.telefon, o.email, o.ulice, o.mesto, o.psc, o.stat.name])
- return response
+class SoustredeniUcastniciExportView(SoustredeniUcastniciBaseView):
+ """ CSV tabulka účastníků. """
+ def render(self, request, *args, **kwargs):
+ response = HttpResponse(content_type='text/csv')
+ response['Content-Disposition'] = 'attachment; filename="ucastnici.csv"'
-def soustredeniStvrzenkyView(request, soustredeni):
- soustredeni = get_object_or_404(Soustredeni, id=soustredeni)
- ucastnici = soustredeni.ucastnici.all()
- if ucastnici.count() == 0:
- return HttpResponse(
- render(request, 'universal.html', {
- 'title': 'Není pro koho vyrobit stvrzenky.',
- 'text': 'Právě ses pokusil/a vygenerovat stvrzenky pro prázdnou množinu lidí. Můžeš to zkusit změnit, případně se zeptej webařů :-)',
- }),
- status=http.HTTPStatus.NOT_FOUND,
- )
- castka = Nastaveni.get_solo().cena_sous
- tex = render(request, 'soustredeni/stvrzenky.tex', {'ucastnici': ucastnici, 'soustredeni': soustredeni, 'castka': castka}).content
+ writer = csv.writer(response)
+ writer.writerow(["jmeno", "prijmeni", "rok_maturity", "telefon", "email", "ulice", "mesto", "psc","stat"])
+ for u in self.object_list:
+ o = u.resitel.osoba
+ writer.writerow([o.jmeno, o.prijmeni, str(u.resitel.rok_maturity), o.telefon, o.email, o.ulice, o.mesto, o.psc, o.stat.name])
+ return response
- with tempfile.TemporaryDirectory() as tempdirfn:
- tempdir = Path(tempdirfn)
- with open(tempdir / "stvrzenky.tex", "w") as texfile:
- texfile.write(tex.decode())
- shutil.copy(find('soustredeni/logomm.pdf'), tempdir)
- subprocess.call(["pdflatex", "stvrzenky.tex"], cwd = tempdir, stdout=subprocess.DEVNULL)
+class SoustredeniStvrzenkyView(
+ various.views.generic.TeXResponseMixin,
+ SoustredeniUcastniciBaseView,
+):
+ template_name = 'soustredeni/stvrzenky.tex'
+ dalsi_potrebne_soubory = [find('soustredeni/logomm.pdf')]
- with open(tempdir / "stvrzenky.pdf", "rb") as pdffile:
- response = HttpResponse(pdffile.read(), content_type='application/pdf')
- return response
+ if_prazdny_title = "Není pro koho vyrobit stvrzenky."
+ if_prazdny_text = "Právě ses pokusil/a vygenerovat stvrzenky pro prázdnou množinu lidí. Můžeš to zkusit změnit, případně se zeptej webařů :-)"
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+
+ context["castka"] = Nastaveni.get_solo().cena_sous
+ context["soustredeni"] = self.soustredeni
+ context["ucastnici"] = self.object_list
+ return context
class SoustredeniAbstraktyView(generic.DetailView):
model = Soustredeni
diff --git a/various/views/generic.py b/various/views/generic.py
index b18178fb..98c6b880 100644
--- a/various/views/generic.py
+++ b/various/views/generic.py
@@ -1,5 +1,22 @@
+"""
+Stejně jako je `django.views.generic` jsou zde generické Views
+a pár mixinů, které upravují chování Views.
+"""
+
+import http
+import shutil
+import subprocess
+import tempfile
+
+from pathlib import Path
+
import django.views
+from django.http import HttpResponse
+from django.shortcuts import render
+from django.template.loader import render_to_string
+from django.views import generic
+
def viewMethodSwitch(get, post):
"""
@@ -27,3 +44,65 @@ def viewMethodSwitch(get, post):
return thePostView(request, *args, **kwargs)
return NewView.as_view()
+
+
+class NeprazdnyListView(generic.ListView):
+ """
+ Použití jako generic.ListView, jen při prázdném listu vyhodí M&M stránku
+ s titlem `self.if_prazdny_title` a textem `self.if_prazdny_text`
+ a způsob renderování (např. CSV) lze změnit přepsáním metody render.
+ """
+ allow_empty = False # Interní djangová věc
+ if_prazdny_title = "V seznamu nic není"
+ if_prazdny_text = "V seznamu nic není. Zkus to napravit v adminu, nebo se zeptej webařů."
+
+ # Skoro copy-paste generic.list.ListView.get,
+ # protože nemůžu chytat 404, neboť může nastat i v get_context_data
+ def get(self, request, *args, **kwargs):
+ self.object_list = self.get_queryset()
+
+ if self.get_paginate_by(self.object_list) is not None and hasattr(
+ self.object_list, "exists"
+ ):
+ is_empty = not self.object_list.exists()
+ else:
+ is_empty = not self.object_list
+ if is_empty:
+ return render(request, 'universal.html', {
+ 'title': self.if_prazdny_title,
+ 'text': self.if_prazdny_text,
+ }, status=http.HTTPStatus.NOT_FOUND)
+
+ return self.render(request, *args, **kwargs)
+
+ # Tohle jsem vyčlenil, aby šlo generovat i něco jiného než template
+ def render(self, request, *args, **kwargs):
+ context = self.get_context_data()
+ return self.render_to_response(context)
+
+
+class TeXResponseMixin:
+ """
+ Mixin pro TemplateView, aby výsledek projel TeXem a vrátil rovnou PDF.
+ Obrázky a jiné soubory lze přidat nastavením `dalsi_potrebne_soubory`
+ (např. na `[django.contrib.staticfiles.finders.find('bla')]`,
+ nebo jiný seznam absolutních cest).
+ """
+ dalsi_potrebne_soubory = []
+ tex_prikaz = "pdflatex"
+
+ def render_to_response(self, context, **response_kwargs):
+ zdrojak = render_to_string(self.get_template_names(), context)
+
+ with tempfile.TemporaryDirectory() as tempdirfn:
+ tempdir = Path(tempdirfn)
+ with open(tempdir / "main.tex", "w") as texfile:
+ texfile.write(zdrojak)
+ for file in self.dalsi_potrebne_soubory:
+ shutil.copy(file, tempdir)
+ subprocess.call([self.tex_prikaz, "main.tex"], cwd=tempdir, stdout=subprocess.DEVNULL)
+
+ with open(tempdir / "main.pdf", "rb") as pdffile:
+ response = HttpResponse(pdffile.read(), content_type='application/pdf', **response_kwargs)
+ return response
+