# 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 m from personalni.models import Resitel from soustredeni.models import Konfera from tvorba.models import Problem, Cislo, Rocnik, Tema, Clanek, Deadline, Uloha from various.models import Nastaveni 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 = 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, m.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 = 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 = 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, m.KonferaNode): # raise Exception("Not implemented yet") # if node.isinstance(node, m.PohadkaNode): # Mohu ignorovat, má pod sebou # pass # # return render(request, 'tvorba/tematka/toaletak.html', {}) # # #def TemataRozcestnikView(request): # print("=============================================") # nastaveni = Nastaveni.objects.first() # tematka_objects = 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(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 = { # Uloha: "uloha", # Tema: "tema", # Konfera: "konfera", # 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 = { 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 = 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 = 'tvorba_rocnik' def get_redirect_url(self, *args, **kwargs): aktualni_rocnik = Nastaveni.get_solo().aktualni_rocnik.rocnik return super().get_redirect_url(rocnik=aktualni_rocnik, *args, **kwargs)