from django.shortcuts import get_object_or_404, render, redirect from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, JsonResponse from django.urls import reverse,reverse_lazy from django.core.exceptions import PermissionDenied, ObjectDoesNotExist from django.views import generic from django.utils.translation import ugettext as _ from django.http import Http404,HttpResponseBadRequest,HttpResponseRedirect from django.db.models import Q, Sum, Count from django.views.decorators.csrf import ensure_csrf_cookie from django.views.generic.edit import FormView, CreateView from django.views.generic.base import TemplateView from django.contrib.auth import authenticate, login, get_user_model, logout from django.contrib.auth import views as auth_views from django.contrib.auth.models import User, Permission from django.contrib.auth.mixins import LoginRequiredMixin from django.db import transaction from django.core import serializers from django.core.exceptions import PermissionDenied from django.forms.models import model_to_dict import seminar.models as s import seminar.models as m from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni, Organizator, Resitel, Novinky, Soustredeni_Ucastnici, Pohadka, Tema, Clanek, Osoba, Skola # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci #from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva from seminar import utils, treelib from seminar.forms import PrihlaskaForm, LoginForm, ProfileEditForm import seminar.forms as f import seminar.templatetags.treenodes as tnltt import seminar.views.views_rest as vr from datetime import timedelta, date, datetime, MAXYEAR from django.utils import timezone from itertools import groupby from collections import OrderedDict import tempfile import subprocess import shutil import os import os.path as op from django.conf import settings import unicodedata import json import traceback import sys import csv import logging import time from seminar.utils import aktivniResitele, resi_v_rocniku # 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) def get_problemy_k_tematu(tema): return Problem.objects.filter(nadproblem = tema) class VlozBodyView(generic.ListView): template_name = 'seminar/org/vloz_body.html' def get_queryset(self): self.tema = get_object_or_404(Problem,id=self.kwargs['tema']) print(self.tema) self.problemy = Problem.objects.filter(nadproblem = self.tema) print(self.problemy) self.reseni = Reseni.objects.filter(problem__in=self.problemy) print(self.reseni) return self.reseni class ObalkovaniView(generic.ListView): template_name = 'seminar/org/obalkovani.html' def get_queryset(self): rocnik = get_object_or_404(Rocnik,rocnik=self.kwargs['rocnik']) cislo = get_object_or_404(Cislo,rocnik=rocnik,poradi=self.kwargs['cislo']) self.cislo = cislo self.hodnoceni = s.Hodnoceni.objects.filter(cislo_body=cislo) self.reseni = Reseni.objects.filter(hodnoceni__in = self.hodnoceni).annotate(Sum('hodnoceni__body')).annotate(Count('hodnoceni')).order_by('resitele__osoba') return self.reseni def get_context_data(self, **kwargs): context = super(ObalkovaniView, self).get_context_data(**kwargs) print(self.cislo) context['cislo'] = self.cislo return context class TNLData(object): def __init__(self,anode,parent=None, index=None): self.node = anode self.sernode = vr.TreeNodeSerializer(anode) self.children = [] self.parent = parent self.tema_in_path = False self.index = index if parent: self.tema_in_path = parent.tema_in_path if isinstance(anode, m.TemaVCisleNode): self.tema_in_path = True def add_edit_options(self): self.deletable = tnltt.deletable(self) self.editable_siblings = tnltt.editableSiblings(self) self.editable_children = tnltt.editableChildren(self) self.text_only_subtree = tnltt.textOnlySubtree(self) self.can_podvesit_za = tnltt.canPodvesitZa(self) self.can_podvesit_pred = tnltt.canPodvesitPred(self) self.appendable_children = tnltt.appendableChildren(self) print("appChld",self.appendable_children) if self.parent: self.appendable_siblings = tnltt.appendableChildren(self.parent) else: self.appendable_siblings = [] @classmethod def public_above(cls, anode): """ Returns output of verejne for closest Rocnik, Cislo or Problem above. (All of them have method verejne.)""" parent = anode # chceme začít už od konkrétního node včetně while True: rocnik = isinstance(parent, s.RocnikNode) cislo = isinstance(parent, s.CisloNode) uloha = (isinstance(parent, s.UlohaVzorakNode) or isinstance(parent, s.UlohaZadaniNode)) tema = isinstance(parent, s.TemaVCisleNode) if (rocnik or cislo or uloha or tema) or parent==None: break else: parent = treelib.get_parent(parent) if rocnik: return parent.rocnik.verejne() elif cislo: return parent.cislo.verejne() elif uloha: return parent.uloha.verejne() elif tema: return parent.tema.verejne() elif None: print("Existuje TreeNode, který není pod číslem, ročníkem, úlohou" "ani tématem. {}".format(anode)) return False @classmethod def all_public_children(cls, anode): for ch in treelib.all_children(anode): if TNLData.public_above(ch): yield ch else: continue @classmethod def from_treenode(cls, anode, user, parent=None, index=None): if TNLData.public_above(anode) or user.has_perm('auth.org'): out = cls(anode,parent,index) else: raise PermissionDenied() if user.has_perm('auth.org'): enum_children = enumerate(treelib.all_children(anode)) else: enum_children = enumerate(TNLData.all_public_children(anode)) for (idx,ch) in enum_children: outitem = cls.from_treenode(ch, user, out, idx) out.children.append(outitem) out.add_edit_options() return out @classmethod def from_tnldata_list(cls, tnllist): """Vyrobíme virtuální TNL, který nemá obsah, ale má za potomky všechna zadaná TNLData""" result = cls(None) for idx, tnl in enumerate(tnllist): result.children.append(tnl) tnl.parent = result tnl.index = idx result.add_edit_options() return result @classmethod def filter_treenode(cls, treenode, predicate): tnll = cls._filter_treenode_recursive(treenode, predicate) # TreeNodeList List :-) return TNLData.from_tnldata_list(tnll) @classmethod def _filter_treenode_recursive(cls, treenode, predicate): if predicate(treenode): return [cls.from_treenode(treenode)] else: found = [] for tn in all_children(treenode): result = cls.filter_treenode(tn, predicate) # Result by v tuhle chvíli měl být seznam TNLDat odpovídající treenodům, jež matchnuly predikát. for tnl in result: found.append(tnl) return found def to_json(self): #self.node = anode #self.children = [] #self.parent = parent #self.tema_in_path = False #self.index = index out = {} out['node'] = self.sernode.data out['children'] = [n.to_json() for n in self.children] out['tema_in_path'] = self.tema_in_path out['index'] = self.index out['deletable'] = self.deletable out['editable_siblings'] = self.editable_siblings out['editable_children'] = self.editable_children out['text_only_subtree'] = self.text_only_subtree out['can_podvesit_za'] = self.can_podvesit_za out['can_podvesit_pod'] = self.can_podvesit_pred out['appendable_children'] = self.appendable_children out['appendable_siblings'] = self.appendable_siblings return out def __repr__(self): return("TNL({})".format(self.node)) class TreeNodeView(generic.DetailView): model = s.TreeNode template_name = 'seminar/treenode.html' def get_context_data(self,**kwargs): context = super().get_context_data(**kwargs) context['tnldata'] = TNLData.from_treenode(self.object,self.request.user) return context class TreeNodeJSONView(generic.DetailView): model = s.TreeNode def get(self,request,*args, **kwargs): self.object = self.get_object() data = TNLData.from_treenode(self.object,self.request.user).to_json() return JsonResponse(data) class TreeNodePridatView(generic.View): type_from_str = { 'rocnikNode': m.RocnikNode, 'cisloNode': m.CisloNode, 'castNode': m.CastNode, 'textNode': m.TextNode, 'temaVCisleNode': m.TemaVCisleNode, 'reseniNode': m.ReseniNode, 'ulohaZadaniNode': m.UlohaZadaniNode, 'ulohaVzorakNode': m.UlohaVzorakNode, 'pohadkaNode': m.PohadkaNode, 'orgText': m.OrgTextNode, } def post(self, request, *args, **kwargs): ######## FIXME: ROZEPSANE, NEFUNGUJE, DOPSAT !!!!!! ########### node = s.TreeNode.objects.get(pk=self.kwargs['pk']) kam = self.kwargs['kam'] co = self.kwargs['co'] typ = self.type_from_str[co] raise NotImplementedError('Neni to dopsane, dopis to!') if kam not in ('pred','syn','za'): raise ValidationError('Přidat lze pouze před nebo za node nebo jako syna') if co == m.TextNode: new_obj = m.Text() new_obj.save() elif co == m.CastNode: new_obj = m.CastNode() new_obj.nadpis = request.POST.get('pridat-castNode-{}-{}'.format(node.id,kam)) new_obj.save() elif co == m.ReseniNode: new_obj = m pass elif co == m.UlohaZadaniNode: pass elif co == m.UlohaReseniNode: pass else: new_obj = None if kam == 'pred': pass if kam == 'syn': if typ == m.TextNode: text_obj = m.Text() text_obj.save() node = treelib.create_child(node,typ,text=text_obj) else: node = treelib.create_child(node,typ) if kam == 'za': if typ == m.TextNode: text_obj = m.Text() text_obj.save() node = treelib.create_node_after(node,typ,text=text_obj) else: node = treelib.create_node_after(node,typ) return redirect(node.get_admin_url()) class TreeNodeSmazatView(generic.base.View): def post(self, request, *args, **kwargs): node = s.TreeNode.objects.get(pk=self.kwargs['pk']) if node.first_child: raise NotImplementedError('Mazání TreeNode se syny není zatím podporováno!') treelib.disconnect_node(node) node.delete() return redirect(request.headers.get('referer')) class TreeNodeOdvesitPrycView(generic.base.View): def post(self, request, *args, **kwargs): node = s.TreeNode.objects.get(pk=self.kwargs['pk']) treelib.disconnect_node(node) node.root = None node.save() return redirect(request.headers.get('referer')) class TreeNodePodvesitView(generic.base.View): def post(self, request, *args, **kwargs): node = s.TreeNode.objects.get(pk=self.kwargs['pk']) kam = self.kwargs['kam'] if kam == 'pred': treelib.lower_node(node) elif kam == 'za': raise NotImplementedError('Podvěsit za není zatím podporováno') return redirect(request.headers.get('referer')) class TreeNodeProhoditView(generic.base.View): def post(self, request, *args, **kwargs): node = s.TreeNode.objects.get(pk=self.kwargs['pk']) treelib.swap_succ(node) return redirect(request.headers.get('referer')) #FIXME ve formulari predat puvodni url a vratit redirect na ni class SirotcinecView(generic.ListView): model = s.TreeNode template_name = 'seminar/orphanage.html' def get_queryset(self): return s.TreeNode.objects.not_instance_of(s.RocnikNode).filter(root=None,prev=None,succ=None,father_of_first=None) # FIXME pouzit Django REST Framework class TextWebView(generic.DetailView): model = s.Text def get(self,request,*args, **kwargs): self.object = self.get_object() return JsonResponse(model_to_dict(self.object,exclude='do_cisla')) 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 = 'seminar/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() # problemy = Problem.objects.filter(cislo_zadani=nastaveni.aktualni_cislo).filter(stav = 'zadany') # ulohy = problemy.filter(typ = 'uloha').order_by('kod') # serialy = problemy.filter(typ = 'serial').order_by('kod') # jednorazove_problemy = [ulohy, serialy] # return render(request, 'seminar/zadani/AktualniZadani.html', # {'nastaveni': nastaveni, # 'jednorazove_problemy': jednorazove_problemy, # 'temata': verejna_temata(nastaveni.aktualni_rocnik), # '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, 'seminar/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, 'seminar/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, 'seminar/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, 'seminar/tematka/rozcestnik.html', {"tematka": tematka, "rocnik" : nastaveni.aktualni_rocnik().rocnik}) # #def ZadaniAktualniVysledkovkaView(request): # nastaveni = get_object_or_404(Nastaveni) # # Aktualni verejna vysledkovka # vysledkovka = vysledkovka_rocniku(nastaveni.aktualni_rocnik) # # kdyz neni verejna vysledkovka, tak zobraz starou # if not vysledkovka: # try: # minuly_rocnik = Rocnik.objects.get( # prvni_rok=(nastaveni.aktualni_rocnik.prvni_rok-1)) # vysledkovka = vysledkovka_rocniku(minuly_rocnik) # except ObjectDoesNotExist: # pass # # vysledkovka s neverejnyma vysledkama # vysledkovka_s_neverejnymi = vysledkovka_rocniku(nastaveni.aktualni_rocnik, jen_verejne=False) # return render( # request, # 'seminar/zadani/AktualniVysledkovka.html', # { # 'nastaveni': nastaveni, # 'vysledkovka': vysledkovka, # 'vysledkovka_s_neverejnymi': vysledkovka_s_neverejnymi, # } # ) ### Titulni strana def spravne_novinky(request): """ Vrátí správný QuerySet novinek, tedy ten, který daný uživatel smí vidět. Tj. Organizátorům všechny, ostatním jen veřejné """ user = request.user # Využíváme líné vyhodnocování QuerySetů qs = Novinky.objects.all() if not user.je_org: qs = qs.filter(zverejneno=True) return qs.order_by('-datum') 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') class TitulniStranaView(generic.ListView): template_name='seminar/titulnistrana.html' def get_queryset(self): return spravne_novinky(self.request)[:3] def get_context_data(self, **kwargs): context = super(TitulniStranaView, self).get_context_data(**kwargs) nastaveni = get_object_or_404(Nastaveni) deadline_soustredeni = (nastaveni.aktualni_cislo.datum_deadline_soustredeni, "soustredeni") preddeadline = (nastaveni.aktualni_cislo.datum_preddeadline, "preddeadline") deadline = (nastaveni.aktualni_cislo.datum_deadline, "deadline") try: nejblizsi_deadline = sorted(filter(lambda dl: dl[0] is not None and dl[0] >= date.today(), [deadline_soustredeni, preddeadline, deadline]))[0] if nejblizsi_deadline[0] == deadline_soustredeni[0]: nejblizsi_deadline = deadline_soustredeni except IndexError: nejblizsi_deadline = (None, None) # neni zadna aktualni deadline if nejblizsi_deadline[0] is not None: context['nejblizsi_deadline'] = datetime.combine(nejblizsi_deadline[0], datetime.max.time()) else: context['nejblizsi_deadline'] = None context['typ_deadline'] = nejblizsi_deadline[1] # Aktuální témata nazvy_a_odkazy_na_aktualni_temata = [] akt_temata = aktualni_temata(nastaveni.aktualni_rocnik) for tema in akt_temata: # FIXME: netuším, jestli funguje tema.verejne_url(), nemáme testdata na témátka - je to asi url vzhledem k ročníku nazvy_a_odkazy_na_aktualni_temata.append({'nazev':tema.nazev,'url':tema.verejne_url()}) context['aktualni_temata'] = nazvy_a_odkazy_na_aktualni_temata print(context) return context class StareNovinkyView(generic.ListView): template_name = 'seminar/stare_novinky.html' def get_queryset(self): return spravne_novinky(self.request) ### Co je M&M # Organizatori def aktivniOrganizatori(datum=timezone.now()): return Organizator.objects.exclude( organizuje_do__isnull=False, organizuje_do__lt=datum ).order_by('osoba__jmeno') class CojemamOrganizatoriView(generic.ListView): model = Organizator template_name = 'seminar/cojemam/organizatori.html' queryset = aktivniOrganizatori() def get_context_data(self, **kwargs): context = super(CojemamOrganizatoriView, self).get_context_data(**kwargs) context['aktivni'] = True return context class CojemamOrganizatoriStariView(generic.ListView): model = Organizator template_name = 'seminar/cojemam/organizatori.html' queryset = Organizator.objects.exclude( id__in=aktivniOrganizatori()).order_by('-organizuje_do') ### Archiv class ArchivView(generic.ListView): model = Rocnik template_name='seminar/archiv/cisla.html' def get_context_data(self, **kwargs): context = super(ArchivView, self).get_context_data(**kwargs) cisla = Cislo.objects.filter(poradi=1) urls ={} for i, c in enumerate(cisla): if c.titulka_nahled: urls[c.rocnik] = c.titulka_nahled.url else: urls[c.rocnik] = op.join(settings.MEDIA_URL, "cislo", "png", "default.png") context["object_list"] = urls return context ### Výsledky def sloupec_s_poradim(setrizene_body): """ Ze seznamu obsahujícího sestupně setřízené body řešitelů za daný ročník vytvoří seznam s pořadími (včetně 3.-5. a pak 2 volná místa atp.), podle toho, jak jdou za sebou ve výsledkovce. Parametr: setrizene_body (seznam integerů): sestupně setřízená čísla Výstup: sloupec_s_poradim (seznam stringů) """ # ze seznamu obsahujícího setřízené body spočítáme sloupec s pořadím aktualni_poradi = 1 sloupec_s_poradim = [] # seskupíme seznam všech bodů podle hodnot for index in range(0, len(setrizene_body)): # pokud je pořadí větší než číslo řádku, tak jsme vypsali větší rozsah a chceme # vypsat už jen prázdné místo, než dojdeme na správný řádek if (index + 1) < aktualni_poradi: sloupec_s_poradim.append("") continue velikost_skupiny = 0 # zjistíme počet po sobě jdoucích stejných hodnot while setrizene_body[index] == setrizene_body[index + velikost_skupiny]: velikost_skupiny = velikost_skupiny + 1 # na konci musíme ošetřit přetečení seznamu if (index + velikost_skupiny) > len(setrizene_body) - 1: break # pokud je velikost skupiny 1, vypíšu pořadí if velikost_skupiny == 1: sloupec_s_poradim.append("{}.".format(aktualni_poradi)) # pokud je skupina větší, vypíšu rozsah else: sloupec_s_poradim.append("{}.–{}.".format(aktualni_poradi, aktualni_poradi+velikost_skupiny-1)) # zvětšíme aktuální pořadí o tolik, kolik pozic bylo přeskočeno aktualni_poradi = aktualni_poradi + velikost_skupiny return sloupec_s_poradim def cisla_rocniku(rocnik, jen_verejne=True): """ Vrátí všechna čísla daného ročníku. Parametry: rocnik (Rocnik): ročník semináře jen_verejne (bool): zda se mají vrátit jen veřejná, nebo všechna čísla Vrátí: seznam objektů typu Cislo """ if jen_verejne: return rocnik.verejna_cisla() else: return rocnik.cisla.all() def hlavni_problem(problem): """ Pro daný problém vrátí jeho nejvyšší nadproblém.""" while not(problem.nadproblem == None): problem = problem.nadproblem return problem def hlavni_problemy_rocniku(rocnik, jen_verejne=True): """ Pro zadaný ročník vrátí hlavní problémy ročníku, tj. ty, které už nemají nadproblém.""" hlavni_problemy = [] for cislo in cisla_rocniku(rocnik, jen_verejne): for problem in hlavni_problemy_cisla(cislo): hlavni_problemy.append(problem) hlavni_problemy_set = set(hlavni_problemy) hlavni_problemy = list(hlavni_problemy_set) hlavni_problemy.sort(key=lambda k:k.kod_v_rocniku()) # setřídit podle pořadí return hlavni_problemy def hlavni_problemy_cisla(cislo): """ Vrátí seznam všech problémů s body v daném čísle, které již nemají nadproblém. """ hodnoceni = cislo.hodnoceni.select_related('problem', 'reseni').all() # hodnocení, která se vážou k danému číslu reseni = [h.reseni for h in hodnoceni] problemy = [h.problem for h in hodnoceni] problemy_set = set(problemy) # chceme každý problém unikátně, problemy = (list(problemy_set)) # převedení na množinu a zpět to zaručí # hlavní problémy čísla # (mají vlastní sloupeček ve výsledkovce, nemají nadproblém) hlavni_problemy = [] for p in problemy: hlavni_problemy.append(hlavni_problem(p)) # zunikátnění hlavni_problemy_set = set(hlavni_problemy) hlavni_problemy = list(hlavni_problemy_set) hlavni_problemy.sort(key=lambda k: k.kod_v_rocniku()) # setřídit podle t1, t2, c3, ... return hlavni_problemy def body_resitelu(resitele, za, odjakziva=True): """ Funkce počítající počty bodů pro zadané řešitele, buď odjakživa do daného ročníku/čísla anebo za daný ročník/číslo. Parametry: resitele (seznam obsahující položky typu Resitel): aktivní řešitelé za (Rocnik/Cislo): za co se mají počítat body (generování starších výsledkovek) odjakziva (bool): zda se mají počítat body odjakživa, nebo jen za číslo/ročník zadané v "za" Výstup: slovník (Resitel.id):body """ resitele_id = [r.id for r in resitele] # Zjistíme, typ objektu v parametru "za" if isinstance(za, Rocnik): cislo = None rocnik = za rok = rocnik.prvni_rok elif isinstance(za, Cislo): cislo = za rocnik = None rok = cislo.rocnik.prvni_rok else: assert True, "body_resitelu: za není ani číslo ani ročník." # Kvůli rychlosti používáme sčítáme body už v databázi, viz # https://docs.djangoproject.com/en/3.0/topics/db/aggregation/, # sekce Filtering on annotations (protože potřebujeme filtrovat výsledky # jen do nějakého data, abychom uměli správně nagenerovat výsledkovky i # za historická čísla. # Níže se vytváří dotaz na součet bodů za správně vyfiltrovaná hodnocení, # který se použije ve výsledném dotazu. if cislo and odjakziva: # Body se sčítají odjakživa do zadaného čísla. # Vyfiltrujeme všechna hodnocení, která jsou buď ze starších ročníků, # anebo ze stejného ročníku, jak je zadané číslo, tam ale sčítáme jen # pro čísla s pořadím nejvýše stejným, jako má zadané číslo. body_k_zapocteni = Sum('reseni__hodnoceni__body', filter=( Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok__lt=rok) | Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok=rok, reseni__hodnoceni__cislo_body__poradi__lte=cislo.poradi) )) elif cislo and not odjakziva: # Body se sčítají za dané číslo. body_k_zapocteni = Sum('reseni__hodnoceni__body', filter=( Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok=rok, reseni__hodnoceni__cislo_body__poradi__lte=cislo.poradi) )) elif rocnik and odjakziva: # Spočítáme body za starší ročníky až do zadaného včetně. body_k_zapocteni = Sum('reseni__hodnoceni__body', filter= Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok__lte=rok)) elif rocnik and not odjakziva: # Spočítáme body za daný ročník. body_k_zapocteni = Sum('reseni__hodnoceni__body', filter= Q(reseni__hodnoceni__cislo_body__rocnik=rocnik)) else: assert True, "body_resitelu: Neplatná kombinace za a odjakživa." # Následující řádek přidá ke každému řešiteli údaj ".body" se součtem jejich bodů resitele_s_body = Resitel.objects.filter(id__in=resitele_id).annotate( body=body_k_zapocteni) # Teď jen z QuerySetu řešitelů anotovaných body vygenerujeme slovník # indexovaný řešitelským id obsahující body. # Pokud jsou body None, nahradíme za 0. slovnik = {int(res.id) : (res.body if res.body else 0) for res in resitele_s_body} return slovnik class RadekVysledkovkyRocniku(object): """ Obsahuje věci, které se hodí vědět při konstruování výsledkovky. Umožňuje snazší práci v templatu (lepší, než seznam).""" def __init__(self, poradi, resitel, body_cisla_sezn, body_rocnik, body_odjakziva, rok): self.poradi = poradi self.resitel = resitel self.rocnik_resitele = resitel.rocnik(rok) self.body_rocnik = body_rocnik self.body_celkem_odjakziva = body_odjakziva self.body_cisla_sezn = body_cisla_sezn self.titul = resitel.get_titul(body_odjakziva) def setrid_resitele_a_body(slov_resitel_body): setrizeni_resitele_id = [dvojice[0] for dvojice in slov_resitel_body] setrizeni_resitele = [Resitel.objects.get(id=i) for i in setrizeni_resitele_id] setrizene_body = [dvojice[1] for dvojice in slov_resitel_body] return setrizeni_resitele_id, setrizeni_resitele, setrizene_body def vysledkovka_rocniku(rocnik, jen_verejne=True): """ Přebírá ročník (např. context["rocnik"]) a vrací výsledkovou listinu ve formě vhodné pro šablonu "seminar/vysledkovka_rocniku.html" """ ## TODO možná chytřeji vybírat aktivní řešitele # aktivní řešitelé - chceme letos něco poslal, TODO později vyfiltrujeme ty, kdo mají # u alespoň jedné hodnoty něco jiného než NULL aktivni_resitele = list(resi_v_rocniku(rocnik)) cisla = cisla_rocniku(rocnik, jen_verejne) body_cisla_slov = {} for cislo in cisla: # získáme body za číslo _, cislobody = secti_body_za_cislo(cislo, aktivni_resitele) body_cisla_slov[cislo.id] = cislobody # získáme body za ročník, seznam obsahuje dvojice (řešitel_id, body) setřízené sestupně resitel_rocnikbody_sezn = secti_body_za_rocnik(rocnik, aktivni_resitele) # setřídíme řešitele podle počtu bodů a získáme seznam s body od nejvyšších po nenižší setrizeni_resitele_id, setrizeni_resitele, setrizene_body = setrid_resitele_a_body(resitel_rocnikbody_sezn) poradi = sloupec_s_poradim(setrizene_body) # získáme body odjakživa resitel_odjakzivabody_slov = body_resitelu(aktivni_resitele, rocnik) # vytvoříme jednotlivé sloupce výsledkovky radky_vysledkovky = [] i = 0 for ar_id in setrizeni_resitele_id: # seznam počtu bodů daného řešitele pro jednotlivá čísla body_cisla_sezn = [] for cislo in cisla: body_cisla_sezn.append(body_cisla_slov[cislo.id][ar_id]) # vytáhneme informace pro daného řešitele radek = RadekVysledkovkyRocniku( poradi[i], # pořadí Resitel.objects.get(id=ar_id), # řešitel (z id) body_cisla_sezn, # seznam bodů za čísla setrizene_body[i], # body za ročník (spočítané výše s pořadím) resitel_odjakzivabody_slov[ar_id], # body odjakživa rocnik) # ročník semináře pro získání ročníku řešitele radky_vysledkovky.append(radek) i += 1 return radky_vysledkovky class RocnikView(generic.DetailView): model = Rocnik template_name = 'seminar/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() 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(RocnikView, self).get_context_data(**kwargs) # vysledkovka = True zajistí vykreslení, # zkontrolovat, kdy se má a nemá vykreslovat context['vysledkovka'] = True context['cisla_s_neverejnymi'] = cisla_rocniku(context["rocnik"], jen_verejne=False) context['cisla'] = cisla_rocniku(context["rocnik"]) context['radky_vysledkovky'] = vysledkovka_rocniku(context["rocnik"]) context['radky_vysledkovky_s_neverejnymi'] = vysledkovka_rocniku( context["rocnik"], jen_verejne=False) context['hlavni_problemy_v_rocniku'] = hlavni_problemy_rocniku(context["rocnik"]) context['hlavni_problemy_v_rocniku_s_neverejnymi'] = hlavni_problemy_rocniku(context["rocnik"], jen_verejne=False) return context 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 ['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 class RadekVysledkovkyCisla(object): """Obsahuje věci, které se hodí vědět při konstruování výsledkovky. Umožňuje snazší práci v templatu (lepší, než seznam).""" def __init__(self, poradi, resitel, body_problemy_sezn, body_cislo, body_rocnik, body_odjakziva, rok): self.resitel = resitel self.rocnik_resitele = resitel.rocnik(rok) self.body_cislo = body_cislo self.body_rocnik = body_rocnik self.body_celkem_odjakziva = body_odjakziva self.poradi = poradi self.body_problemy_sezn = body_problemy_sezn self.titul = resitel.get_titul(body_odjakziva) def pricti_body(slovnik, resitel, body): """ Přiřazuje danému řešiteli body do slovníku. """ # testujeme na None (""), pokud je to první řešení # daného řešitele, předěláme na 0 # (v dalším kroku přičteme reálný počet bodů), # rozlišujeme tím mezi 0 a neodevzdaným řešením if slovnik[resitel.id] == "": slovnik[resitel.id] = 0 slovnik[resitel.id] += body def secti_body_za_rocnik(za, aktivni_resitele): """ Spočítá body za ročník (celý nebo do daného čísla), setřídí je sestupně a vrátí jako seznam. Parametry: za (typu Rocnik nebo Cislo) spočítá za ročník, nebo za ročník až do daného čísla """ # spočítáme všem řešitelům jejich body za ročník (False => ne odjakživa) resitel_rocnikbody_slov = body_resitelu(aktivni_resitele, za, False) # zeptáme se na dvojice (řešitel, body) za ročník a setřídíme sestupně resitel_rocnikbody_sezn = sorted(resitel_rocnikbody_slov.items(), key = lambda x: x[1], reverse = True) return resitel_rocnikbody_sezn def secti_body_za_cislo(cislo, aktivni_resitele, hlavni_problemy=None): """ Spočítá u řešitelů body za číslo a za jednotlivé hlavní problémy (témata).""" # TODO setřídit hlavní problémy čísla podle id, ať jsou ve stejném pořadí pokaždé # pro každý hlavní problém zavedeme slovník s body za daný hlavní problém # pro jednotlivé řešitele (slovník slovníků hlavních problémů) if hlavni_problemy is None: hlavni_problemy = hlavni_problemy_cisla(cislo) def ne_clanek_ne_konfera(problem): return not(isinstance(problem.get_real_instance(), m.Clanek) or isinstance(problem.get_real_instance(), m.Konfera)) temata_a_spol = list(filter(ne_clanek_ne_konfera, hlavni_problemy)) def cosi(problem): return problem.id hlavni_problemy_slovnik = {} for hp in temata_a_spol: hlavni_problemy_slovnik[hp.id] = {} hlavni_problemy_slovnik[-1] = {} # zakládání prázdných záznamů pro řešitele cislobody = {} for ar in aktivni_resitele: # řešitele převedeme na řetězec pomocí unikátního id cislobody[ar.id] = "" for hp in temata_a_spol: slovnik = hlavni_problemy_slovnik[hp.id] slovnik[ar.id] = "" hlavni_problemy_slovnik[-1][ar.id] = "" # vezmeme všechna řešení s body do daného čísla reseni_do_cisla = Reseni.objects.prefetch_related('problem', 'resitele', 'hodnoceni_set').filter(hodnoceni__cislo_body=cislo) # projdeme všechna řešení do čísla a přičteme body každému řešiteli do celkových # bodů i do bodů za problém for reseni in reseni_do_cisla: # řešení může řešit více problémů for prob in list(reseni.problem.all()): nadproblem = hlavni_problem(prob) if ne_clanek_ne_konfera(nadproblem): nadproblem_slovnik = hlavni_problemy_slovnik[nadproblem.id] else: nadproblem_slovnik = hlavni_problemy_slovnik[-1] # a mít více hodnocení for hodn in list(reseni.hodnoceni_set.all()): body = hodn.body # a mít více řešitelů for resitel in list(reseni.resitele.all()): if resitel not in aktivni_resitele: print("Skipping {}".format(resitel.id)) continue pricti_body(cislobody, resitel, body) pricti_body(nadproblem_slovnik, resitel, body) return hlavni_problemy_slovnik, cislobody def vysledkovka_cisla(cislo, context=None): if context is None: context = {} hlavni_problemy = hlavni_problemy_cisla(cislo) ## TODO možná chytřeji vybírat aktivní řešitele # aktivní řešitelé - chceme letos něco poslal, TODO později vyfiltrujeme ty, kdo mají # u alespoň jedné hodnoty něco jiného než NULL aktivni_resitele = list(aktivniResitele(cislo)) # získáme body za číslo hlavni_problemy_slovnik, cislobody = secti_body_za_cislo(cislo, aktivni_resitele, hlavni_problemy) # získáme body za ročník, seznam obsahuje dvojice (řešitel_id, body) setřízené sestupně resitel_rocnikbody_sezn = secti_body_za_rocnik(cislo, aktivni_resitele) # získáme body odjakživa resitel_odjakzivabody_slov = body_resitelu(aktivni_resitele, cislo) # řešitelé setřídění podle bodů za číslo sestupně setrizeni_resitele_id = [dvojice[0] for dvojice in resitel_rocnikbody_sezn] setrizeni_resitele = [Resitel.objects.get(id=i) for i in setrizeni_resitele_id] # spočítáme pořadí řešitelů setrizeni_resitele_body = [dvojice[1] for dvojice in resitel_rocnikbody_sezn] poradi = sloupec_s_poradim(setrizeni_resitele_body) # vytvoříme jednotlivé sloupce výsledkovky radky_vysledkovky = [] i = 0 def ne_clanek_ne_konfera(problem): return not(isinstance(problem.get_real_instance(), m.Clanek) or isinstance(problem.get_real_instance(), m.Konfera)) temata_a_spol = list(filter(ne_clanek_ne_konfera, hlavni_problemy)) # def not_empty(value): # return value != '' # # je_nejake_ostatni = any(filter(not_empty, hlavni_problemy_slovnik[-1].values())) > 0 je_nejake_ostatni = len(hlavni_problemy) - len(temata_a_spol) > 0 for ar_id in setrizeni_resitele_id: # získáme seznam bodů za problémy pro daného řešitele problemy = [] for hp in temata_a_spol: problemy.append(hlavni_problemy_slovnik[hp.id][ar_id]) if je_nejake_ostatni: problemy.append(hlavni_problemy_slovnik[-1][ar_id]) # vytáhneme informace pro daného řešitele radek = RadekVysledkovkyCisla( poradi[i], # pořadí Resitel.objects.get(id=ar_id), # řešitel (z id) problemy, # seznam bodů za hlavní problémy čísla cislobody[ar_id], # body za číslo setrizeni_resitele_body[i], # body za ročník (spočítané výše s pořadím) resitel_odjakzivabody_slov[ar_id], # body odjakživa cislo.rocnik) # ročník semináře pro zjištění ročníku řešitele radky_vysledkovky.append(radek) i += 1 # vytahané informace předáváme do kontextu context['cislo'] = cislo context['radky_vysledkovky'] = radky_vysledkovky context['problemy'] = temata_a_spol context['ostatni'] = je_nejake_ostatni #context['v_cisle_zadane'] = TODO #context['resene_problemy'] = resene_problemy 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 = 'seminar/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() # vrátíme context (aktuálně obsahuje jen věci ohledně výsledkovky return vysledkovka_cisla(cislo, context) class ArchivTemataView(generic.ListView): model = Problem template_name = 'seminar/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 = 'seminar/archiv/odmeny.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) fromcislo = Cislo.objects.get(rocnik=self.kwargs.get('frocnik'), poradi=self.kwargs.get('fcislo')) tocislo = Cislo.objects.get(rocnik=self.kwargs.get('trocnik'), poradi=self.kwargs.get('tcislo')) resitele = aktivniResitele(tocislo) frombody = body_resitelu(resitele, fromcislo) tobody = body_resitelu(resitele, tocislo) outlist = [] for (aid, tbody) in tobody.items(): fbody = frombody.get(aid,0) resitel = Resitel.objects.get(pk=aid) ftitul = resitel.get_titul(fbody) ttitul = resitel.get_titul(tbody) if ftitul != ttitul: outlist.append({'jmeno': resitel.osoba.plne_jmeno(), 'ftitul': ftitul, 'ttitul': ttitul}) context['zmeny'] = outlist return context ### Generovani vysledkovky class CisloVysledkovkaView(CisloView): """View vytvořené pro stránku zobrazující výsledkovku čísla v TeXu.""" model = Cislo template_name = 'seminar/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 class RocnikVysledkovkaView(RocnikView): """ View vytvořené pro stránku zobrazující výsledkovku ročníku v TeXu.""" model = Rocnik template_name = 'seminar/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 = Cislo.objects.get(poradi=cislo, rocnik__rocnik=rocnik) return obalkyView(request, aktivniResitele(realne_cislo)) def obalkyView(request, resitele): tex = render(request,'seminar/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 def oldObalkovaniView(request, rocnik, cislo): rocnik = Rocnik.objects.get(rocnik=rocnik) cislo = Cislo.objects.get(rocnik=rocnik, cislo=cislo) reseni = ( Reseni.objects.filter(cislo_body=cislo) .order_by( 'resitel__prijmeni', 'resitel__jmeno', 'problem__typ', 'problem__kod' ) ) problemy = sorted(set(r.problem for r in reseni), key=lambda p: (p.typ, p.kod)) return render( request, 'seminar/archiv/cislo_obalkovani.html', {'cislo': cislo, 'problemy': problemy, 'reseni': reseni} ) ### Orgostránky class OrgoRozcestnikView(TemplateView): ''' Zobrazí organizátorský rozcestník.''' template_name = 'seminar/orgorozcestnik.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['posledni_soustredeni'] = Soustredeni.objects.order_by('-datum_konce').first() nastaveni = Nastaveni.objects.first() aktualni_rocnik = nastaveni.aktualni_rocnik context['posledni_cislo_url'] = nastaveni.aktualni_cislo.verejne_url() # TODO možná chceme odkazovat na právě rozpracované číslo, a ne to poslední vydané # pokud nechceme haluzit kód (= poradi) dalšího čísla, bude asi potřeba jít # přes treenody (a dát si přitom pozor na MezicisloNode) u = self.request.user os = s.Osoba.objects.get(user=u) organizator = s.Organizator.objects.get(osoba=os) temata_garant = s.Tema.objects.filter(garant=organizator, rocnik=aktualni_rocnik) #FIXME: přidat opravovatel, stav='STAV_ZADANY' ulohy_garant = s.Uloha.objects.filter(garant=organizator, cislo_zadani__rocnik=aktualni_rocnik) clanky_garant = s.Clanek.objects.filter(garant=organizator, cislo__rocnik=aktualni_rocnik) context['temata'] = temata_garant context['ulohy'] = ulohy_garant context['clanky'] = clanky_garant context['organizator'] = organizator return context #content_type = 'text/plain; charset=UTF8' #XXX ### Tituly def TitulyView(request, rocnik, cislo): """ View pro stažení makra titulů v TeXu.""" rocnik_obj = Rocnik.objects.get(rocnik = rocnik) resitele = Resitel.objects.filter(rok_maturity__gte = rocnik_obj.prvni_rok) cislo_obj = Cislo.objects.get(rocnik = rocnik_obj, poradi = cislo) asciijmena = [] jmenovci = False # detekuje, zda jsou dva řešitelé jmenovci (modulo nabodeníčka), # pokud ano, vrátí se jako true slovnik_s_body = body_resitelu(resitele, rocnik_obj) 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, 'seminar/archiv/tituly.tex', {'resitele': resitele,'jmenovci':jmenovci},content_type="text/plain") ### Soustredeni class SoustredeniListView(generic.ListView): model = Soustredeni template_name = 'seminar/soustredeni/seznam_soustredeni.html' def soustredeniObalkyView(request,soustredeni): soustredeni = get_object_or_404(Soustredeni,id = soustredeni) return obalkyView(request,soustredeni.ucastnici.all()) class SoustredeniUcastniciBaseView(generic.ListView): model = Soustredeni_Ucastnici def get_queryset(self): soustredeni = get_object_or_404( Soustredeni, pk=self.kwargs["soustredeni"] ) return Soustredeni_Ucastnici.objects.filter( soustredeni=soustredeni).select_related('resitel') class SoustredeniMailyUcastnikuView(SoustredeniUcastniciBaseView): """ Seznam e-mailů řešitelů oddělených čárkami. """ model = Soustredeni_Ucastnici template_name = 'seminar/soustredeni/maily_ucastniku.txt' class SoustredeniUcastniciView(SoustredeniUcastniciBaseView): """ HTML tabulka účastníků pro tisk. """ model = Soustredeni_Ucastnici template_name = 'seminar/soustredeni/seznam_ucastniku.html' def soustredeniUcastniciExportView(request,soustredeni): soustredeni = get_object_or_404(Soustredeni,id = soustredeni) ucastnici = Resitel.objects.filter(soustredeni=soustredeni) 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 ### Č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 = 'seminar/clanky/resitelske_clanky.html' # FIXME: QuerySet není pole! def get_queryset(self): clanky = Clanek.objects.filter(stav=Problem.STAV_ZADANY).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 = 'seminar/clanky/organizatorske_clanky.html' # queryset = Problem.objects.filter(stav=Problem.STAV_ZADANY).select_related('cislo_zadani__rocnik').order_by('-cislo_zadani__rocnik__rocnik', 'kod') ### Status def StavDatabazeView(request): # nastaveni = Nastaveni.objects.get() problemy = utils.seznam_problemu() muzi = Resitel.objects.filter(osoba__pohlavi_muz=True) zeny = Resitel.objects.filter(osoba__pohlavi_muz=False) return render(request, 'seminar/stav_databaze.html', { # 'nastaveni': nastaveni, 'problemy': problemy, 'resitele': Resitel.objects.all(), 'muzi': muzi, 'zeny': zeny, 'jmena_muzu': utils.histogram([r.osoba.jmeno for r in muzi]), 'jmena_zen': utils.histogram([r.osoba.jmeno for r in zeny]), }) class ResitelView(LoginRequiredMixin,generic.DetailView): model = Resitel template_name = 'seminar/profil/resitel.html' def get_object(self, queryset=None): print(self.request.user) return Resitel.objects.get(osoba__user=self.request.user) ### Formulare # pro přidání políčka do formuláře je potřeba # - mít v modelu tu položku, kterou chci upravovat # - přidat do views (prihlaskaView, resitelEditView) # - přidat do forms # - includovat do html class AddSolutionView(LoginRequiredMixin, FormView): template_name = 'seminar/org/vloz_reseni.html' form_class = f.VlozReseniForm success_url = '/' class NahrajReseniView(LoginRequiredMixin, CreateView): model = s.Reseni template_name = 'seminar/profil/nahraj_reseni.html' form_class = f.NahrajReseniForm success_url = '/' def get_context_data(self,**kwargs): data = super().get_context_data(**kwargs) if self.request.POST: data['prilohy'] = f.ReseniSPrilohamiFormSet(self.request.POST,self.request.FILES) else: data['prilohy'] = f.ReseniSPrilohamiFormSet() return data # FIXME prepsat tak, aby form_valid se volalo jen tehdy, kdyz je form i formset validni # Inspirace: https://stackoverflow.com/questions/41599809/using-a-django-filefield-in-an-inline-formset def form_valid(self,form): context = self.get_context_data() prilohy = context['prilohy'] if not prilohy.is_valid(): return super().form_invalid(form) with transaction.atomic(): self.object = form.save() self.object.resitele.add(Resitel.objects.get(osoba__user = self.request.user)) self.object.cas_doruceni = timezone.now() self.object.forma = s.Reseni.FORMA_UPLOAD self.object.save() prilohy.instance = self.object prilohy.save() return HttpResponseRedirect(self.get_success_url()) def prihlaska_log_gdpr_safe(logger, gdpr_logger, msg, form_data): msg = "{}, form_hash:{}".format(msg,hash(frozenset(form_data.items))) logger.warn(msg) gdpr_logger.warn(msg+", form:{}".format(form_data)) from django.forms.models import model_to_dict def resitelEditView(request): err_logger = logging.getLogger('seminar.prihlaska.problem') ## Načtení objektů Osoba a Resitel patřících k aktuálně přihlášenému uživateli u = request.user osoba_edit = Osoba.objects.get(user=u) if hasattr(osoba_edit,'resitel'): resitel_edit = osoba_edit.resitel else: resitel_edit = None user_edit = osoba_edit.user ## Vytvoření slovníku, kterým předvyplním formulář prefill_1=model_to_dict(user_edit) if resitel_edit: prefill_2=model_to_dict(resitel_edit) prefill_1.update(prefill_2) prefill_3=model_to_dict(osoba_edit) prefill_1.update(prefill_3) form = ProfileEditForm(initial=prefill_1) ## Změna údajů a jejich uložení if request.method == 'POST': form = ProfileEditForm(request.POST) if form.is_valid(): ## Změny v osobě fcd = form.cleaned_data osoba_edit.jmeno = fcd['jmeno'] osoba_edit.prijmeni = fcd['prijmeni'] osoba_edit.pohlavi_muz = fcd['pohlavi_muz'] osoba_edit.email = fcd['email'] osoba_edit.telefon = fcd['telefon'] osoba_edit.ulice = fcd['ulice'] osoba_edit.mesto = fcd['mesto'] osoba_edit.psc = fcd['psc'] ## Změny v osobě s podmínkami if fcd.get('spam',False): osoba_edit.datum_souhlasu_zasilani = date.today() if fcd.get('stat','') in ('CZ','SK'): osoba_edit.stat = fcd['stat'] else: ## Neznámá země msg = "Unknown country {}".format(fcd['stat_text']) if resitel_edit: ## Změny v řešiteli resitel_edit.skola = fcd['skola'] resitel_edit.rok_maturity = fcd['rok_maturity'] resitel_edit.zasilat = fcd['zasilat'] resitel_edit.zasilat_cislo_emailem = fcd['zasilat_cislo_emailem'] if fcd.get('skola'): resitel_edit.skola = fcd['skola'] else: # Unknown school - log it msg = "Unknown school {}, {}".format(fcd['skola_nazev'],fcd['skola_adresa']) resitel_edit.save() osoba_edit.save() return formularOKView(request) else: ## Stránka před odeslaním formuláře = předvyplněný formulář return render(request, 'seminar/profil/edit.html', {'form': form}) def prihlaskaView(request): generic_logger = logging.getLogger('seminar.prihlaska') err_logger = logging.getLogger('seminar.prihlaska.problem') form_logger = logging.getLogger('seminar.prihlaska.form') if request.method == 'POST': form = PrihlaskaForm(request.POST) # TODO vyresit, co se bude v jakych situacich zobrazovat if form.is_valid(): generic_logger.info("Form valid") fcd = form.cleaned_data form_hash = hash(frozenset(fcd.items())) form_logger.info(str(fcd) + str(form_hash)) # TODO možná logovat jinak with transaction.atomic(): u = User.objects.create_user( username=fcd['username'], password=fcd['password'], email = fcd['email']) u.save() resitel_perm = Permission.objects.filter(codename__exact='resitel').first() u.user_permissions.add(resitel_perm) o = Osoba( jmeno = fcd['jmeno'], prijmeni = fcd['prijmeni'], pohlavi_muz = fcd['pohlavi_muz'], email = fcd['email'], telefon = fcd.get('telefon',''), datum_narozeni = fcd.get('datum_narozeni',None), datum_souhlasu_udaje = date.today(), datum_registrace = date.today(), ulice = fcd.get('ulice',''), mesto = fcd.get('mesto',''), psc = fcd.get('psc',''), poznamka = str(fcd) ) if fcd.get('spam',False): o.datum_souhlasu_zasilani = date.today() if fcd.get('stat','') in ('CZ','SK'): o.stat = fcd['stat'] else: # Unknown country - log it msg = "Unknown country {}".format(fcd['stat_text']) err_logger.warn(msg + str(form_hash)) o.save() o.user = u o.save() r = Resitel( rok_maturity = fcd['rok_maturity'], zasilat = fcd['zasilat'], zasilat_cislo_emailem = fcd['zasilat_cislo_emailem'] ) r.save() r.osoba = o if fcd.get('skola'): r.skola = fcd['skola'] else: # Unknown school - log it msg = "Unknown school {}, {}".format(fcd['skola_nazev'],fcd['skola_adresa']) err_logger.warn(msg + str(form_hash)) r.save() return formularOKView(request) # if a GET (or any other method) we'll create a blank form else: form = PrihlaskaForm() return render(request, 'seminar/profil/prihlaska.html', {'form': form}) # FIXME: Tohle asi vlastně vůbec nepatří do aplikace 'seminar' class LoginView(auth_views.LoginView): # Jen vezmeme vestavěný a dáme mu vhodný template a přesměrovací URL template_name = 'seminar/profil/login.html' # Přesměrovací URL má být v kontextu: def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) ctx['next'] = reverse('profil') return ctx class LogoutView(auth_views.LogoutView): # Jen vezmeme vestavěný a dáme mu vhodný template a přesměrovací URL template_name = 'seminar/profil/logout.html' # Pavel: Vůbec nevím, proč to s _lazy funguje, ale bez toho to bylo rozbité. next_page = reverse_lazy('titulni_strana') class PasswordResetView(auth_views.PasswordResetView): """ Chci resetovat heslo. """ #template_name = 'seminar/password_reset.html' # TODO: vlastní email_template_name a subject_template_name a html_email_template_name success_url = reverse_lazy('reset_password_done') from_email = 'login@mam.mff.cuni.cz' class PasswordResetDoneView(auth_views.PasswordResetDoneView): """ Poslali jsme e-mail (pokud bylo kam)). """ #template_name = 'seminar/password_reset_done.html' pass class PasswordResetConfirmView(auth_views.PasswordResetConfirmView): """ Vymysli si heslo. """ #template_name = 'seminar/password_confirm_done.html' success_url = reverse_lazy('reset_password_complete') class PasswordResetCompleteView(auth_views.PasswordResetCompleteView): """ Heslo se asi změnilo.""" #template_name = 'seminar/password_complete_done.html' pass class PasswordChangeView(auth_views.PasswordChangeView): #template_name = 'seminar/password_change.html' success_url = reverse_lazy('titulni_strana') class VueTestView(generic.TemplateView): template_name = 'seminar/vuetest.html' class NahrajObrazekKTreeNoduView(LoginRequiredMixin, CreateView): model = s.Obrazek form_class = f.NahrajObrazekKTreeNoduForm def get_initial(self): initial = super().get_initial() initial['na_web'] = self.request.FILES['upload'] return initial def form_valid(self,form): print(self.request.headers) print(self.request.headers['Textid']) print(form.instance) print(form) self.object = form.save(commit=False) print(self.object.na_web) self.object.text = m.Text.objects.get(pk=int(self.request.headers['Textid'])) self.object.save() return JsonResponse({"url":self.object.na_web.url}) # Jen hloupé rozhazovátko def profilView(request): user = request.user if user.has_perm('auth.org'): return OrgoRozcestnikView.as_view()(request) if user.has_perm('auth.resitel'): return ResitelView.as_view()(request) else: return LoginView.as_view()(request) # Interní, nemá se nikdy objevit v urls (jinak to účastníci vytrolí) def formularOKView(request): template_name = 'seminar/formular_ok.html' odkazy = [ # (Text, odkaz) ('Vrátit se na titulní stránku', reverse('titulni_strana')), ('Zobrazit aktuální zadání', reverse('seminar_aktualni_zadani')), ] context = { 'odkazy': odkazy, } return render(request, template_name, context) #------------------ Jak řešit - možná má být udělané úplně jinak class JakResitView(generic.ListView): template_name = 'seminar/jak-resit.html' def get_queryset(self): return None