diff --git a/soustredeni/urls.py b/soustredeni/urls.py index 7ae79cdd..ccc3c493 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 a0e3fe13..bed2d02b 100644 --- a/soustredeni/views.py +++ b/soustredeni/views.py @@ -1,15 +1,11 @@ -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 import csv -import tempfile -import shutil -import subprocess -from pathlib import Path -import http +import various.views from seminar.views import obalkyView from .models import Soustredeni, Soustredeni_Ucastnici @@ -34,70 +30,69 @@ class SoustredeniListView(generic.ListView): ) -def soustredeniObalkyView(request, soustredeni): - soustredeni = get_object_or_404(Soustredeni, id=soustredeni) - return 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.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 - -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 +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"' + + 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 + + +class SoustredeniStvrzenkyView(various.views.TeXResponseMixin, SoustredeniUcastniciBaseView): + template_name = 'soustredeni/stvrzenky.tex' + dalsi_potrebne_soubory = [find('images/logomm.pdf')] - with tempfile.TemporaryDirectory() as tempdirfn: - tempdir = Path(tempdirfn) - with open(tempdir / "stvrzenky.tex", "w") as texfile: - texfile.write(tex.decode()) + 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řů :-)" - shutil.copy(find('images/logomm.pdf'), tempdir) - subprocess.call(["pdflatex", "stvrzenky.tex"], cwd = tempdir, stdout=subprocess.DEVNULL) + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) - with open(tempdir / "stvrzenky.pdf", "rb") as pdffile: - response = HttpResponse(pdffile.read(), content_type='application/pdf') - return response + context["castka"] = Nastaveni.get_solo().cena_sous + context["soustredeni"] = self.soustredeni + context["ucastnici"] = self.object_list + return context diff --git a/various/views.py b/various/views.py index 96d9a29d..c4d72b2d 100644 --- a/various/views.py +++ b/various/views.py @@ -1,7 +1,14 @@ -from django.http import HttpResponseForbidden -from django.shortcuts import render +import http +import tempfile +import shutil +import subprocess + +from pathlib import Path -# Create your views here. +from django.http import HttpResponseForbidden, HttpResponse +from django.shortcuts import render +from django.views import generic +from django.template.loader import render_to_string def csrf_error(request, reason=""): @@ -11,3 +18,68 @@ def csrf_error(request, reason=""): {"url": request.META.get("HTTP_REFERER", None), "reason": reason}, status=HttpResponseForbidden.status_code, ) + + +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 + 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 = "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, "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 + +