diff --git a/tvorba/views/__init__.py b/tvorba/views/__init__.py index 8db4424b..823ddd96 100644 --- a/tvorba/views/__init__.py +++ b/tvorba/views/__init__.py @@ -1,4 +1,584 @@ -from .views_all import * - # Dočsasné views from .docasne import * + +# Zbytek + +from django.shortcuts import get_object_or_404, render +from django.http import HttpResponse +from django.urls import reverse +from django.core.exceptions import ObjectDoesNotExist +from django.views import generic +from django.utils.translation import gettext as _ +from django.http import Http404 +from django.db.models import Q, Sum, Count +from django.views.generic.base import RedirectView +from django.core.exceptions import PermissionDenied + +import seminar.models as s +import seminar.models as m +from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, \ + Resitel, Novinky, Tema, Clanek, \ + Deadline # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci +#from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva +from treenode import treelib +import treenode.templatetags as tnltt +import treenode.serializers as vr +from vysledkovky.utils import body_resitelu, VysledkovkaCisla, \ + VysledkovkaRocniku, VysledkovkaDoTeXu + +from datetime import date, datetime +from itertools import groupby +from collections import OrderedDict +import os +import os.path as op +from django.conf import settings +import unicodedata +import logging +import time + +import personalni.views + +from .. import utils + +# ze starého modelu +#def verejna_temata(rocnik): +# """ +# Vrací queryset zveřejněných témat v daném ročníku. +# """ +# return Problem.objects.filter(typ=Problem.TYP_TEMA, cislo_zadani__rocnik=rocnik, cislo_zadani__verejne_db=True).order_by('kod') +# +#def temata_v_rocniku(rocnik): +# return Problem.objects.filter(typ=Problem.TYP_TEMA, rocnik=rocnik) + +logger = logging.getLogger(__name__) + +def get_problemy_k_tematu(tema): + return Problem.objects.filter(nadproblem = tema) + + +# FIXME: Pozor, níž je ještě jeden ProblemView! +#class ProblemView(generic.DetailView): +# model = s.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, s.Clanek) or isinstance(self.object, s.Konfera): +# # Tyhle Problémy mají ŘešeníNode +# 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,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 +# context['tnldata'] = TNLData.filter_treenode(rocniknode, lambda x: isinstance(x, s.TemaVCisleNode)) +# else: +# raise ValueError("Obecný problém nejde zobrazit.") +# return context + + +#class AktualniZadaniView(generic.TemplateView): +# template_name = 'treenode/treenode.html' + +# TODO Co chceme vlastně zobrazovat na této stránce? Zatím je zde aktuální číslo, ale může tu být cokoli jiného... +#class AktualniZadaniView(TreeNodeView): +# def get_object(self): +# nastaveni = get_object_or_404(Nastaveni) +# return nastaveni.aktualni_cislo.cislonode +# +# def get_context_data(self,**kwargs): +# nastaveni = get_object_or_404(Nastaveni) +# context = super().get_context_data(**kwargs) +# verejne = nastaveni.aktualni_cislo.verejne() +# context['verejne'] = verejne +# return context + +def AktualniZadaniView(request): + nastaveni = get_object_or_404(Nastaveni) + verejne = nastaveni.aktualni_cislo.verejne() + return render(request, 'tvorba/zadani/AktualniZadani.html', + {'nastaveni': nastaveni, + 'verejne': verejne, + }, + ) + +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, 'tvorba/tematka/rozcestnik.html', + { + 'tematka': temata, + 'verejne': verejne, + }, + ) + + +# nastaveni = get_object_or_404(Nastaveni) +# temata = verejna_temata(nastaveni.aktualni_rocnik) +# for t in temata: +# if request.user.is_staff: +# t.prispevky = t.prispevek_set.filter(problem=t) +# else: +# t.prispevky = t.prispevek_set.filter(problem=t, zverejnit=True) +# return render(request, 'tvorba/zadani/Temata.html', +# { +# 'temata': temata, +# } +# ) +# +# +# +#def TematkoView(request, rocnik, tematko): +# nastaveni = s.Nastaveni.objects.first() +# rocnik_object = s.Rocnik.objects.filter(rocnik=rocnik) +# tematko_object = s.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, s.KonferaNode): +# raise Exception("Not implemented yet") +# if node.isinstance(node, s.PohadkaNode): # Mohu ignorovat, má pod sebou +# pass +# +# return render(request, 'tvorba/tematka/toaletak.html', {}) +# +# +#def TemataRozcestnikView(request): +# print("=============================================") +# nastaveni = s.Nastaveni.objects.first() +# tematka_objects = s.Tema.objects.filter(rocnik=nastaveni.aktualni_rocnik()) +# tematka = [] #List tematka obsahuje pro kazde tematko object a list vsech TemaVCisleNodu - implementované pomocí slovníku +# for tematko_object in tematka_objects: +# print("AKTUALNI TEMATKO") +# print(tematko_object.id) +# odkazy = vytahniZLesaSeznam(tematko_object, nastaveni.aktualni_rocnik().rocniknode, pouze_zajimave = True) #Odkazy jsou tuply (node, depth) v listu +# print(odkazy) +# cisla = [] # List tuplů (nazev cisla, list odkazů) +# vcisle = [] +# cislo = None +# for odkaz in odkazy: +# if odkaz[1] == 0: +# if cislo != None: +# cisla.append((cislo, vcisle)) +# cislo = (odkaz[0].getOdkazStr(), odkaz[0].getOdkaz()) +# vcisle = [] +# else: +# print(odkaz[0].getOdkaz()) +# vcisle.append((odkaz[0].getOdkazStr(), odkaz[0].getOdkaz())) +# if cislo != None: +# cisla.append((cislo, vcisle)) +# +# print(cisla) +# tematka.append({ +# "kod" : tematko_object.kod, +# "nazev" : tematko_object.nazev, +# "abstrakt" : tematko_object.abstrakt, +# "obrazek": tematko_object.obrazek, +# "cisla" : cisla +# }) +# return render(request, 'tvorba/tematka/rozcestnik.html', {"tematka": tematka, "rocnik" : nastaveni.aktualni_rocnik().rocnik}) +# + +def ZadaniAktualniVysledkovkaView(request): + nastaveni = get_object_or_404(Nastaveni) + # Aktualni verejna vysledkovka + rocnik = nastaveni.aktualni_rocnik + context = {'vysledkovka': VysledkovkaRocniku(rocnik, True)} + + # kdyz neni verejna vysledkovka, tak zobraz starou + if len(context['vysledkovka'].cisla_rocniku) == 0: + try: + minuly_rocnik = Rocnik.objects.get( + rocnik=(rocnik.rocnik-1)) + rocnik = minuly_rocnik + + # Přepíšeme prázdnou výsledkovku výsledkovkou z minulého ročníku + context['vysledkovka'] = VysledkovkaRocniku(rocnik, True) + except ObjectDoesNotExist: + pass + + context['rocnik'] = rocnik + return render( + request, + 'tvorba/zadani/AktualniVysledkovka.html', + context + ) + + +### Titulni strana + +def aktualni_temata(rocnik): + """ + Vrací PolymorphicQuerySet témat v daném ročníku, ke kterým se aktuálně dá něco odevzdat. + """ + return Tema.objects.filter(rocnik=rocnik, stav='zadany').order_by('kod') + + +### 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, "tvorba", "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( + utils.resi_v_rocniku( + get_object_or_404(m.Rocnik, rocnik=rocnik) + ) + ) + + +# 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 = { +# s.Uloha: "uloha", +# s.Tema: "tema", +# s.Konfera: "konfera", +# s.Clanek: "clanek", +# } +# context = super().get_context_data(**kwargs) +# return ['tvorba/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 + + + +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 = { + m.Deadline.TYP_CISLA: "Výsledkovka", + m.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í", + m.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 = utils.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 personalni.views.obalkyView(request, utils.aktivniResitele(realne_cislo)) + + + +### 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 +def group_by_rocnik(clanky): + ''' Vezme zadaný seznam článků a seskupí je podle ročníku. + Vrátí seznam seznamů článků ze stejného ročníku.''' + if len(clanky) == 0: + return clanky + clanky.order_by('cislo__rocnik__rocnik') + skupiny_clanku = [] + skupina = [] + + rocnik = clanky.first().cislo.rocnik.rocnik # první ročník + for clanek in clanky: + if clanek.cislo.rocnik.rocnik == rocnik: + skupina.append(clanek) + else: + skupiny_clanku.append(skupina) + skupina = [] + skupina.append(clanek) + rocnik = clanek.cislo.rocnik.rocnik + skupiny_clanku.append(skupina) + return skupiny_clanku + + +# 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): +# 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') + + + + +class AktualniRocnikRedirectView(RedirectView): + permanent=False + pattern_name = 'seminar_rocnik' + + def get_redirect_url(self, *args, **kwargs): + aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik.rocnik + return super().get_redirect_url(rocnik=aktualni_rocnik, *args, **kwargs)