Web M&M
https://mam.matfyz.cz
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
408 lines
15 KiB
408 lines
15 KiB
1 year ago
|
import tempfile
|
||
|
import subprocess
|
||
|
import shutil
|
||
|
import os
|
||
|
import os.path as op
|
||
|
import unicodedata
|
||
|
from itertools import groupby
|
||
|
from collections import OrderedDict
|
||
|
|
||
|
from django.shortcuts import get_object_or_404, render
|
||
|
from django.http import HttpResponse
|
||
|
from django.views import generic
|
||
|
from django.utils.translation import ugettext as _
|
||
|
from django.http import Http404
|
||
|
from django.db.models import Q
|
||
|
from django.conf import settings
|
||
|
|
||
|
from personalni.models import Resitel
|
||
|
from .models import *
|
||
|
from .utils import *
|
||
|
from personalni.utils import aktivniResitele, resi_v_rocniku
|
||
|
from vysledkovky.utils import body_resitelu, VysledkovkaCisla, \
|
||
|
VysledkovkaRocniku, VysledkovkaDoTeXu
|
||
|
|
||
|
|
||
|
### Archiv
|
||
|
class ArchivView(generic.ListView):
|
||
|
model = Rocnik
|
||
|
template_name = 'tvorba/archiv/cisla.html'
|
||
|
|
||
|
def get_context_data(self, **kwargs):
|
||
|
context = super(ArchivView, self).get_context_data(**kwargs)
|
||
|
|
||
|
cisla = Cislo.objects.filter(poradi=1)
|
||
|
if not self.request.user.je_org:
|
||
|
cisla = cisla.filter(verejne_db=True)
|
||
|
urls = {}
|
||
|
|
||
|
for i, c in enumerate(cisla):
|
||
|
# Výchozí nastavení
|
||
|
if c.rocnik not in urls:
|
||
|
urls[c.rocnik] = op.join(settings.STATIC_URL, "images", "no-picture.png")
|
||
|
# NOTE: tohle možná nastavuje poslední titulku
|
||
|
if c.titulka_nahled:
|
||
|
urls[c.rocnik] = c.titulka_nahled.url
|
||
|
|
||
|
context["object_list"] = urls
|
||
|
|
||
|
return context
|
||
|
|
||
|
|
||
|
class RocnikView(generic.DetailView):
|
||
|
model = Rocnik
|
||
|
template_name = 'tvorba/archiv/rocnik.html'
|
||
|
|
||
|
# Vlastni ziskavani objektu z databaze podle (Rocnik.rocnik)
|
||
|
def get_object(self, queryset=None):
|
||
|
if queryset is None:
|
||
|
queryset = self.get_queryset()
|
||
|
|
||
|
return get_object_or_404(queryset, rocnik=self.kwargs.get('rocnik'))
|
||
|
|
||
|
def get_context_data(self, **kwargs):
|
||
|
context = super(RocnikView, self).get_context_data(**kwargs)
|
||
|
context["vysledkovka"] = VysledkovkaRocniku(context["rocnik"], True)
|
||
|
context["neprazdna_vysledkovka"] = len(context['vysledkovka'].cisla_rocniku) != 0
|
||
|
context["vysledkovka_neverejna"] = VysledkovkaRocniku(context["rocnik"], False)
|
||
|
return context
|
||
|
|
||
|
|
||
|
def resiteleRocnikuCsvExportView(request, rocnik):
|
||
|
from personalni.views import dataResiteluCsvResponse
|
||
|
assert request.method in ('GET', 'HEAD')
|
||
|
return dataResiteluCsvResponse(
|
||
|
resi_v_rocniku(
|
||
|
get_object_or_404(Rocnik, rocnik=rocnik)
|
||
|
)
|
||
|
)
|
||
|
|
||
|
|
||
|
class CisloView(generic.DetailView):
|
||
|
# FIXME zobrazování témátek a vůbec, teď je tam jen odkaz na číslo v pdf
|
||
|
model = Cislo
|
||
|
template_name = 'tvorba/archiv/cislo.html'
|
||
|
|
||
|
# Vlastni ziskavani objektu z databaze podle (Rocnik.rocnik)
|
||
|
def get_object(self, queryset=None):
|
||
|
if queryset is None:
|
||
|
queryset = self.get_queryset()
|
||
|
rocnik_arg = self.kwargs.get('rocnik')
|
||
|
poradi_arg = self.kwargs.get('cislo')
|
||
|
queryset = queryset.filter(rocnik__rocnik=rocnik_arg, poradi=poradi_arg)
|
||
|
|
||
|
try:
|
||
|
obj = queryset.get()
|
||
|
except queryset.model.DoesNotExist:
|
||
|
raise Http404(_("No %(verbose_name)s found matching the query") %
|
||
|
{'verbose_name': queryset.model._meta.verbose_name})
|
||
|
return obj
|
||
|
|
||
|
def get_context_data(self, **kwargs):
|
||
|
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()
|
||
|
|
||
|
deadliny = Deadline.objects.filter(cislo=cislo).reverse()
|
||
|
deadliny_s_vysledkovkami = []
|
||
|
|
||
|
nadpisy = {
|
||
|
Deadline.TYP_CISLA: "Výsledkovka",
|
||
|
Deadline.TYP_PRVNI: "Výsledkovka do prvního deadlinu",
|
||
|
Deadline.TYP_PRVNI_A_SOUS: "Výsledkovka do prvního deadlinu a deadlinu pro účast na soustředění",
|
||
|
Deadline.TYP_SOUS: "Výsledkovka do deadlinu pro účast na soustředění",
|
||
|
}
|
||
|
|
||
|
for deadline in deadliny:
|
||
|
if self.request.user.je_org | deadline.verejna_vysledkovka:
|
||
|
deadliny_s_vysledkovkami.append((deadline, nadpisy[deadline.typ], VysledkovkaCisla(cislo, not self.request.user.je_org, deadline)))
|
||
|
|
||
|
context['deadliny_s_vysledkovkami'] = deadliny_s_vysledkovkami
|
||
|
return context
|
||
|
|
||
|
|
||
|
class ArchivTemataView(generic.ListView):
|
||
|
model = Problem
|
||
|
template_name = 'tvorba/archiv/temata.html'
|
||
|
queryset = Tema.objects.filter(stav=Problem.STAV_ZADANY).select_related('rocnik').order_by('rocnik', 'kod')
|
||
|
|
||
|
def get_context_data(self, *args, **kwargs):
|
||
|
ctx = super().get_context_data(*args, **kwargs)
|
||
|
ctx['rocniky'] = OrderedDict()
|
||
|
for rocnik, temata in groupby(ctx['object_list'], lambda tema: tema.rocnik):
|
||
|
ctx['rocniky'][rocnik] = list(temata)
|
||
|
return ctx
|
||
|
|
||
|
|
||
|
class OdmenyView(generic.TemplateView):
|
||
|
template_name = 'tvorba/archiv/odmeny.html'
|
||
|
|
||
|
def get_context_data(self, **kwargs):
|
||
|
context = super().get_context_data(**kwargs)
|
||
|
fromcislo = get_object_or_404(Cislo, rocnik=self.kwargs.get('frocnik'), poradi=self.kwargs.get('fcislo'))
|
||
|
tocislo = get_object_or_404(Cislo, rocnik=self.kwargs.get('trocnik'), poradi=self.kwargs.get('tcislo'))
|
||
|
resitele = aktivniResitele(tocislo)
|
||
|
|
||
|
def get_diff(from_deadline: Deadline, to_deadline: Deadline):
|
||
|
frombody = body_resitelu(resitele=resitele, jen_verejne=False, do=from_deadline)
|
||
|
tobody = body_resitelu(resitele=resitele, jen_verejne=False, do=to_deadline)
|
||
|
outlist = []
|
||
|
for resitel in resitele:
|
||
|
fbody = frombody.get(resitel.id, 0)
|
||
|
tbody = tobody.get(resitel.id, 0)
|
||
|
ftitul = resitel.get_titul(fbody)
|
||
|
ttitul = resitel.get_titul(tbody)
|
||
|
if ftitul != ttitul:
|
||
|
outlist.append({'jmeno': resitel.osoba.plne_jmeno(), 'ftitul': ftitul, 'ttitul': ttitul})
|
||
|
return outlist
|
||
|
|
||
|
def posledni_deadline_oprava(cislo: Cislo) -> Deadline:
|
||
|
posledni_deadline = cislo.posledni_deadline
|
||
|
if posledni_deadline is None:
|
||
|
return Deadline.objects.filter(Q(cislo__poradi__lt=cislo.poradi, cislo__rocnik=cislo.rocnik) | Q(cislo__rocnik__rocnik__lt=cislo.rocnik.rocnik)).order_by("deadline").last()
|
||
|
return posledni_deadline
|
||
|
|
||
|
context["from_cislo"] = fromcislo
|
||
|
context["to_cislo"] = tocislo
|
||
|
from_deadline = posledni_deadline_oprava(fromcislo)
|
||
|
to_deadline = posledni_deadline_oprava(tocislo)
|
||
|
context["from_deadline"] = from_deadline
|
||
|
context["to_deadline"] = to_deadline
|
||
|
context["zmeny"] = get_diff(from_deadline, to_deadline)
|
||
|
|
||
|
return context
|
||
|
|
||
|
|
||
|
### Generovani vysledkovky
|
||
|
class CisloVysledkovkaView(CisloView):
|
||
|
"""View vytvořené pro stránku zobrazující výsledkovku čísla v TeXu."""
|
||
|
|
||
|
model = Cislo
|
||
|
template_name = 'tvorba/archiv/cislo_vysledkovka.tex'
|
||
|
# content_type = 'application/x-tex; charset=UTF8'
|
||
|
# umozni rovnou stahnout TeXovsky dokument
|
||
|
content_type = 'text/plain; charset=UTF8'
|
||
|
# vypise na stranku textovy obsah vyTeXane vysledkovky k okopirovani
|
||
|
|
||
|
def get_context_data(self, **kwargs):
|
||
|
context = super(CisloVysledkovkaView, self).get_context_data()
|
||
|
cislo = context['cislo']
|
||
|
|
||
|
cislopred = cislo.predchozi()
|
||
|
if cislopred is not None:
|
||
|
context['vysledkovka'] = VysledkovkaDoTeXu(
|
||
|
cislo,
|
||
|
od_vyjma=cislopred.zlomovy_deadline_pro_papirove_cislo(),
|
||
|
do_vcetne=cislo.zlomovy_deadline_pro_papirove_cislo(),
|
||
|
)
|
||
|
else:
|
||
|
context['vysledkovka'] = VysledkovkaCisla(
|
||
|
cislo,
|
||
|
jen_verejne=False,
|
||
|
do_deadlinu=cislo.zlomovy_deadline_pro_papirove_cislo(),
|
||
|
)
|
||
|
return context
|
||
|
|
||
|
|
||
|
# Podle předchozího
|
||
|
class PosledniCisloVysledkovkaView(generic.DetailView):
|
||
|
"""View vytvořené pro zobrazení výsledkovky posledního čísla v TeXu."""
|
||
|
|
||
|
model = Rocnik
|
||
|
template_name = 'tvorba/archiv/cislo_vysledkovka.tex'
|
||
|
content_type = 'text/plain; charset=UTF8'
|
||
|
|
||
|
def get_object(self, queryset=None):
|
||
|
if queryset is None:
|
||
|
queryset = self.get_queryset()
|
||
|
rocnik_arg = self.kwargs.get('rocnik')
|
||
|
queryset = queryset.filter(rocnik=rocnik_arg)
|
||
|
|
||
|
try:
|
||
|
obj = queryset.get()
|
||
|
except queryset.model.DoesNotExist:
|
||
|
raise Http404(_("No %(verbose_name)s found matching the query") %
|
||
|
{'verbose_name': queryset.model._meta.verbose_name})
|
||
|
return obj
|
||
|
|
||
|
def get_context_data(self, **kwargs):
|
||
|
context = super(PosledniCisloVysledkovkaView, self).get_context_data()
|
||
|
rocnik = context['rocnik']
|
||
|
cislo = rocnik.cisla.order_by("poradi").filter(deadline_v_cisle__isnull=False).last()
|
||
|
if cislo is None:
|
||
|
raise Http404(f"Ročník {rocnik.rocnik} nemá číslo s deadlinem.")
|
||
|
cislopred = cislo.predchozi()
|
||
|
context['vysledkovka'] = VysledkovkaDoTeXu(
|
||
|
cislo,
|
||
|
od_vyjma=cislopred.zlomovy_deadline_pro_papirove_cislo(),
|
||
|
do_vcetne=cislo.deadline_v_cisle.order_by("deadline").last(),
|
||
|
)
|
||
|
return context
|
||
|
|
||
|
|
||
|
class RocnikVysledkovkaView(RocnikView):
|
||
|
""" View vytvořené pro stránku zobrazující výsledkovku ročníku v TeXu."""
|
||
|
model = Rocnik
|
||
|
template_name = 'tvorba/archiv/rocnik_vysledkovka.tex'
|
||
|
# content_type = 'application/x-tex; charset=UTF8'
|
||
|
# umozni rovnou stahnout TeXovsky dokument
|
||
|
content_type = 'text/plain; charset=UTF8'
|
||
|
# vypise na stranku textovy obsah vyTeXane vysledkovky k okopirovani
|
||
|
|
||
|
|
||
|
def cisloObalkyView(request, rocnik, cislo):
|
||
|
realne_cislo = get_object_or_404(Cislo, poradi=cislo, rocnik__rocnik=rocnik)
|
||
|
return obalkyView(request, aktivniResitele(realne_cislo))
|
||
|
|
||
|
|
||
|
def obalkyView(request, resitele):
|
||
|
tex = render(request, 'tvorba/archiv/obalky.tex', {'resitele': resitele}).content
|
||
|
|
||
|
tempdir = tempfile.mkdtemp()
|
||
|
with open(tempdir+"/obalky.tex", "w") as texfile:
|
||
|
texfile.write(tex.decode())
|
||
|
shutil.copy(os.path.join(settings.STATIC_ROOT, 'seminar/lisak.pdf'), tempdir)
|
||
|
subprocess.call(["pdflatex", "obalky.tex"], cwd=tempdir)
|
||
|
|
||
|
with open(tempdir+"/obalky.pdf", "rb") as pdffile:
|
||
|
response = HttpResponse(pdffile.read(), content_type='application/pdf')
|
||
|
shutil.rmtree(tempdir)
|
||
|
return response
|
||
|
|
||
|
|
||
|
### Tituly
|
||
|
def TitulyViewRocnik(request, rocnik):
|
||
|
return TitulyView(request, rocnik, None)
|
||
|
|
||
|
|
||
|
def TitulyView(request, rocnik, cislo):
|
||
|
""" View pro stažení makra titulů v TeXu."""
|
||
|
rocnik_obj = get_object_or_404(Rocnik, rocnik=rocnik)
|
||
|
resitele = Resitel.objects.filter(rok_maturity__gte=rocnik_obj.prvni_rok)
|
||
|
|
||
|
asciijmena = []
|
||
|
jmenovci = False # detekuje, zda jsou dva řešitelé jmenovci (modulo nabodeníčka),
|
||
|
# pokud ano, vrátí se jako true
|
||
|
if cislo is not None:
|
||
|
cislo_obj = get_object_or_404(Cislo, rocnik=rocnik_obj, poradi=cislo)
|
||
|
slovnik_s_body = body_resitelu(do=cislo_obj.zlomovy_deadline_pro_papirove_cislo(), jen_verejne=False)
|
||
|
else:
|
||
|
slovnik_s_body = body_resitelu(do=Deadline.objects.filter(cislo__rocnik=rocnik_obj).last(), jen_verejne=False)
|
||
|
|
||
|
for resitel in resitele:
|
||
|
resitel.titul = resitel.get_titul(slovnik_s_body[resitel.id])
|
||
|
jmeno = resitel.osoba.jmeno+resitel.osoba.prijmeni
|
||
|
# převedeme jména a příjmení řešitelů do ASCII
|
||
|
ascii_jmeno_bytes = unicodedata.normalize('NFKD', jmeno).encode("ascii", "ignore")
|
||
|
# vrátí se byte string, převedeme na standardní string
|
||
|
ascii_jmeno_divnoznaky = str(ascii_jmeno_bytes, "utf-8", "ignore").replace(" ", "")
|
||
|
resitel.ascii = ''.join(a for a in ascii_jmeno_divnoznaky if a.isalnum())
|
||
|
if resitel.ascii not in asciijmena:
|
||
|
asciijmena.append(resitel.ascii)
|
||
|
else:
|
||
|
jmenovci = True
|
||
|
|
||
|
return render(
|
||
|
request,
|
||
|
'tvorba/archiv/tituly.tex',
|
||
|
{'resitele': resitele, 'jmenovci': jmenovci},
|
||
|
content_type="text/plain",
|
||
|
)
|
||
|
|
||
|
|
||
|
### Články
|
||
|
# FIXME: clanky jsou vsechny, pokud budou i neresitelske, tak se take zobrazi
|
||
|
# FIXME: Původně tu byl kód přímo v těle třídy, což rozbíjelo migrace. Opravil jsem, ale vůbec nevím, jestli to funguje.
|
||
|
class ClankyResitelView(generic.ListView):
|
||
|
model = Problem
|
||
|
template_name = 'tvorba/clanky/resitelske_clanky.html'
|
||
|
|
||
|
# FIXME: QuerySet není pole!
|
||
|
def get_queryset(self):
|
||
|
clanky = Clanek.objects.filter(stav=Problem.STAV_VYRESENY).select_related('cislo__rocnik').order_by('-cislo__rocnik__rocnik')
|
||
|
queryset = []
|
||
|
skupiny_clanku = group_by_rocnik(clanky)
|
||
|
for skupina in skupiny_clanku:
|
||
|
skupina.sort(key=lambda clanek: clanek.kod_v_rocniku)
|
||
|
for clanek in skupina:
|
||
|
queryset.append(clanek)
|
||
|
return queryset
|
||
|
|
||
|
# FIXME: pokud chceme orgoclanky, tak nejak zavest do modelu a podle toho odkomentovat a upravit
|
||
|
# class ClankyOrganizatorView(generic.ListView)<F12>:
|
||
|
# model = Problem
|
||
|
# template_name = 'tvorba/clanky/organizatorske_clanky.html'
|
||
|
# queryset = Problem.objects.filter(stav=Problem.STAV_ZADANY).select_related('cislo_zadani__rocnik').order_by('-cislo_zadani__rocnik__rocnik', 'kod')
|
||
|
|
||
|
|
||
|
# def TematkoView(request, rocnik, tematko):
|
||
|
# nastaveni = Nastaveni.objects.first()
|
||
|
# rocnik_object = Rocnik.objects.filter(rocnik=rocnik)
|
||
|
# tematko_object = Tema.objects.filter(rocnik=rocnik_object[0], kod=tematko)
|
||
|
# seznam = vytahniZLesaSeznam(tematko_object[0], nastaveni.aktualni_rocnik().rocniknode)
|
||
|
# for node, depth in seznam:
|
||
|
# if node.isinstance(node, KonferaNode):
|
||
|
# raise Exception("Not implemented yet")
|
||
|
# if node.isinstance(node, PohadkaNode): # Mohu ignorovat, má pod sebou
|
||
|
# pass
|
||
|
#
|
||
|
# return render(request, 'seminar/tematka/toaletak.html', {})
|
||
|
|
||
|
from seminar.views.docasne import problemView
|
||
|
|
||
|
# FIXME: Pozor, níž je ještě jeden ProblemView!
|
||
|
# class ProblemView(generic.DetailView):
|
||
|
# model = Problem
|
||
|
# # Zkopírujeme template_name od TreeNodeView, protože jsme prakticky jen trošku upravený TreeNodeView
|
||
|
# template_name = TreeNodeView.template_name
|
||
|
#
|
||
|
# 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:
|
||
|
# # Hezčí formátování zbytku :-P
|
||
|
# pass
|
||
|
# elif isinstance(self.object, Clanek) or isinstance(self.object, Konfera):
|
||
|
# # Tyhle Problémy mají ŘešeníNode
|
||
|
# context['tnldata'] = TNLData.from_treenode(self.object.reseninode,user)
|
||
|
# 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
|
||
|
# 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, Tema):
|
||
|
# rocniknode = self.object.rocnik.rocniknode
|
||
|
# context['tnldata'] = TNLData.filter_treenode(rocniknode, lambda x: isinstance(x, TemaVCisleNode))
|
||
|
# else:
|
||
|
# raise ValueError("Obecný problém nejde zobrazit.")
|
||
|
# return context
|
||
|
|
||
|
|
||
|
# FIXME: Pozor, výš je ještě jeden ProblemView!
|
||
|
# class ProblemView(generic.DetailView):
|
||
|
# model = Problem
|
||
|
#
|
||
|
# # Používáme funkci, protože přímo template_name neumí mít v přiřazení dost logiky. Ledaže by se to udělalo polymorfně...
|
||
|
# 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.
|
||
|
# spravne_templaty = {
|
||
|
# Uloha: "uloha",
|
||
|
# Tema: "tema",
|
||
|
# Konfera: "konfera",
|
||
|
# Clanek: "clanek",
|
||
|
# }
|
||
|
# context = super().get_context_data(**kwargs)
|
||
|
# return ['seminar/archiv/problem_' + spravne_templaty[context['object'].__class__] + '.html']
|
||
|
#
|
||
|
# def get_context_data(self, **kwargs):
|
||
|
# context = super().get_context_data(**kwargs)
|
||
|
# # Musí se používat context['object'], protože nevíme, jestli dostaneme úložku, téma, článek, .... a tyhle věci vyrábějí různé klíče.
|
||
|
# if not context['object'].verejne() and not self.request.user.je_org:
|
||
|
# raise PermissionDenied()
|
||
|
# if isinstance(context['object'], Clanek):
|
||
|
# context['reseni'] = Reseni.objects.filter(problem=context['object']).select_related('resitel').order_by('resitel__prijmeni')
|
||
|
# return context
|