diff --git a/aesop/views.py b/aesop/views.py index 5fd49cbc..1ff6c7ee 100644 --- a/aesop/views.py +++ b/aesop/views.py @@ -8,7 +8,7 @@ from django.utils.encoding import force_str from .utils import default_ovvpfile from seminar.models import Rocnik, Soustredeni from vysledkovky import utils -from seminar.utils import aktivniResitele +from tvorba.utils import aktivniResitele class ExportIndexView(generic.View): def get(self, request): diff --git a/api/tests/test_skola_autocomplete.py b/api/tests/test_skola_autocomplete.py index 36df97e8..75019983 100644 --- a/api/tests/test_skola_autocomplete.py +++ b/api/tests/test_skola_autocomplete.py @@ -1,8 +1,7 @@ from django.test import TestCase, tag from django.urls import reverse import seminar.models as m -import seminar.views as v -from seminar.utils import sync_skoly +from personalni.utils import sync_skoly @tag('stejny-model-na-produkci') class OrgSkolyAutocompleteTestCase(TestCase): diff --git a/api/urls.py b/api/urls.py index 9ff38424..be58d3f9 100644 --- a/api/urls.py +++ b/api/urls.py @@ -1,6 +1,6 @@ from django.urls import path from . import views -from seminar.utils import org_required +from personalni.utils import org_required urlpatterns = [ # Export škol diff --git a/docs/zapisy/2021-12-06-testovani_dokumentace_codereview.md b/docs/zapisy/2021-12-06-testovani_dokumentace_codereview.md index 224ea529..c2cfc1b5 100644 --- a/docs/zapisy/2021-12-06-testovani_dokumentace_codereview.md +++ b/docs/zapisy/2021-12-06-testovani_dokumentace_codereview.md @@ -116,7 +116,7 @@ Aktuálně: Jakýsi coding style zhruba existuje, není popsaný, šíří se li - Nesmí být striktně vynucovaný - Musel by být hodně nastavitelný - Nechceme mít kód plný `#NOQA: WTF42` -- Nejspíš vždycky bude mít false positives (`seminar.utils.roman_numerals`) i false negatives (`seminar.models.tvorba.Cislo.posli_cislo_mailem`) +- Nejspíš vždycky bude mít false positives (`tvorba.utils.roman_numerals`) i false negatives (`seminar.models.tvorba.Cislo.posli_cislo_mailem`) - Možná dobrý sluha, ale určitě špatný pán (also: špatná zkušenost ☺) - __Důsledek:__ Hrozí, že těch falešných varování bude moc, čímž to ztratí smysl úplně - Potenciálně by šlo aplikovat jen lokálně na změny? diff --git a/galerie/urls.py b/galerie/urls.py index 32824248..28b43a22 100644 --- a/galerie/urls.py +++ b/galerie/urls.py @@ -1,5 +1,5 @@ from django.urls import path -from seminar.utils import org_required +from personalni.utils import org_required from . import views urlpatterns = [ diff --git a/korektury/urls.py b/korektury/urls.py index dcd1d965..cf45ea8f 100644 --- a/korektury/urls.py +++ b/korektury/urls.py @@ -1,5 +1,5 @@ from django.urls import path -from seminar.utils import org_required +from personalni.utils import org_required from . import views urlpatterns = [ diff --git a/mamweb/settings_common.py b/mamweb/settings_common.py index f737be1e..078c3d4d 100644 --- a/mamweb/settings_common.py +++ b/mamweb/settings_common.py @@ -54,7 +54,7 @@ SESSION_EXPIRE_AT_BROWSER_CLOSE = True DOBA_ODHLASENI_PRI_ZASKRTNUTI_NEODHLASOVAT = 365 * 24 * 3600 # rok # View pro chybu s CSRF tokenem (např. se sušenkami) -CSRF_FAILURE_VIEW = 'various.views.csrf_error' +CSRF_FAILURE_VIEW = 'various.views.csrf.csrf_error' # Modules configuration @@ -131,6 +131,7 @@ INSTALLED_APPS = ( # MaMweb 'mamweb', 'seminar', + 'tvorba', 'galerie', 'korektury', 'prednasky', diff --git a/mamweb/urls.py b/mamweb/urls.py index 4b870fec..4152ae80 100644 --- a/mamweb/urls.py +++ b/mamweb/urls.py @@ -17,8 +17,8 @@ urlpatterns = [ path('admin/', admin.site.urls), # NOQA path('ckeditor/', include('ckeditor_uploader.urls')), - # Seminarova aplikace (ma vlastni podadresare) - path('', include('seminar.urls')), + # Tvorba = ročníky, čísla, problémy atd. (ma vlastni podadresare) + path('', include('tvorba.urls')), # Odevzdavatko (ma vlastni podadresare) path('', include('odevzdavatko.urls')), @@ -39,6 +39,9 @@ urlpatterns = [ # Autentizační aplikace (ma vlastni podadresare) path('', include('various.autentizace.urls')), + # Novinková aplikace (ma vlastni podadresare) + path('', include('novinky.urls')), + # Api (ma vlastni podadresare) (autocomplete apod.) path('', include('api.urls')), @@ -48,6 +51,9 @@ urlpatterns = [ # Aesop (ma vlastni podadresare) path('', include('aesop.urls')), + # Various = co se nevešlo jinam + path('', include('various.urls')), + # REST API # path('api/', include(router.urls)), diff --git a/novinky/__init__.py b/novinky/__init__.py index e69de29b..26e6a606 100644 --- a/novinky/__init__.py +++ b/novinky/__init__.py @@ -0,0 +1,3 @@ +""" +Obsahuje vše okolo novinek (zpráv „Co je nového?“ na titulní straně). +""" diff --git a/seminar/templates/seminar/novinky.html b/novinky/templates/novinky/novinky.html similarity index 100% rename from seminar/templates/seminar/novinky.html rename to novinky/templates/novinky/novinky.html diff --git a/seminar/templates/seminar/stare_novinky.html b/novinky/templates/novinky/stare_novinky.html similarity index 78% rename from seminar/templates/seminar/stare_novinky.html rename to novinky/templates/novinky/stare_novinky.html index c300eaae..faf4c972 100644 --- a/seminar/templates/seminar/stare_novinky.html +++ b/novinky/templates/novinky/stare_novinky.html @@ -8,6 +8,6 @@ {% endblock %} - {% include 'seminar/novinky.html' %} + {% include 'novinky/novinky.html' %} {% endblock %} diff --git a/novinky/testutils.py b/novinky/testutils.py new file mode 100644 index 00000000..f1ef6adc --- /dev/null +++ b/novinky/testutils.py @@ -0,0 +1,27 @@ +import logging + +from .models import Novinky + +logger = logging.getLogger(__name__) + + +def gen_novinky(rnd, organizatori): + logger.info('Generuji novinky...') + + jake = ["zábavné", "veselé", "dobrodružné", "skvělé"] + co = ["soustředění", "Fyziklání", "víkendové setkání"] + kde = ["na Šumavě", "v Praze", "u Plzně", "na Marsu"] + kdy = ["Zítra bude", "10. 10. 2020 bude", "V prosinci bude", "V létě bude"] + + for i in range(5): + text_novinky = " ".join([ + rnd.choice(kdy), rnd.choice(kde), + rnd.choice(jake), rnd.choice(co), + ]) + novinka = Novinky.objects.create( + id=i, autor=rnd.choice(organizatori), + text=(text_novinky+", těšíme se na vás!"), + zverejneno=rnd.choice([True, False]), + ) + novinka.save() + return diff --git a/novinky/urls.py b/novinky/urls.py new file mode 100644 index 00000000..6a3be15b --- /dev/null +++ b/novinky/urls.py @@ -0,0 +1,7 @@ +from django.urls import path + +from .views import StareNovinkyView + +urlpatterns = [ + path('stare-novinky/', StareNovinkyView.as_view(), name='stare_novinky'), +] diff --git a/novinky/views.py b/novinky/views.py index e69de29b..2cb20433 100644 --- a/novinky/views.py +++ b/novinky/views.py @@ -0,0 +1,23 @@ +from django.views import generic + +from .models import Novinky + + +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') + + +class StareNovinkyView(generic.ListView): + template_name = 'novinky/stare_novinky.html' + + def get_queryset(self): + return spravne_novinky(self.request) diff --git a/odevzdavatko/templates/odevzdavatko/tabulka.html b/odevzdavatko/templates/odevzdavatko/tabulka.html index 8b79b2f7..41f91b14 100644 --- a/odevzdavatko/templates/odevzdavatko/tabulka.html +++ b/odevzdavatko/templates/odevzdavatko/tabulka.html @@ -1,6 +1,5 @@ {% extends "base.html" %} -{% load utils %} {# Možná by mohlo být někde výš v hierarchii templatů... #} {% load barvy_reseni %} {% block content %} diff --git a/odevzdavatko/testutils.py b/odevzdavatko/testutils.py new file mode 100644 index 00000000..1f382438 --- /dev/null +++ b/odevzdavatko/testutils.py @@ -0,0 +1,40 @@ +import datetime +import random + +from seminar.models.odevzdavatko import Reseni, Hodnoceni + + +def gen_reseni_ulohy(rnd, cisla, uloha, pocet_resitelu, poradi_cisla, resitele_cisla, resitele): + pocet_reseni = rnd.randint(pocet_resitelu//4, pocet_resitelu * 4) + # generujeme náhodný počet řešení vzhledem k počtu řešitelů čísla + for _ in range(pocet_reseni): + #print("Generuji {}-té řešení".format(reseni)) + if rnd.randint(1, 10) == 1: + # cca desetina řešení od více řešitelů + res_vyber = rnd.sample(resitele_cisla, rnd.randint(2, 5)) + else: + res_vyber = rnd.sample(resitele_cisla, 1) + if resitele[0] in res_vyber: # speciální řešitel, který nemá žádné body + res_vyber.remove(resitele[0]) + + # Vytvoření řešení. + if uloha.cislo_zadani.zlomovy_deadline_pro_papirove_cislo() is not None: + # combine, abychom dostali plný čas a ne jen datum + cas_doruceni = uloha.cislo_zadani.deadline_v_cisle.first().deadline - datetime.timedelta(days=random.randint(0, 40)) - datetime.timedelta(minutes=random.randint(0, 60*24)) + # astimezone, protože jinak vyhazuje warning o nenastavené TZ + res = Reseni.objects.create(forma=rnd.choice(Reseni.FORMA_CHOICES)[0], cas_doruceni=cas_doruceni.astimezone(datetime.timezone.utc)) + else: + res = Reseni.objects.create(forma=rnd.choice(Reseni.FORMA_CHOICES)[0]) + # Problém a řešitele přiřadíme později, ManyToManyField + # se nedá vyplnit v create(). + res.resitele.set(res_vyber) + res.save() + + # Vytvoření hodnocení. + hod = Hodnoceni.objects.create( + body=rnd.randint(0, uloha.max_body), + cislo_body=cisla[poradi_cisla - 1], + reseni=res, + problem=uloha + ) + return diff --git a/odevzdavatko/urls.py b/odevzdavatko/urls.py index e41b9c14..d4c2a092 100644 --- a/odevzdavatko/urls.py +++ b/odevzdavatko/urls.py @@ -1,7 +1,7 @@ from django.urls import path -from seminar.utils import org_required, resitel_required, viewMethodSwitch, \ - resitel_or_org_required +from personalni.utils import org_required, resitel_required, resitel_or_org_required +from various.views.generic import viewMethodSwitch from . import views urlpatterns = [ diff --git a/odevzdavatko/utils.py b/odevzdavatko/utils.py new file mode 100644 index 00000000..4157de4b --- /dev/null +++ b/odevzdavatko/utils.py @@ -0,0 +1,11 @@ +import decimal + + +def vzorecek_na_prepocet(body, resitelu): + """ Vzoreček na přepočet plných bodů na parciálni, když má řešení více řešitelů. """ + return body * 3 / (resitelu + 2) + + +def inverze_vzorecku_na_prepocet(body: decimal.Decimal, resitelu) -> decimal.Decimal: + """ Vzoreček na přepočet parciálních bodů na plné, když má řešení více řešitelů. """ + return round(body * (resitelu + 2) / 3, 1) diff --git a/odevzdavatko/views.py b/odevzdavatko/views.py index e5de47c2..cbe9019e 100644 --- a/odevzdavatko/views.py +++ b/odevzdavatko/views.py @@ -20,8 +20,8 @@ import logging import seminar.models as m from . import forms as f from .forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm -from seminar.utils import resi_v_rocniku -from seminar.views import formularOKView +from tvorba.utils import resi_v_rocniku +from various.views.pomocne import formularOKView logger = logging.getLogger(__name__) diff --git a/seminar/static/seminar/lisak.pdf b/personalni/static/personalni/lisak.pdf similarity index 100% rename from seminar/static/seminar/lisak.pdf rename to personalni/static/personalni/lisak.pdf diff --git a/seminar/static/images/no-photo.png b/personalni/static/personalni/no-photo.png similarity index 100% rename from seminar/static/images/no-photo.png rename to personalni/static/personalni/no-photo.png diff --git a/seminar/templates/seminar/archiv/obalky.tex b/personalni/templates/personalni/obalky.tex similarity index 100% rename from seminar/templates/seminar/archiv/obalky.tex rename to personalni/templates/personalni/obalky.tex diff --git a/seminar/templates/seminar/cojemam/organizatori.html b/personalni/templates/personalni/organizatori.html similarity index 94% rename from seminar/templates/seminar/cojemam/organizatori.html rename to personalni/templates/personalni/organizatori.html index a9257a01..88d16994 100644 --- a/seminar/templates/seminar/cojemam/organizatori.html +++ b/personalni/templates/personalni/organizatori.html @@ -46,7 +46,7 @@ {% if org.osoba.foto %} {{org.osoba.jmeno}} {{org.osoba.prijmeni}} {% else %} {# pokud osoba nemá fotku, zobrazuje se defaultní obrázek #} - {% load static %} {{org.osoba.jmeno}} {{org.osoba.prijmeni}} + {% load static %} {{org.osoba.jmeno}} {{org.osoba.prijmeni}} {% endif %} diff --git a/personalni/testutils.py b/personalni/testutils.py new file mode 100644 index 00000000..2b2d6bcc --- /dev/null +++ b/personalni/testutils.py @@ -0,0 +1,235 @@ +import datetime +import logging +import unidecode + +from django.contrib.auth.models import Permission +from django.contrib.auth.models import Group +import django.contrib.auth + +from .models import Osoba, Skola, Organizator, Resitel, Prijemce + +logger = logging.getLogger(__name__) + +User = django.contrib.auth.get_user_model() + +zlinska = None # tohle bude speciální škola, které později dodáme kontaktní osobu + + +# testuje unikátnost vygenerovaného jména +def __unikatni_jmeno(osoby, jmeno, prijmeni): + for os in osoby: + if os.jmeno == jmeno and os.prijmeni == prijmeni: + return 0 + else: + return 1 + + +def gen_osoby(rnd, size): + logger.info('Generuji osoby (size={})...'.format(size)) + + jmena_m = ['Aleš', 'Tomáš', 'Martin', 'Jakub', 'Petr', 'Lukáš', 'Cyril', 'Pavel Karel'] + jmena_f = ['Eva', 'Karolína', 'Zuzana', 'Sylvie', 'Iva', 'Jana', 'Marie', 'Marta Iva', 'Shu Shan'] + prijmeni_m = ['Novotný', 'Svoboda', 'Pecha', 'Kořen', 'Holan', 'Uhlíř', 'Chytráček', 'Pokora', 'Koch', 'Szegedy', 'Rudý', "von Neumann", "d'Este"] + prijmeni_f = ['Novotná', 'Svobodová', 'Machová', 'Zelená', 'Yu-Xin', 'Mlsná', 'Dubná', 'Mrkvová', 'Suchá', 'Lovelace', 'Holcová', 'Rui', "Nováčková Tydlitátová"] + prezdivky = ['Kaki', 'Hurdur', 'Maracuja', 'Bobbo', "", "", "", "", "", "", "", 'Riki', 'Sapa', "", '', '---', 'Koko'] + domain = ['example.com', 'dolujeme.eu', 'mff.cuni.cz', 'strcprstskrzkrk.cz', 'british.co.uk', 'splachni.to', 'haha.org'] + seznam_ulic = ['Krátká', 'Vlhká', 'Jungmanova', '17. listopadu', '4. října', 'Roztocká', 'Forstova', 'Generála Františka Janouška', 'Náměstí Války', 'Svratecké náměstí', 'Zelená lhota', 'Z Plynu', 'K Jezeru', 'U Kocourkova', 'Uštěpačná', 'Ostrorepská', 'Zubří'] + seznam_mest = ['Praha', 'Brno', 'Ostrava', 'Horní Jelení', 'Dolní Zábrdovice', 'Prdelkov', 'Stará myslivna', 'Kocourkov', 'Šalingrad', 'Medvědí hora', 'Basilej', 'Unterschiedlich', 'Old York', 'Lancastershire', 'Vóloďháza'] + + osoby = [] + # 30 je náhodná konstanta, size je použité na víc místech a + # říká, jak velká asi chceme testovací data + for i in range(30 * size): + pohlavi_idx = rnd.randint(0, 2) # 2 = nebinární + osloveni = [Osoba.OSLOVENI_MUZSKE, Osoba.OSLOVENI_ZENSKE, Osoba.OSLOVENI_ZADNE][pohlavi_idx] + jmeno = rnd.choice([jmena_m, jmena_f, jmena_m + jmena_f][pohlavi_idx]) + prijmeni = rnd.choice([prijmeni_m, prijmeni_f, prijmeni_m + prijmeni_f][pohlavi_idx]) + if pohlavi_idx == 2: logger.debug(f'Testdata: nebinární osoba: {jmeno} {prijmeni}.') + pokusy = 0 + max_pokusy = 120*size + while not __unikatni_jmeno and pokusy < max_pokusy: + # pokud jméno a příjmení není unikátní, zkoušíme generovat nová + # do daného limitu (abychom se nezacyklili do nekonečna při málo jménech a příjmeních + # ze kterých se generuje) + jmeno = rnd.choice([jmena_m, jmena_f, jmena_m + jmena_f][pohlavi_idx]) + prijmeni = rnd.choice([prijmeni_m, prijmeni_f, prijmeni_m + prijmeni_f][pohlavi_idx]) + pokusy += 1 + if pokusy >= max_pokusy: + print("Chyba, na danou velikost testovacích dat příliš málo možných jmen a příjmení") + exit() + prezdivka = rnd.choice(prezdivky) + email = "@".join([unidecode.unidecode(jmeno), rnd.choice(domain)]) + telefon = "".join([str(rnd.choice([k for k in range(10)])) for _ in range(9)]) + narozeni = datetime.date( + rnd.randint(1980, datetime.datetime.now().year), + rnd.randint(1, 12), + rnd.randint(1, 28) + ) + ulic = rnd.choice(seznam_ulic) + cp = rnd.randint(1, 99) + ulice = " ".join([ulic, str(cp)]) + mesto = rnd.choice(seznam_mest) + psc = "".join([str(rnd.choice([k for k in range(10)])) for _ in range(5)]) + + osoby.append(Osoba.objects.create( + jmeno=jmeno, prijmeni=prijmeni, + prezdivka=prezdivka, osloveni=osloveni, + email=email, telefon=telefon, + datum_narozeni=narozeni, + ulice=ulice, mesto=mesto, psc=psc, + datum_registrace=datetime.date( + rnd.randint(2019, 2029), rnd.randint(1, 12), rnd.randint(1, 28) + ) + )) + + # TODO pridat foto male a velke. Jak? + # Pavel tvrdí, že to necháme a přidáme až do adminu + + return osoby + + +def gen_skoly(): # TODO někdy to přepsat, aby jich bylo více + logger.info('Generuji školy...') + + skoly = [] + prvnizs = Skola.objects.create( + mesto='Praha', stat='CZ', psc='101 00', + ulice='Krátká 5', nazev='První ZŠ', je_zs=True, je_ss=False, + ) + skoly.append(prvnizs) + skoly.append(Skola.objects.create( + mesto='Praha', stat='CZ', psc='101 00', + ulice='Krátká 5', nazev='První SŠ', je_zs=False, je_ss=True, + )) + skoly.append(Skola.objects.create( + mesto='Praha', stat='CZ', psc='102 00', + ulice='Dlouhá 5', nazev='Druhá SŠ', je_zs=False, je_ss=True, + )) + skoly.append(Skola.objects.create( + mesto='Praha', stat='CZ', psc='103 00', + ulice='Široká 3', nazev='Třetí SŠ a ZŠ', je_zs=True, je_ss=True, + )) + skoly.append(Skola.objects.create( + mesto='Ostrava', stat='CZ', psc='700 00', + ulice='Hluboká 42', nazev='Hutní gympl', je_zs=False, je_ss=True, + )) + skoly.append(Skola.objects.create( + mesto='Humenné', stat='SK', psc='012 34', + ulice='Pltká 1', nazev='Sredná škuola', je_zs=False, je_ss=True, + )) + global zlinska + zlinska = Skola.objects.create( + mesto='Zlín', stat='CZ', psc='76001', + ulice='náměstí T.G. Masaryka 2734-9', + nazev='Gymnázium a Střední jazyková škola s právem SJZ', + kratky_nazev="GaSJŠspSJZ", je_zs=True, je_ss=True, + ) + skoly.append(zlinska) + return skoly + + +def gen_resitele(rnd, osoby, skoly): + logger.info('Generuji řešitele...') + + resitele = [] + x = 0 + resitel_perm = Permission.objects.filter(codename__exact='resitel').first() + resitel_group = Group.objects.filter(name__exact='resitel').first() + for os in osoby: + rand = rnd.randint(0, 8) + if not (rand % 8 == 0): + if not os.user: + if x: + user = User.objects.create_user( + username='r'+str(x), email=os.email, password='r', + ) + else: + user = User.objects.create_user( + username='r', email=os.email, password='r', + ) + x += 1 + os.user = user + os.save() + os.user.user_permissions.add(resitel_perm) + os.user.groups.add(resitel_group) + resitele.append(Resitel.objects.create( + osoba=os, skola=rnd.choice(skoly), + rok_maturity=os.datum_narozeni.year + rnd.randint(18, 21), + zasilat=rnd.choice(Resitel.ZASILAT_CHOICES)[0], + )) + return resitele + + +def gen_prijemci(rnd, osoby, kolik=10): + logger.info('Generuji příjemce (kolik={})...'.format(kolik)) + prijemci = [] + for i in rnd.sample(osoby, kolik): + prijemci.append(Prijemce.objects.create(osoba=i)) + + global zlinska + if zlinska is not None: + zlinska.kontaktni_osoba=rnd.choice(osoby) + zlinska.save() + + return prijemci + + +def gen_organizatori(rnd, osoby, last_rocnik): + logger.info('Generuji organizátory...') + organizatori = [] + + + seznam_konicku = ["vařím", "jezdím na kole", "řeším diferenciální rovnice", "koukám z okna", "tancuji", "programuji", "jezdím vlakem", "nedělám nic"] + seznam_oboru = ["matematiku", "matematiku", "matematiku", "fyziku", "literaturu", "informatiku", "informatiku", "běhání dokolečka"] + + x = 0 + org_perm = Permission.objects.filter(codename__exact='org').first() + org_group = Group.objects.filter(name__exact='org').first() + for os in osoby: + rand = rnd.randint(0, 8) + if rand % 8 == 0: + pusobnost = rnd.randint(1, last_rocnik) + od = datetime.datetime( + year=1993 + pusobnost, + month=rnd.randint(1, 12), + day=rnd.randint(1, 28), + tzinfo=datetime.timezone.utc, + ) + do = datetime.datetime( + year=od.year + rnd.randint(1, 6), + month=rnd.randint(1, 12), + day=rnd.randint(1, 28), + tzinfo=datetime.timezone.utc, + ) + # aktualni organizatori jeste nemaji vyplnene organizuje_do + + # popis orga + konicek1 = rnd.choice(seznam_konicku) + konicek2 = rnd.choice(seznam_konicku) + obor = rnd.choice(seznam_oboru) + popis_orga = "Ve volném čase " + konicek1 + " a také " + konicek2 + ". Studuji " + obor + " a moc mě to baví." + + if do.year > datetime.datetime.now().year: + do = None + if not os.user: + if x: + user = User.objects.create_user( + username='o'+str(x), email=os.email, password='o', + ) + else: + user = User.objects.create_user( + username='o', email=os.email, password='o', + ) + x += 1 + os.user = user + os.save() + os.user.user_permissions.add(org_perm) + os.user.groups.add(org_group) + os.user.is_staff = True + os.user.save() + organizatori.append(Organizator.objects.create( + osoba=os, + organizuje_od=od, organizuje_do=do, + strucny_popis_organizatora=popis_orga, + )) + return organizatori diff --git a/personalni/urls.py b/personalni/urls.py index 73a6f720..8abbb434 100644 --- a/personalni/urls.py +++ b/personalni/urls.py @@ -1,7 +1,7 @@ from django.urls import path from django.contrib.auth.decorators import login_required from . import views -from seminar.utils import org_required +from personalni.utils import org_required urlpatterns = [ path( @@ -21,4 +21,16 @@ urlpatterns = [ # Obecný view na profil -- orgům dá rozcestník, řešitelům jejich stránku path('profil/', views.profilView, name='profil'), + # Seznam organizátorů + path( + 'o-nas/organizatori/', + views.CojemamOrganizatoriView.as_view(), + name='organizatori' + ), + path( + 'o-nas/organizatori/organizovali/', + views.CojemamOrganizatoriStariView.as_view(), + name='stari_organizatori' + ), + ] diff --git a/personalni/utils.py b/personalni/utils.py index 0701d66a..4aac1e28 100644 --- a/personalni/utils.py +++ b/personalni/utils.py @@ -2,10 +2,183 @@ import seminar.models as m from various.utils import bez_diakritiky_translate import re -def normalizuj_jmeno(o: m.Osoba) -> str: +from django.contrib.auth import get_user_model +from django.contrib.auth.decorators import permission_required, user_passes_test +from django.contrib.auth.models import AnonymousUser +from django.db import transaction + +import seminar.models as m +import soustredeni.models + +from .models import Osoba, Organizator, Skola, Resitel, Prijemce + + +org_required = permission_required('auth.org') +resitel_required = permission_required('auth.resitel') + + +# inspirováno django.contrib.auth.decorators permission_required +def check_perms(user): + if user.has_perms(('auth.resitel',)): + return True + if user.has_perms(('auth.org',)): + return True + return False + + +resitel_or_org_required = user_passes_test(check_perms) + +User = get_user_model() +# Není to úplně hezké, ale budeme doufat, že to je funkční... +User.je_org = property(lambda self: self.has_perm('auth.org')) +User.je_resitel = property(lambda self: self.has_perm('auth.resitel')) +AnonymousUser.je_org = False +AnonymousUser.je_resitel = False + +def normalizuj_jmeno(o: Osoba) -> str: # FIXME: Možná není potřeba vázat na model? cele_jmeno = f'{o.jmeno} {o.prijmeni}' cele_jmeno = cele_jmeno.translate(bez_diakritiky_translate) cele_jmeno = re.sub(r'[^a-zA-Z- ]', '', cele_jmeno) return cele_jmeno + +def sync_skoly(base_url): + """Stáhne všechny školy z mamwebu na adrese a uloží je do databáze""" + from django.urls import reverse + full_url = base_url.rstrip('/') + reverse('export_skoly') + import requests + from django.core import serializers + json = requests.get(full_url, stream=True).content + for skola in serializers.deserialize('json', json): + skola.save() + +@transaction.atomic +def merge_resitele(cilovy, zdrojovy): + """Spojí dva řešitelské objekty do cílového. + + Pojmenování "zdrojový" je silně nepřiléhající, ale co už…""" + + # Postup: + # Sjednotit / upravit informace cílového řešitele + print('Upravuji data modelu') + fieldy_shoda = ['skola', 'rok_maturity', 'zasilat', 'zasilat_cislo_emailem', 'zasilat_cislo_papirove'] + + for f in fieldy_shoda: + zf = getattr(zdrojovy, f) + cf = getattr(cilovy, f) + if cf == zf: + print(f' Údaj {f} je shodný ({zf})') + else: + if zf is None: + print(f' Údaj {f} je pouze v cílovém, používám') + continue + if cf is None: + setattr(cilovy, f, zf) + cilovy.poznamka += f'\nDEBUG: Merge: doplnéný údaj {f} ze zdrojového: {zf}' + print(f" Přiřazuji {f} ze zdrojového: {zf}") + continue + # Jsou fakt různé… + # FIXME: chybí možnost na vlastní úpravu… + verdikt = input(f"\n\n Údaj {f} se u řešitele {cilovy} ({cilovy.id}) liší:\n Zdrojový: {zf}\n Cílový: {cf}\n Který použít, [z]drojový, [c]ílový? ") + verdikt = verdikt[0].casefold() + if verdikt == 'z': + setattr(cilovy, f, zf) + cilovy.poznamka += f'\nDEBUG: Merge: pro {f} použit údaj {zf} (zdrojový), nepoužit {cf} (cílový)' + elif verdikt == 'c': + cilovy.poznamka += f'\nDEBUG: Merge: pro {f} použit údaj {cf} (cílový), nepoužit {zf} (zdrojový)' + else: raise ValueError('Špatná odpověď, řešitel pravděpodobně neuložen') + # poznámku chceme nezahodit… + cilovy.poznamka += f'\nDEBUG: Merge: Původní poznámka: {zdrojovy.poznamka}' + print(f' Výsledný řešitel: {cilovy.__dict__}, ukládám') + cilovy.save() + + + # Přepojit všechny vazby ze zdrojového na cílového + print('Přepojuji vazby') + # Vazby: Škola (hotovo), Řešení_Řešitelé, Konfery_Účastníci, Soustředění_Účastníci, Osoba (vyřeší se později, nejde přepojit) + ct = m.Reseni_Resitele.objects.filter(resitele=zdrojovy).update(resitele=cilovy) + print(f' Přepojeno {ct} řešení') + ct = soustredeni.models.Konfery_Ucastnici.objects.filter(resitel=zdrojovy).update(resitel=cilovy) + print(f' Přepojeno {ct} konfer') + ct = soustredeni.models.Soustredeni_Ucastnici.objects.filter(resitel=zdrojovy).update(resitel=cilovy) + print(f' Přepojeno {ct} sousů') + + # Teď by na zdrojovém řešiteli nemělo nic viset, smazat ho, pamatujíce si jeho Osobu + zdrosoba = zdrojovy.osoba + print(f'Mažu zdrojového řešitele {zdrojovy.__dict__}') + zdrojovy.delete() + # Spojit osoby (separátní funkce). + merge_osoby(cilovy.osoba, zdrosoba) + + input("Potvrdit transakci řešitelů (^C pro zrušení) ") + +@transaction.atomic +def merge_osoby(cilova, zdrojova): + """ Spojí dvě osoby do cílové + + Nehlídá omezení typu "max 1 řešitel na osobu", to by měla hlídat databáze (OneToOneField).""" + # Sjednocení dat + print('Sjednocuji data osob') + # ID, User neřešíme, poznámku vyřešíme separátně. + fieldy = ['datum_narozeni', 'datum_registrace', 'datum_souhlasu_udaje', + 'datum_souhlasu_zasilani', 'email', 'foto', 'jmeno', 'mesto', + 'osloveni', 'prezdivka', 'prijmeni', 'psc', 'stat', 'telefon', 'ulice'] + for f in fieldy: + zf = getattr(zdrojova, f) + cf = getattr(cilova, f) + if cf == zf: + print(f' Údaj {f} je shodný ({zf})') + else: + if zf is None: + print(f' Údaj {f} je pouze v cílové, používám') + continue + if cf is None: + setattr(cilova, f, zf) + cilova.poznamka += f'\nDEBUG: Merge: doplnéný údaj {f} ze zdrojové: {zf}' + print(f" Přiřazuji {f} ze zdrojové: {zf}") + continue + # Jsou fakt různé… + # FIXME: chybí možnost na vlastní úpravu… + verdikt = input(f"\n\n Údaj {f} se u osoby {cilova} ({cilova.id}) liší:\n Zdrojový: {zf}\n Cílový: {cf}\n Který použít, [z]drojový, [c]ílový? ") + verdikt = verdikt[0].casefold() + if verdikt == 'z': + setattr(cilova, f, zf) + cilova.poznamka += f'\nDEBUG: Merge: pro {f} použit údaj {zf} (zdrojová), nepoužit {cf} (cílová)' + elif verdikt == 'c': + cilova.poznamka += f'\nDEBUG: Merge: pro {f} použit údaj {cf} (cílová), nepoužit {zf} (zdrojová)' + else: raise ValueError('Špatná odpověď, řešitel pravděpodobně neuložen') + # poznámku chceme nezahodit… + cilova.poznamka += f'\nDEBUG: Merge: Původní poznámka: {zdrojova.poznamka}' + print(f' Výsledná osoba: {cilova.__dict__}, ukládám') + cilova.save() + + # Vazby: Řešitel, User, Příjemce, Organizátor, Škola.kontaktní_osoba + print('Přepojuji vazby') + ct = Skola.objects.filter(kontaktni_osoba=zdrojova).update(kontaktni_osoba=cilova) + print(f' Přepojeno {ct} kontaktních osob') + # Ostatní vazby vyřeší OneToOneFieldy, ale někdy nemusí existovat… + ct = Resitel.objects.filter(osoba=zdrojova).update(osoba=cilova) + print(f' Přepojeno {ct} řešitelů') + ct = Prijemce.objects.filter(osoba=zdrojova).update(osoba=cilova) + print(f' Přepojeno {ct} příjemců') + ct = Organizator.objects.filter(osoba=zdrojova).update(osoba=cilova) + print(f' Přepojeno {ct} organizátorů') + # Uživatelé vedou opačným směrem, radši chceme zkontrolovat, že jsou různí ručně: + if zdrojova.user != cilova.user: + # Jeden z nich může být nenastavený… + if zdrojova.user is None: + print('Uživatel je již v cílové osobě') + elif cilova.user is None: + print('Používám uživatele zdrojové osoby') + cilova.user = zdrojova.user + # Teď nemůžeme uložit, protože kolize uživatelů. Ukládat cílovou budeme až po smazání zdrojové. + else: raise ValueError('Osoby mají obě uživatele, radši padám') + + # Uložení a mazání + print(f'Mažu zdrojovou osobu {zdrojova.__dict__}') + zdrojova.delete() + print(f'Ukládám cílovou osobu {cilova.__dict__}') + cilova.save() + + input("Potvrdit transakci osob (^C pro zrušení) ") diff --git a/personalni/views.py b/personalni/views.py index c2712b30..12768c34 100644 --- a/personalni/views.py +++ b/personalni/views.py @@ -1,3 +1,8 @@ +import tempfile +import subprocess +import shutil +import http + from django.shortcuts import render from django.urls import reverse from django.views import generic @@ -6,8 +11,10 @@ from django.views.decorators.debug import sensitive_post_parameters from django.views.generic.base import TemplateView from django.contrib.auth.models import User, Permission, Group, AnonymousUser from django.contrib.auth.mixins import LoginRequiredMixin +from django.contrib.staticfiles.finders import find from django.db import transaction from django.http import HttpResponse +from django.utils import timezone import seminar.models as s import seminar.models as m @@ -17,12 +24,65 @@ from datetime import date import logging import csv -from seminar.views import formularOKView +from various.views.pomocne import formularOKView from various.autentizace.views import LoginView from various.autentizace.utils import posli_reset_hesla from django.forms.models import model_to_dict +from .models import Organizator + + +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 = 'personalni/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 = 'personalni/organizatori.html' + queryset = Organizator.objects.exclude( + id__in=aktivniOrganizatori() + ).order_by('-organizuje_do') + + +def obalkyView(request, resitele): + if len(resitele) == 0: + return HttpResponse( + render(request, 'universal.html', { + 'title': 'Není pro koho vyrobit obálky.', + 'text': 'Právě ses pokusil/a vygenerovat obálky pro prázdnou množinu lidí. Můžeš to zkusit změnit, případně se zeptej webařů :-)', + }), + status=http.HTTPStatus.NOT_FOUND, + ) + + tex = render(request, 'personalni/obalky.tex', { + 'resitele': resitele + }).content + + with tempfile.TemporaryDirectory() as tempdir: + with open(tempdir+"/obalky.tex", "w") as texfile: + texfile.write(tex.decode()) + shutil.copy(find('personalni/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') + return response + class OrgoRozcestnikView(TemplateView): """ Zobrazí organizátorský rozcestník.""" diff --git a/prednasky/urls.py b/prednasky/urls.py index 6b455163..eecc45ad 100644 --- a/prednasky/urls.py +++ b/prednasky/urls.py @@ -1,5 +1,5 @@ from django.urls import path -from seminar.utils import org_required, resitel_or_org_required +from personalni.utils import org_required, resitel_or_org_required from . import views urlpatterns = [ diff --git a/seminar/models/odevzdavatko.py b/seminar/models/odevzdavatko.py index b0dec663..0c106df7 100644 --- a/seminar/models/odevzdavatko.py +++ b/seminar/models/odevzdavatko.py @@ -13,7 +13,7 @@ from seminar.models import tvorba as am from seminar.models import treenode as tm from seminar.models import base as bm -from seminar.utils import vzorecek_na_prepocet, inverze_vzorecku_na_prepocet +from odevzdavatko.utils import vzorecek_na_prepocet, inverze_vzorecku_na_prepocet from personalni.models import Resitel diff --git a/seminar/models/tvorba.py b/seminar/models/tvorba.py index 36157c96..c11a3861 100644 --- a/seminar/models/tvorba.py +++ b/seminar/models/tvorba.py @@ -23,7 +23,7 @@ from taggit.managers import TaggableManager from reversion import revisions as reversion -from seminar.utils import roman +from tvorba.utils import roman, aktivniResitele from treenode import treelib from unidecode import unidecode # Používám pro získání ID odkazu (ještě je to někde po někom zakomentované) @@ -31,7 +31,6 @@ from unidecode import unidecode # Používám pro získání ID odkazu (ještě from polymorphic.models import PolymorphicModel from django.core.mail import EmailMessage -from seminar.utils import aktivniResitele from personalni.models import Prijemce, Organizator diff --git a/seminar/templates/seminar/jakresit/jak-resit.html b/seminar/templates/seminar/jakresit/jak-resit.html deleted file mode 100644 index fd278c68..00000000 --- a/seminar/templates/seminar/jakresit/jak-resit.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends 'base.html' %} - -{% load humanize %} -{% load static %} - - -{% block content %} - -
- -{% include 'seminar/jakresit/jakresit_1.svg' %} -{% include 'seminar/jakresit/jakresit_2.svg' %} -{% include 'seminar/jakresit/jakresit_3.svg' %} - -
-{% endblock %} diff --git a/seminar/templatetags/utils.py b/seminar/templatetags/utils.py deleted file mode 100644 index ca400050..00000000 --- a/seminar/templatetags/utils.py +++ /dev/null @@ -1,19 +0,0 @@ -from django import template -from django.utils.safestring import mark_safe -from datetime import datetime, timedelta -from mamweb.settings import TIME_ZONE -import logging -register = template.Library() - -logger = logging.getLogger(__name__) - -@register.filter(name='kratke_datum', expects_localtime=True) -def kratke_datum(dt): - # None dává None, ne-datum dává False, aby se daly použít filtry typu "default". - if dt is None: - return None - if not isinstance(dt, datetime): - logger.warning(f"Špatné volání filtru {__name__}: {dt}") - return False - out = f'' - return mark_safe(out) diff --git a/seminar/testutils.py b/seminar/testutils.py deleted file mode 100644 index be7f3677..00000000 --- a/seminar/testutils.py +++ /dev/null @@ -1,910 +0,0 @@ -import datetime - -from django.contrib.auth.models import Permission -from django.contrib.auth.models import Group -import random -import lorem -import django.contrib.auth -from django.db import transaction -import unidecode -import logging - -from korektury.testutils import create_test_pdf -from seminar.models import Skola, Resitel, Rocnik, Cislo, Deadline, Problem, Reseni, PrilohaReseni, Nastaveni, Soustredeni, Soustredeni_Ucastnici, Soustredeni_Organizatori, Osoba, Organizator, Prijemce, Tema, Uloha, Konfera, TextNode, UlohaVzorakNode, RocnikNode, CisloNode, TemaVCisleNode, Text, Hodnoceni, UlohaZadaniNode, Novinky, TreeNode -import seminar.models as m - -from django.contrib.flatpages.models import FlatPage -from django.contrib.sites.models import Site -from treenode.treelib import all_children, insert_last_child, all_children_of_type, create_node_after - - -User = django.contrib.auth.get_user_model() -zlinska = None # tohle bude speciální škola, které později dodáme kontaktní osobu - -logger = logging.getLogger(__name__) - -# testuje unikátnost vygenerovaného jména -def __unikatni_jmeno(osoby, jmeno, prijmeni): - for os in osoby: - if os.jmeno == jmeno and os.prijmeni == prijmeni: - return 0 - else: return 1 - -def gen_osoby(rnd, size): - logger.info('Generuji osoby (size={})...'.format(size)) - - jmena_m = ['Aleš', 'Tomáš', 'Martin', 'Jakub', 'Petr', 'Lukáš', 'Cyril', 'Pavel Karel'] - jmena_f = ['Eva', 'Karolína', 'Zuzana', 'Sylvie', 'Iva', 'Jana', 'Marie', - 'Marta Iva', 'Shu Shan'] - prijmeni_m = ['Novotný', 'Svoboda', 'Pecha', 'Kořen', 'Holan', 'Uhlíř', 'Chytráček', - 'Pokora', 'Koch', 'Szegedy', 'Rudý', "von Neumann", "d'Este"] - prijmeni_f = ['Novotná', 'Svobodová', 'Machová', 'Zelená', 'Yu-Xin', 'Mlsná', 'Dubná', - 'Mrkvová', 'Suchá', 'Lovelace', 'Holcová', 'Rui', "Nováčková Tydlitátová"] - prezdivky = ['Kaki', 'Hurdur', 'Maracuja', 'Bobbo', "", "", "", "", "", - "", "", 'Riki', 'Sapa', "", '', '---', 'Koko'] - domain = ['example.com', 'dolujeme.eu', 'mff.cuni.cz', 'strcprstskrzkrk.cz', - 'british.co.uk', 'splachni.to', 'haha.org'] - seznam_ulic = ['Krátká', 'Vlhká', 'Jungmanova', '17. listopadu', '4. října', 'Roztocká', - 'Forstova', 'Generála Františka Janouška', 'Náměstí Války', - 'Svratecké náměstí', 'Zelená lhota', 'Z Plynu', 'K Jezeru', 'U Kocourkova', - 'Uštěpačná', 'Ostrorepská', 'Zubří'] - seznam_mest = ['Praha', 'Brno', 'Ostrava', 'Horní Jelení', 'Dolní Zábrdovice', 'Prdelkov', - 'Stará myslivna', 'Kocourkov', 'Šalingrad', 'Medvědí hora', 'Basilej', - 'Unterschiedlich', 'Old York', 'Lancastershire', 'Vóloďháza'] - - osoby = [] - # 30 je náhodná konstanta, size je použité na víc místech a - # říká, jak velká asi chceme testovací data - for i in range(30 * size): - pohlavi_idx = rnd.randint(0,2) # 2 = nebinární - osloveni = [Osoba.OSLOVENI_MUZSKE, Osoba.OSLOVENI_ZENSKE, Osoba.OSLOVENI_ZADNE][pohlavi_idx] - jmeno = rnd.choice([jmena_m, jmena_f, jmena_m + jmena_f][pohlavi_idx]) - prijmeni = rnd.choice([prijmeni_m, prijmeni_f, prijmeni_m + prijmeni_f][pohlavi_idx]) - if pohlavi_idx == 2: logger.debug(f'Testdata: nebinární osoba: {jmeno} {prijmeni}.') - pokusy = 0 - max_pokusy = 120*size - while (not __unikatni_jmeno and pokusy < max_pokusy): - # pokud jméno a příjmení není unikátní, zkoušíme generovat nová - # do daného limitu (abychom se nezacyklili do nekonečna při málo jménech a příjmeních - # ze kterých se generuje) - jmeno = rnd.choice([jmena_m, jmena_f, jmena_m + jmena_f][pohlavi_idx]) - prijmeni = rnd.choice([prijmeni_m, prijmeni_f, prijmeni_m + prijmeni_f][pohlavi_idx]) - pokusy = pokusy + 1 - if pokusy >= max_pokusy: - print("Chyba, na danou velikost testovacích dat příliš málo možných" - " jmen a příjmení") - exit() - prezdivka = rnd.choice(prezdivky) - email = "@".join([unidecode.unidecode(jmeno), rnd.choice(domain)]) - telefon = "".join([str(rnd.choice([k for k in range(10)])) for i in range(9)]) - narozeni = datetime.date(rnd.randint(1980, datetime.datetime.now().year), - rnd.randint(1, 12), rnd.randint(1, 28)) - ulic = rnd.choice(seznam_ulic) - cp = rnd.randint(1, 99) - ulice = " ".join([ulic, str(cp)]) - mesto = rnd.choice(seznam_mest) - psc = "".join([str(rnd.choice([k for k in range(10)])) for i in range(5)]) - - osoby.append(Osoba.objects.create(jmeno = jmeno, prijmeni = prijmeni, - prezdivka = prezdivka, osloveni = osloveni, email = email, - telefon = telefon, datum_narozeni = narozeni, ulice = ulice, - mesto = mesto, psc = psc, - datum_registrace = datetime.date(rnd.randint(2019, 2029), - rnd.randint(1, 12), rnd.randint(1, 28)))) - #TODO pridat foto male a velke. Jak? - # Pavel tvrdí, že to necháme a přidáme až do adminu - - return osoby - - - -def gen_skoly(): #TODO někdy to přepsat, aby jich bylo více - logger.info('Generuji školy...') - - skoly = [] - prvnizs = Skola.objects.create(mesto='Praha', stat='CZ', psc='101 00', - ulice='Krátká 5', nazev='První ZŠ', je_zs=True, je_ss=False) - skoly.append(prvnizs) - skoly.append(Skola.objects.create(mesto='Praha', stat='CZ', psc='101 00', - ulice='Krátká 5', nazev='První SŠ', je_zs=False, je_ss=True)) - skoly.append(Skola.objects.create(mesto='Praha', stat='CZ', psc='102 00', - ulice='Dlouhá 5', nazev='Druhá SŠ', je_zs=False, je_ss=True)) - skoly.append(Skola.objects.create(mesto='Praha', stat='CZ', psc='103 00', - ulice='Široká 3', nazev='Třetí SŠ a ZŠ', je_zs=True, je_ss=True)) - skoly.append(Skola.objects.create(mesto='Ostrava', stat='CZ', psc='700 00', - ulice='Hluboká 42', nazev='Hutní gympl', je_zs=False, je_ss=True)) - skoly.append(Skola.objects.create(mesto='Humenné', stat='SK', psc='012 34', - ulice='Pltká 1', nazev='Sredná škuola', je_zs=False, je_ss=True)) - global zlinska - zlinska = Skola.objects.create(mesto = 'Zlín', stat='CZ', psc='76001', - ulice='náměstí T.G. Masaryka 2734-9', - nazev='Gymnázium a Střední jazyková škola s právem SJZ', - kratky_nazev="GaSJŠspSJZ", je_zs=True, je_ss=True) - skoly.append(zlinska) - return skoly - -def gen_resitele(rnd, osoby, skoly): - logger.info('Generuji řešitele...') - - resitele = [] - x = 0 - resitel_perm = Permission.objects.filter(codename__exact='resitel').first() - resitel_group = Group.objects.filter(name__exact='resitel').first() - for os in osoby: - rand = rnd.randint(0, 8) - if not (rand % 8 == 0): - if not os.user: - if x: - user = User.objects.create_user(username='r'+str(x), email=os.email, password='r') - else: - user = User.objects.create_user(username='r', email=os.email, password='r') - x += 1 - os.user = user - os.save() - os.user.user_permissions.add(resitel_perm) - os.user.groups.add(resitel_group) - resitele.append(Resitel.objects.create(osoba=os, skola=rnd.choice(skoly), - rok_maturity=os.datum_narozeni.year + rnd.randint(18, 21), - zasilat=rnd.choice(Resitel.ZASILAT_CHOICES)[0])) - return resitele - -def gen_prijemci(rnd, osoby, kolik=10): - logger.info('Generuji příjemce (kolik={})...'.format(kolik)) - prijemci = [] - for i in rnd.sample(osoby, kolik): - prijemci.append(Prijemce.objects.create(osoba=i)) - return prijemci - -def gen_organizatori(rnd, osoby, last_rocnik): - logger.info('Generuji organizátory...') - organizatori = [] - - - seznam_konicku = ["vařím", "jezdím na kole", "řeším diferenciální rovnice", "koukám z okna", - "tancuji", "programuji", "jezdím vlakem", "nedělám nic"] - seznam_oboru = ["matematiku", "matematiku", "matematiku", "fyziku", "literaturu", - "informatiku", "informatiku", "běhání dokolečka"] - - x = 0 - org_perm = Permission.objects.filter(codename__exact='org').first() - org_group = Group.objects.filter(name__exact='org').first() - for os in osoby: - rand = rnd.randint(0, 8) - if (rand % 8 == 0): - pusobnost = rnd.randint(1, last_rocnik) - od = datetime.datetime( - year=1993 + pusobnost, - month=rnd.randint(1, 12), - day=rnd.randint(1, 28), - tzinfo=datetime.timezone.utc, - ) - do = datetime.datetime( - year=od.year + rnd.randint(1, 6), - month=rnd.randint(1, 12), - day=rnd.randint(1, 28), - tzinfo=datetime.timezone.utc, - ) - #aktualni organizatori jeste nemaji vyplnene organizuje_do - - #popis orga - konicek1 = rnd.choice(seznam_konicku) - konicek2 = rnd.choice(seznam_konicku) - obor = rnd.choice(seznam_oboru) - popis_orga = "Ve volném čase " + konicek1 + " a také " + konicek2 + ". Studuji " + obor + " a moc mě to baví." - - if do.year > datetime.datetime.now().year: - do = None - if not os.user: - if x: - user = User.objects.create_user(username='o'+str(x), email=os.email, password='o') - else: - user = User.objects.create_user(username='o', email=os.email, password='o') - x += 1 - os.user = user - os.save() - os.user.user_permissions.add(org_perm) - os.user.groups.add(org_group) - os.user.is_staff = True - os.user.save() - organizatori.append(Organizator.objects.create(osoba=os, - organizuje_od=od, organizuje_do=do, strucny_popis_organizatora = popis_orga)) - return organizatori - -def gen_zadani_ulohy(rnd, cisla, organizatori, pocet_oboru, poradi_cisla, poradi_problemu): - - # Proměnné pro náhodné generování názvů a zadání. - jaka = ["Šachová", "Černá", "Větrná", "Dlouhá", "Křehká", "Rychlá", - "Zákeřná", "Fyzikální"] - co = ["kostka", "smršť", "díra", "zrada", "toulka", "tyč", - "úloha", "blecha"] - sloveso = ["Najděte", "Spočítejte", "Zapište", "Změřte", "Odhadněte"] - koho = ["délku", "počet", "množství", "dílky"] - ceho = ["všech", "správných", "konstatních", "zelených"] - jmeno = ["řešení", "tahů", "čísel", "kalhot", "koulí", "hadů"] - kde = ["na zemi", "ve vesmíru", "ve vzduchu", "na šňůře", "v letadle"] - obory = ["M", "F", "I", "O", "B"] - - p = Uloha.objects.create( - # atributy třídy Problem - nazev=" ".join([rnd.choice(jaka), rnd.choice(co)]), - stav=Problem.STAV_ZADANY, - zamereni=rnd.sample(obory, pocet_oboru), - autor=rnd.choice(organizatori), - garant=rnd.choice(organizatori), - kod=str(poradi_problemu), - # atributy třídy Uloha - cislo_zadani=cisla[poradi_cisla-2-1], - cislo_reseni=cisla[poradi_cisla-1], - cislo_deadline=cisla[poradi_cisla-1], - max_body = rnd.randint(1, 8) - ) - - text = " ".join( - [rnd.choice(sloveso), - rnd.choice(koho), - rnd.choice(ceho), - rnd.choice(jmeno), - rnd.choice(kde)] - ) - text_zadani = Text.objects.create( - na_web = text, - do_cisla = text, - ) - zad = TextNode.objects.create(text = text_zadani, root = p.cislo_zadani.rocnik.rocniknode) - uloha_zadani = UlohaZadaniNode.objects.create(uloha = p, first_child = zad, root = p.cislo_zadani.rocnik.rocniknode) - p.ulohazadaninode = uloha_zadani - otec_syn(cisla[poradi_cisla-2-1].cislonode, uloha_zadani) - - return p - -def gen_vzoroveho_reseni_ulohy(rnd, organizatori, uloha, pocet_opravovatelu): - reseni = ["to je přece jasné", "triviální", "omlouváme se," - "otevřený problém", "neřešitelné", "triviálně triviální", - "použitím věty z prvního semestru na matfyzu", - "jednoduše pomocí látky z druhého semestru na matfyzu", - "netriviální aplikace diferenciálních rovnic", "zadání je vnitřně" - "sporné", "nepopsatelně jednoduché", "pokud jste na to nepřišli," - "tak jste fakt hloupí"] - - # Generování vzorového řešení. - obsah = rnd.choice(reseni) - text_vzoraku = Text.objects.create( - na_web = obsah, - do_cisla = obsah - ) - vzorak = TextNode.objects.create(text = text_vzoraku, root = uloha.cislo_zadani.rocnik.rocniknode) - uloha_vzorak = UlohaVzorakNode.objects.create(uloha = uloha, first_child = vzorak, root = uloha.cislo_zadani.rocnik.rocniknode) - uloha.ulohavzoraknode = uloha_vzorak - - uloha.opravovatele.set(rnd.sample(organizatori, pocet_opravovatelu)) - uloha.save() - return uloha_vzorak - -def gen_reseni_ulohy(rnd, cisla, uloha, pocet_resitelu, poradi_cisla, resitele_cisla, resitele): - - pocet_reseni = rnd.randint(pocet_resitelu//4, pocet_resitelu * 4) - # generujeme náhodný počet řešení vzhledem k počtu řešitelů čísla - for _ in range(pocet_reseni): - #print("Generuji {}-té řešení".format(reseni)) - if rnd.randint(1, 10) == 1: - # cca desetina řešení od více řešitelů - res_vyber = rnd.sample(resitele_cisla, - rnd.randint(2, 5)) - else: - res_vyber = rnd.sample(resitele_cisla, 1) - if resitele[0] in res_vyber: # speciální řešitel, který nemá žádné body - res_vyber.remove(resitele[0]) - - # Vytvoření řešení. - if uloha.cislo_zadani.zlomovy_deadline_pro_papirove_cislo() is not None: - # combine, abychom dostali plný čas a ne jen datum - cas_doruceni = uloha.cislo_zadani.deadline_v_cisle.first().deadline - datetime.timedelta(days=random.randint(0, 40)) - datetime.timedelta(minutes=random.randint(0, 60*24)) - # astimezone, protože jinak vyhazuje warning o nenastavené TZ - res = Reseni.objects.create(forma=rnd.choice(Reseni.FORMA_CHOICES)[0], cas_doruceni=cas_doruceni.astimezone(datetime.timezone.utc)) - else: - res = Reseni.objects.create(forma=rnd.choice(Reseni.FORMA_CHOICES)[0]) - # Problém a řešitele přiřadíme později, ManyToManyField - # se nedá vyplnit v create(). - res.resitele.set(res_vyber) - res.save() - - # Vytvoření hodnocení. - hod = Hodnoceni.objects.create( - body=rnd.randint(0, uloha.max_body), - cislo_body=cisla[poradi_cisla - 1], - reseni=res, - problem=uloha - ) - return - -def gen_ulohy_do_cisla(rnd, organizatori, resitele, rocnik_cisla, rocniky, size): - logger.info('Generuji úlohy do čísla (size={})...'.format(size)) - - k = 0 - for rocnik in rocniky: - k += 1 - print("Generuji {}. číslo.".format(k)) - cisla = rocnik_cisla[k - 1] - for ci in range(3, len(cisla) + 1): # pro všechna čísla - resitele_size = round(7/8 * 30 * size) # očekáváný celkový počet řešitelů - poc_res = rnd.randint(resitele_size//8, resitele_size//4) - # dané číslo řeší něco mezi osminou a čtvrtinou všech řešitelů - # (náhodná hausnumera, možno změnit) - # účelem je, aby se řešení generovala z menší množiny řešitelů a tedy - # bylo více řešení od jednoho řešitele daného čísla - resitele_cisla = rnd.sample(resitele, poc_res) - for pi in range(1, ((size + 1) // 2) + 1): # počet problémů - - poc_op = rnd.randint(1, 4) # počet opravovatelů - poc_oboru = rnd.randint(1, 2) - - # Generování zadání úlohy a UlohaZadaniNode, - # přivěšení pod dané číslo - p = gen_zadani_ulohy(rnd, cisla, organizatori, poc_oboru, ci, pi) - # Generování vzorového řešení - uloha_vzorak = gen_vzoroveho_reseni_ulohy(rnd, organizatori, - p, poc_op) - insert_last_child(cisla[ci-1].cislonode, uloha_vzorak) - - # Generování řešení a hodnocení k úloze - gen_reseni_ulohy(rnd, cisla, p, poc_res, ci, - resitele_cisla, resitele) - - return - -def gen_soustredeni(rnd, resitele, organizatori): - logger.info('Generuji soustředění...') - - soustredeni = [] - for _ in range(1, 10): #FIXME Tu range si změňte jak chcete, nevím, co přesně znamená size (asi Anet?) - datum_zacatku=datetime.date(rnd.randint(2000, 2020), rnd.randint(1, 12), rnd.randint(1, 28)) - working_sous = Soustredeni.objects.create( - rocnik=Rocnik.objects.order_by('?').first(), - verejne_db=rnd.choice([True, False]), - misto=rnd.choice(['Kremrolovice', 'Indiánov', 'U zmzliny', 'Vafláreň', 'Větrník', 'Horní Rakvička', 'Dolní cheesecake']), - typ=rnd.choice(['jarni', 'podzimni', 'vikend']), - datum_zacatku=datum_zacatku, - datum_konce=datum_zacatku + datetime.timedelta(days=7)) - ucastnici = rnd.sample(resitele, min(len(resitele), 20)) - working_sous.ucastnici.set(ucastnici) - #for res in rnd.sample(resitele, min(len(resitele), 20)): - # Soustredeni_Ucastnici.objects.create(resitel=res, soutredeni=working_sous) - orgove_vyber = rnd.sample(organizatori, min(len(organizatori), 20)) - working_sous.organizatori.set(orgove_vyber) - #for org in rnd.sample(organizatori, min(len(organizatori), 20)): - # Soustredeni_Organizatori.objects.create(organizator=org, soutredeni=working_sous) - working_sous.save() - soustredeni.append(working_sous) - return soustredeni - -def gen_rocniky(last_rocnik, size): - logger.info('Generuji ročníky (size={})...'.format(size)) - - rocniky = [] - node = None - for ri in range(min(last_rocnik - size, 1), last_rocnik + 1): - rocnik = Rocnik.objects.create(prvni_rok = 1993 + ri, rocnik = ri) - node2 = RocnikNode.objects.create(rocnik = rocnik, succ = node) - rocnik.save() - node = node2 - rocniky.append(rocnik) - return rocniky - -def gen_konfery(size, rnd, organizatori, resitele, soustredeni): - logger.info('Generuji konfery (size={})...'.format(size)) - - konfery = [] - for _ in range(1, size): #FIXME Tu range si změňte jak chcete, nevím, co přesně znamená size (asi Anet?) - # Anet: size je parametr udávající velikost testovacích dat a dá se pomocí ní škálovat, - # kolik dat se nageneruje - konfera = Konfera.objects.create( - nazev=rnd.choice(['Pozorování', 'Zkoumání', 'Modelování', 'Počítání', 'Zkoušení']) + rnd.choice([' vlastností', ' jevů', ' charakteristik']) + rnd.choice([' vektorových prostorů', ' kinetické terorie látek', ' molekulární biologie', ' syntentických stromů']), - anotace=lorem.paragraph(), - abstrakt=lorem.paragraph(), - garant=rnd.choice(organizatori), - soustredeni=rnd.choice(soustredeni), - typ_prezentace=rnd.choice(['veletrh', 'prezentace'])) - ucastnici_sous = list(konfera.soustredeni.ucastnici.all()) - ucastnici = rnd.sample(ucastnici_sous, min(len(ucastnici_sous), rnd.randint(3, 6))) - konfera.ucastnici.set(ucastnici) - #for res in rnd.sample(ucastnici, min(len(ucastnici), rnd.randint(3, 6))): - # Konfery_Ucastnici.objects.create(resitel=res, konfera=konfera) - konfera.save() - konfery.append(konfera) - return konfery - -def gen_cisla(rnd, rocniky): - logger.info('Generuji čísla...') - - rocnik_cisla = [] - for rocnik in rocniky: - otec = True - cisla = [] - cisel = rnd.randint(4, 8) - node = None - for ci in range(1, cisel + 1): - # první číslo vydáváme typicky okolo prázdnin - # (ci - 1)*2 zaručuje první číslo v červnu a všechna - # další po dvou měsících (což je rozumná aproximace) - mesic_vydani = (ci - 1)*2 + 6 - # celociselné dělení mi řekne, jestli to je první nebo druhý rok ročníku - vydano = datetime.date(rocnik.prvni_rok + mesic_vydani // 12, - (mesic_vydani - 1) % 12 + 1, - rnd.randint(1, 28)) - deadline = datetime.date(rocnik.prvni_rok + (mesic_vydani + 2) // 12, - (mesic_vydani + 1) % 12 + 1, - rnd.randint(1, 28)) - - cislo = Cislo.objects.create( - rocnik = rocnik, - poradi = str(ci), - datum_vydani=vydano, - verejne_db=True, - ) - node2 = CisloNode.objects.get(cislo = cislo) - node2.succ = node - node2.root = rocnik.rocniknode - cislo.save() - deadline = Deadline.objects.create( - cislo=cislo, - deadline=deadline, - typ=Deadline.TYP_CISLA, - verejna_vysledkovka=True, - ) - deadline.save() - node = node2 - if otec: - otec = False - rocnik.rocniknode.first_child = node - rocnik.save() - - cisla.append(cislo) - rocnik_cisla.append(cisla) - return rocnik_cisla - -def add_first_child(node, child): - node.first_child = child - node.save() - return - -def get_text(): - odstavec = lorem.paragraph() - return Text.objects.create(na_web = odstavec, do_cisla = odstavec) - -def gen_dlouhe_tema(rnd, organizatori, rocnik, nazev, obor, kod): - tema = Tema.objects.create( - nazev=nazev, - stav=Problem.STAV_ZADANY, - zamereni="M", - autor=rnd.choice(organizatori), - garant=rnd.choice(organizatori), - kod=str(kod), - tema_typ=rnd.choice(Tema.TEMA_CHOICES)[0], - rocnik=rocnik, - abstrakt = lorem.paragraph() - ) - - # Generování struktury k tématu - cisla = sorted(rocnik.cisla.all(), key=lambda cislo: cislo.poradi) - for cislo in cisla: - # Přidáme TemaVCisleNode do daného čísla - cislo_node = cislo.cislonode - tema_cislo_node = TemaVCisleNode.objects.create(tema = tema, root = cislo_node.root) - insert_last_child(cislo_node, tema_cislo_node) - - # Přidávání obsahu do čísla - cast_node = m.CastNode.objects.create(nadpis = "Příspěvek k číslu {}".format(cislo.kod), root=cislo_node.root) - add_first_child(tema_cislo_node, cast_node) - - text_node = TextNode.objects.create(text = get_text(), root=cislo_node.root) - add_first_child(cast_node, text_node) - - cast_node2 = m.CastNode.objects.create(nadpis = "První podproblém", root=cislo_node.root) - add_first_child(text_node, cast_node2) - - text_node2 = TextNode.objects.create(text = get_text(), root=cislo_node.root) - add_first_child(cast_node2, text_node2) - - cast_node3 = m.CastNode.objects.create(nadpis = "Druhý podproblém", root=cislo_node.root) - add_first_child(text_node2, cast_node3) - - text_node3 = TextNode.objects.create(text = get_text(), root=cislo_node.root) - add_first_child(cast_node3, text_node3) - - cast_node4 = m.CastNode.objects.create(nadpis = "Třetí podproblém", root=cislo_node.root) - add_first_child(text_node3, cast_node4) - - text_node4 = TextNode.objects.create(text = get_text(), root=cislo_node.root) - add_first_child(cast_node3, text_node4) - - cast_node3a = m.CastNode.objects.create(nadpis = "Podproblém paralelní s " - "druhým podproblémem", root=cislo_node.root) - cast_node3.succ = cast_node3a - cast_node3.save() - - text_node3a = TextNode.objects.create(text = get_text(), root=cislo_node.root) - add_first_child(cast_node3a, text_node3a) - - # Občas přidáme mezičíslo - if rnd.randint(1, 3) == 1: - create_node_after(cislo_node, m.MezicisloNode, root=cislo_node.root) - mezicislo_node = cislo_node.succ - - cast_node_mezicislo = m.CastNode.objects.create( - nadpis = "Příspěvek k mezičíslu".format(cislo.kod), root=cislo_node.root) - add_first_child(mezicislo_node, cast_node_mezicislo) - - odstavec = lorem.paragraph() - text_mezicislo = Text.objects.create(na_web = odstavec, do_cisla = odstavec) - text_node_mezicislo = TextNode.objects.create(text = text_mezicislo, root=cislo_node.root) - add_first_child(cast_node_mezicislo, text_node_mezicislo) - - return tema - -def gen_temata(rnd, rocniky, rocnik_cisla, organizatori): - logger.info('Generuji témata...') - - jake = ["Hravé", "Fyzikální", "Nejlepší", "Totálně masakrální", - "Šokující", "Magnetické", "Modré", "Překvapivé", - "Plasmatické", "Novoroční"] - co = ["téma", "záření", "stavení", "jiskření", "jelito", - "drama", "kuře", "moře", "klání", "proudění", "čekání"] - poc_oboru = rnd.randint(1, 2) - - rocnik_temata = [] - # Věříme, že rocnik_cisla je pole polí čísel podle ročníků, tak si necháme dát - # vždycky jeden ročník a k němu příslušná čísla. - for rocnik, cisla in zip(rocniky, rocnik_cisla): - kod = 1 - letosni_temata = [] - # Do každého ročníku vymyslíme tři (zatím) témata, v každém z prvních čísel jedno - for zacatek_tematu in range(1, 3): - # Vygenerujeme téma - t = Tema.objects.create( - # atributy třídy Problem - nazev=" ".join([rnd.choice(jake), rnd.choice(co)]), - stav=Problem.STAV_ZADANY, - zamereni=rnd.sample(["M", "F", "I", "O", "B"], poc_oboru), - autor=rnd.choice(organizatori), - garant=rnd.choice(organizatori), - kod=str(kod), - # atributy třídy Téma - tema_typ=rnd.choice(Tema.TEMA_CHOICES)[0], - rocnik=rocnik, - abstrakt = "Abstrakt tematka {}".format(kod) - ) - kod += 1 - - # Vymyslíme, kdy skončí - konec_tematu = min(rnd.randint(zacatek_tematu, 7), len(cisla)) - - # Vyrobíme TemaVCisleNody pro obsah - for i in range(zacatek_tematu, konec_tematu+1): - node = TemaVCisleNode.objects.create(tema = t,root=rocnik.rocniknode) - # FIXME: Není to off-by-one? - otec = cisla[i-1].cislonode - otec_syn(otec, node) - - # Vymyslíme, kdo to bude opravovat - poc_opravovatelu = rnd.randint(1, 3) - t.opravovatele.set(rnd.sample(organizatori, poc_opravovatelu)) - - # Uložíme všechno - t.save() - letosni_temata.append((zacatek_tematu, konec_tematu, t)) - rocnik_temata.append(letosni_temata) - return rocnik_temata - -def gen_ulohy_tematu(rnd, organizatori, resitele, tema, kod, cislo, cislo_se_vzorakem): - """ Generování úlohy k danému tématu. """ - - # Proměnné pro náhodné generování názvů a zadání. - jaka = ["Šachová", "Černá", "Větrná", "Dlouhá", "Křehká", "Rychlá", - "Zákeřná", "Fyzikální"] - co = ["kostka", "smršť", "díra", "zrada", "toulka", "tyč", - "úloha", "blecha"] - sloveso = ["Najděte", "Spočítejte", "Zapište", "Změřte", "Odhadněte"] - koho = ["délku", "počet", "množství", "dílky"] - ceho = ["všech", "správných", "konstatních", "zelených"] - jmeno = ["řešení", "tahů", "čísel", "kalhot", "koulí", "hadů"] - kde = ["na zemi", "ve vesmíru", "ve vzduchu", "na šňůře", "v letadle"] - obory = ["M", "F", "I", "O", "B"] - - uloha = Uloha.objects.create( - nazev=": ".join([tema.nazev, - "úloha {}.".format(kod)]), - nadproblem=tema, - stav=Problem.STAV_ZADANY, - zamereni=tema.zamereni, - autor=tema.autor, - garant=tema.garant, - kod=str(kod), - cislo_zadani=cislo, - cislo_reseni=cislo_se_vzorakem, - cislo_deadline=cislo_se_vzorakem, - max_body = rnd.randint(1, 8) - ) - - # Samotný obsah následně vzniklého Textu zadání - obsah = " ".join( - [rnd.choice(sloveso), - rnd.choice(koho), - rnd.choice(ceho), - rnd.choice(jmeno), - rnd.choice(kde)] - ) - text_zadani = Text.objects.create( - na_web = obsah, - do_cisla = obsah, - ) - zad = TextNode.objects.create(text = text_zadani, root=tema.temavcislenode_set.first().root) - uloha_zadani = UlohaZadaniNode.objects.create(uloha=uloha, first_child = zad, root=tema.temavcislenode_set.first().root) - uloha.ulohazadaninode = uloha_zadani - - # Generování řešení a hodnocení k úloze - gen_reseni_ulohy(rnd, [cislo], uloha, len(resitele)//4, 1, - resitele, resitele) - - return uloha, uloha_zadani - - -def gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori, resitele): - logger.info('Generuji úlohy k tématům...') - - # Ke každému ročníku si vezmeme příslušná čísla a témata - for rocnik, cisla, temata in zip(rocniky, rocnik_cisla, rocnik_temata): - # Do každého čísla nagenerujeme ke každému témátku pár úložek - for cislo in cisla: - print("Generuji úložky do {}-tého čísla".format(cislo.poradi)) - # Vzorák bude o dvě čísla dál - cislo_se_vzorakem = Cislo.objects.filter( - rocnik=rocnik, - poradi=str(int(cislo.poradi) + 2), - ) - # Pokud není číslo pro vzorák, tak se dá do posledního čísla - # (i kdyby tam mělo být zadání i řešení...) - # Tohle sice umožňuje vygenerovat vzorák do čísla dávno po konci témátka, - # ale to nám pro jednoduchost nevadí. - if len(cislo_se_vzorakem) == 0: - cislo_se_vzorakem = cisla[-1] - else: - cislo_se_vzorakem = cislo_se_vzorakem.first() - - for tema_node in all_children_of_type(cislo.cislonode, TemaVCisleNode): - tema = tema_node.tema - - # Pokud už témátko skončilo, žádné úložky negenerujeme - # FIXME: Bylo by hezčí, kdyby se čísla předávala jako Cislo a ne - # jako int v té trojici (start, konec, tema) - if not temata[int(tema.kod)-1][1] >= int(cislo_se_vzorakem.poradi): - continue - - # Generujeme 1 až 4 úložky k tomuto témátku do tohoto čísla. - for kod in range(1, rnd.randint(1, 4)): - u, uz = gen_ulohy_tematu(rnd, organizatori, resitele, tema, kod, - cislo, cislo_se_vzorakem) - - insert_last_child(tema_node, uz) - - poc_op = rnd.randint(1, 4) - uvz = gen_vzoroveho_reseni_ulohy(rnd, organizatori, - u, poc_op) - - # Najdeme správný TemaVCisleNode pro vložení vzoráku - res_tema_node = None; - for node in all_children(cislo_se_vzorakem.cislonode): - if isinstance(node, TemaVCisleNode): - if node.tema == tema: - res_tema_node = node - if res_tema_node is None: - raise LookupError("Nenalezen Node pro vložení vzoráku") - insert_last_child(res_tema_node, uvz) - u.save() - return - -def gen_novinky(rnd, organizatori): - logger.info('Generuji novinky...') - - - jake = ["zábavné", "veselé", "dobrodružné", "skvělé"] - co = ["soustředění", "Fyziklání", "víkendové setkání"] - kde = ["na Šumavě", "v Praze", "u Plzně", "na Marsu"] - kdy = ["Zítra bude", "10. 10. 2020 bude", "V prosinci bude", "V létě bude"] - - for i in range(5): - text_novinky = " ".join([rnd.choice(kdy), rnd.choice(kde), rnd.choice(jake), - rnd.choice(co)]) - novinka = Novinky.objects.create(id=i,autor=rnd.choice(organizatori), - text=(text_novinky+", těšíme se na vás!"),zverejneno=rnd.choice([True,False])) - novinka.save() - return - -def otec_syn(otec, syn): - bratr = otec.first_child - syn.succ = bratr - otec.first_child = syn - syn.save() - otec.save() - -def gen_clanek(rnd, organizatori, resitele): - logger.info("Generuji článek do čísla 22.2") - clanek = m.Clanek.objects.create( - nazev="Článek o Lorem ipsum", - nadproblem=None, - stav='vyreseny', - zamereni=['I'], - garant=rnd.choice(organizatori), - kod='cl', - ) - clanek.save() - - reseni = m.Reseni.objects.create( - zverejneno=True, - ) - reseni.resitele.add(rnd.choice(resitele)) - reseni.save() - - cislo = m.Cislo.objects.get(rocnik__rocnik=22, poradi=2) - cislonode = cislo.cislonode - - hodnoceni = m.Hodnoceni.objects.create( - body=15.0, - cislo_body=cislo, - reseni=reseni, - problem=clanek, - ) - hodnoceni.save() - - reseninode = m.ReseniNode.objects.create( - reseni=reseni - ) - reseninode.save() - - # Bude to celý text - reseni.text_cely = reseninode - reseni.save() - - from treenode.treelib import insert_last_child, create_child - insert_last_child(cislonode, reseninode) - - # Vyrobíme nějaký obsah - # FIXME: Ten, kdo vymyslel TreeLib (mj. týž, kdo psal tenhle kód), - # nevyrobil vhodnou funkci, takže to postavíme pozpátku pomocí create_child - # (které vyrábí _prvního_ syna) - create_child(reseninode, m.CastNode, nadpis="Lorem ipsum") - # Taky ten člověk nevyrobil vracení nových věcí... - castnode = reseninode.first_child - - # Úvodní odstaveček - obsah = "Tohle je zamyšlení o textu lorem ipsum. Začneme a skončíme ukázkou." - text = m.Text.objects.create( - na_web=obsah, - do_cisla=obsah, - ) - text.save() - create_child(reseninode, m.TextNode, text=text) - - # Několik odstavců lorem ipsum - for _ in range(rnd.randint(3, 7)): - lipsum = lorem.paragraph() - text = m.Text.objects.create( - na_web=lipsum, - do_cisla=lipsum, - ) - text.save() - create_child(castnode, m.TextNode, text=text) - logger.info(f"Článek vygenerován (reseni={reseni.id}, treenode={reseninode.id})") - - - -@transaction.atomic -def create_test_data(size = 6, rnd = None): - logger.info('Vyrábím testovací data (size={})...'.format(size)) - - assert size >= 1 - # pevna pseudo-nahodnost - rnd = rnd or random.Random(x=42) - - # static URL stranky - # FIXME: nakopirovat sem vsechny z produkcni databaze - s = Site.objects.filter(name="example.com") - f = FlatPage.objects.create(url="/", title="Seminář M&M", - content = "

Vítejte na stránce semináře MaM!

") - print(s) - f.sites.add(s[0]) - f.save() - - # users - admin = User.objects.create_superuser(username='admin', email='', password='admin') - os_admin = Osoba.objects.create( - user=admin, jmeno='admin', prijmeni='admin', - prezdivka='admin', osloveni='', email='admin@admin.admin', - telefon='123 456 789', datum_narozeni=datetime.date(2000, 1, 1), - ulice='admin', mesto='admin', psc='100 00', - datum_registrace=datetime.date(2020, 9, 6) - ) - or_admin = Organizator.objects.create( - osoba=os_admin, organizuje_od=None, organizuje_do=None, - strucny_popis_organizatora="Organizátor k uživateli Admin" - ) - - usernames = ['anet', 'bara', 'cyril', 'david', 'eva', 'filip'] - users = [] - for usr in usernames[:size]: - u = User.objects.create_user(username=usr, password=usr) - u.first_name = usr.capitalize() - u.save() - users.append(u) - print(users) - - # skoly - skoly = gen_skoly() - - # osoby - osoby = gen_osoby(rnd, size) - - # resitele a organizatori - last_rocnik = 25 - organizatori = gen_organizatori(rnd, osoby, last_rocnik) - resitele = gen_resitele(rnd, osoby, skoly) - - #generování novinek - novinky = gen_novinky(rnd, organizatori) - - # prijemci - prijemci = gen_prijemci(rnd, osoby) - - global zlinska - zlinska.kontaktni_osoba=rnd.choice(osoby) - zlinska.save() - - # rocniky - rocniky = gen_rocniky(last_rocnik, size) - - # cisla - # rocnik_cisla je pole polí čísel (typ Cislo), vnitřní pole odpovídají jednotlivým ročníkům. - rocnik_cisla = gen_cisla(rnd, rocniky) - - # generování obyčejných úloh do čísel - gen_ulohy_do_cisla(rnd, organizatori, resitele, rocnik_cisla, rocniky, size) - - # generování témat, zatím v prvních třech číslech po jednom - # FIXME: více témat - # rocnik_temata je pole polí trojic (první číslo :int, poslední číslo :int, téma:Tema), přičemž každé vnitřní pole odpovídá ročníku a FIXME: je to takhle fuj a když to někdo vidí poprvé, tak je z toho smutný, protože vůbec neví, co se děje a co má čekat. - rocnik_temata = gen_temata(rnd, rocniky, rocnik_cisla, organizatori) - - rocnik = Rocnik.objects.filter(rocnik = 23).first() - dlouhe_tema = gen_dlouhe_tema(rnd, organizatori, rocnik, "Strašně dlouhé téma", - "MFI", 8) - - # generování úloh k tématům ve všech číslech - gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori, resitele) - - #generování soustředění - soustredeni = gen_soustredeni(rnd, resitele, organizatori) - - #generování konfer - konfery = gen_konfery(size, rnd, organizatori, resitele, soustredeni) - - # vytvoreni pdf ke korekturam - create_test_pdf(rnd, organizatori) - - # TODO: nastavi správně, kolik se čeho generuje, aby rozsahy přibližně odpovídaly - # FIXME: misto typu ruzne typy objektu a vnoreni do sebe (Tom nechápe, co je tímto fixme míněno) - # TODO: vytvorit temata s ruznymi vlakny - # TODO: nagenerovat starsim rocnikum pohadku - # TODO: nagenerovat články - # TODO: vecpat obrázky všude, kde to jde - # TODO: mezičíslo node - # TODO: přidat ke konferám řešení a dát je do čísel - - # Dohackované vytvoření jednoho článku - gen_clanek(rnd, organizatori, resitele) - - # TODO: přidat články včetně zařazení do struktury treenodů, - # a následně otestovat konsistency check databáze z utils.py - # pomocí stránky /stav - - # obecné nastavení semináře, musí být už přidané ročníky a čísla, jinak se nastaví divně - nastaveni = Nastaveni.objects.create( - aktualni_cislo = Cislo.objects.all()[1]) diff --git a/seminar/utils.py b/seminar/utils.py deleted file mode 100644 index c826bf0b..00000000 --- a/seminar/utils.py +++ /dev/null @@ -1,387 +0,0 @@ -import datetime -import decimal - -from django.contrib.auth import get_user_model -from django.contrib.auth.decorators import permission_required, \ - user_passes_test -from django import views as DjangoViews - -from django.db import transaction - -from django.contrib.auth.models import AnonymousUser -from django.contrib.contenttypes.models import ContentType -from django.core.exceptions import ObjectDoesNotExist - -import logging - -import seminar.models as m -import treenode.treelib as t - -logger = logging.getLogger(__name__) - -org_required = permission_required('auth.org') -resitel_required = permission_required('auth.resitel') - - -# inspirováno django.contrib.auth.decorators permission_required -def check_perms(user): - if user.has_perms(('auth.resitel',)): - return True - if user.has_perms(('auth.org',)): - return True - return False - - -resitel_or_org_required = user_passes_test(check_perms) - -User = get_user_model() -# Není to úplně hezké, ale budeme doufat, že to je funkční... -User.je_org = property(lambda self: self.has_perm('auth.org')) -User.je_resitel = property(lambda self: self.has_perm('auth.resitel')) -AnonymousUser.je_org = False -AnonymousUser.je_resitel = False - - -def vzorecek_na_prepocet(body, resitelu): - """ Vzoreček na přepočet plných bodů na parciálni, když má řešení více řešitelů. """ - return body * 3 / (resitelu + 2) - - -def inverze_vzorecku_na_prepocet(body: decimal.Decimal, resitelu) -> decimal.Decimal: - """ Vzoreček na přepočet parciálních bodů na plné, když má řešení více řešitelů. """ - return round(body * (resitelu + 2) / 3, 1) - - -def histogram(seznam): - d = {} - for i in seznam: - if i not in d: - d[i] = 0 - d[i] += 1 - return d - -# Pozor: zarovnáno velmi netradičně pro přehlednost -roman_numerals = zip((1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1), - ('M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I')) - - -def roman(num): - res = "" - for i, n in roman_numerals: - res += n * (num // i) - num %= i - return res - - -def from_roman(rom): - if not rom: - return 0 - for i, n in roman_numerals: - if rom.upper().startswith(n): - return i + from_roman(rom[len(n):]) - raise Exception('Invalid roman numeral: "%s"', rom) - - -def seznam_problemu(): - """Funkce pro hledání nekonzistencí v databázi a dalších nežádoucích stavů webu/databáze. - - Nijak nesouvisí s Problémy zadanými řešitelům.""" - # FIXME: přejmenovat funkci? - # FIXME: Tak, jak je napsaná, asi spíš patří někam k views a ne do utils (?) - problemy = [] - - # Pomocna fce k formatovani problemovych hlasek - def prb(cls, msg, objs=None): - s = '%s: %s' % (cls.__name__, msg) - if objs: - s += ' [' - for o in objs: - try: - url = o.admin_url() - except: - url = None - if url: - s += '%s, ' % (url, o.pk,) - else: - s += '%s, ' % (o.pk,) - s = s[:-2] + ']' - problemy.append(s) - - # Duplicita jmen - jmena = {} - for r in m.Resitel.objects.all(): - j = r.osoba.plne_jmeno() - if j not in jmena: - jmena[j] = [] - jmena[j].append(r) - for j in jmena: - if len(jmena[j]) > 1: - prb(m.Resitel, 'Duplicitní jméno "%s"' % (j,), jmena[j]) - - # Data maturity a narození - for r in m.Resitel.objects.all(): - if not r.rok_maturity: - prb(m.Resitel, 'Neznámý rok maturity', [r]) - if r.rok_maturity and (r.rok_maturity < 1990 or r.rok_maturity > datetime.date.today().year + 10): - prb(m.Resitel, 'Podezřelé datum maturity', [r]) - if r.osoba.datum_narozeni and ( - r.osoba.datum_narozeni.year < 1970 or r.osoba.datum_narozeni.year > datetime.date.today().year - 12): - prb(m.Resitel, 'Podezřelé datum narození', [r]) -# if not r.email: -# prb(Resitel, u'Neznámý email', [r]) - - ## Kontroly konzistence databáze a TreeNodů - - # Články - for clanek in m.Clanek.objects.all(): - # získáme řešení svázané se článkem a z něj node ve stromě - reseni = clanek.reseni_set - if (reseni.count() != 1): - raise ValueError("Článek k sobě má nejedno řešení!") - r = reseni.first() - clanek_node = r.text_cely # vazba na ReseniNode z Reseni - # content type je věc pomáhající rozeznávat různé typy objektů v django-polymorphic - # protože isinstance vrátí vždy jen TreeNode - # https://django-polymorphic.readthedocs.io/en/stable/migrating.html - cislonode_ct = ContentType.objects.get_for_model(m.CisloNode) - node = clanek_node - while node is not None: - node_ct = node.polymorphic_ctype - if node_ct == cislonode_ct: # dostali jsme se k CisloNode - # zkontrolujeme, že stromové číslo odpovídá atributu - # .cislonode je opačná vazba k treenode_ptr, abychom z TreeNode dostali - # CisloNode - if clanek.cislo != node.cislonode.cislo: - prb(m.Clanek, "Číslo otištění uložené u článku nesedí s " - "číslem otištění podle struktury treenodů.", [clanek]) - break - node = t.get_parent(node) - - return problemy - - -### Generovani obalek -def resi_v_rocniku(rocnik, cislo=None): - """ Vrátí seznam řešitelů, co vyřešili nějaký problém v daném ročníku, do daného čísla. - Parametry: - rocnik (typu Rocnik) ročník, ze kterého chci řešitele, co něco odevzdali - cislo (typu Cislo) číslo, do kterého včetně se počítá, že v daném - ročníku řešitel něco poslal. - Pokud není zadané, počítají se všechna řešení z daného ročníku. - Výstup: - QuerySet objektů typu Resitel """ - - if cislo is None: - # filtrujeme pouze podle ročníku - return m.Resitel.objects.filter(rok_maturity__gte=rocnik.druhy_rok(), - reseni__hodnoceni__deadline_body__cislo__rocnik=rocnik).distinct() - else: # filtrujeme podle ročníku i čísla - return m.Resitel.objects.filter(rok_maturity__gte=rocnik.druhy_rok(), - reseni__hodnoceni__deadline_body__cislo__rocnik=rocnik, - reseni__hodnoceni__deadline_body__cislo__poradi__lte=cislo.poradi).distinct() - - -def aktivniResitele(cislo, pouze_letosni=False): - """ Vrací QuerySet aktivních řešitelů, což jsou ti, co ještě neodmaturovali - a letos něco poslali (anebo loni něco poslali, pokud jde o první tři čísla). - Parametry: - cislo (typu Cislo) číslo, o které se jedná - pouze_letosni jen řešitelé, kteří tento rok něco poslali - - """ - letos = cislo.rocnik - - # detekujeme, zda jde o první tři čísla či nikoli (tj. zda spamovat řešitele z minulého roku) - zacatek_rocniku = True - try: - if int(cislo.poradi) > 3: - zacatek_rocniku = False - except ValueError: - # if cislo.poradi != '7-8': - # raise ValueError(f'{cislo} je neplatné číslo čísla (není int a není 7-8)') - zacatek_rocniku = False - - # nehledě na číslo chceme jen řešitele, kteří letos něco odevzdali - if pouze_letosni: - zacatek_rocniku = False - - try: - loni = m.Rocnik.objects.get(rocnik=letos.rocnik - 1) - except ObjectDoesNotExist: - # Pro první ročník neexistuje ročník předchozí - zacatek_rocniku = False - - if not zacatek_rocniku: - return resi_v_rocniku(letos, cislo).filter(rok_maturity__gte=letos.druhy_rok()) - else: - # spojíme querysety s řešiteli loni a letos do daného čísla - return (resi_v_rocniku(loni) | resi_v_rocniku(letos, cislo)).distinct().filter(rok_maturity__gte=letos.druhy_rok()) - -def viewMethodSwitch(get, post): - """ - Vrátí view, který zavolá různé jiné views podle toho, kterou metodou je zavolán. - - Inspirováno https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#an-alternative-better-solution, jen jsem to udělal genericky. - - Parametry: - post view pro metodu POST - get view pro metodu GET - - V obou případech se míní už view jakožto funkce, takže u class-based views se už má použít .as_view() - - TODO: Podpora i pro metodu HEAD? A možná i pro FILES? - """ - - theGetView = get - thePostView = post - - class NewView(DjangoViews.View): - def get(self, request, *args, **kwargs): - return theGetView(request, *args, **kwargs) - def post(self, request, *args, **kwargs): - return thePostView(request, *args, **kwargs) - - return NewView.as_view() - - -def sync_skoly(base_url): - """Stáhne všechny školy z mamwebu na adrese a uloží je do databáze""" - from django.urls import reverse - full_url = base_url.rstrip('/') + reverse('export_skoly') - import requests - from django.core import serializers - json = requests.get(full_url, stream=True).content - for skola in serializers.deserialize('json', json): - skola.save() - -@transaction.atomic -def merge_resitele(cilovy, zdrojovy): - """Spojí dva řešitelské objekty do cílového. - - Pojmenování "zdrojový" je silně nepřiléhající, ale co už…""" - - # Postup: - # Sjednotit / upravit informace cílového řešitele - print('Upravuji data modelu') - fieldy_shoda = ['skola', 'rok_maturity', 'zasilat', 'zasilat_cislo_emailem', 'zasilat_cislo_papirove'] - - for f in fieldy_shoda: - zf = getattr(zdrojovy, f) - cf = getattr(cilovy, f) - if cf == zf: - print(f' Údaj {f} je shodný ({zf})') - else: - if zf is None: - print(f' Údaj {f} je pouze v cílovém, používám') - continue - if cf is None: - setattr(cilovy, f, zf) - cilovy.poznamka += f'\nDEBUG: Merge: doplnéný údaj {f} ze zdrojového: {zf}' - print(f" Přiřazuji {f} ze zdrojového: {zf}") - continue - # Jsou fakt různé… - # FIXME: chybí možnost na vlastní úpravu… - verdikt = input(f"\n\n Údaj {f} se u řešitele {cilovy} ({cilovy.id}) liší:\n Zdrojový: {zf}\n Cílový: {cf}\n Který použít, [z]drojový, [c]ílový? ") - verdikt = verdikt[0].casefold() - if verdikt == 'z': - setattr(cilovy, f, zf) - cilovy.poznamka += f'\nDEBUG: Merge: pro {f} použit údaj {zf} (zdrojový), nepoužit {cf} (cílový)' - elif verdikt == 'c': - cilovy.poznamka += f'\nDEBUG: Merge: pro {f} použit údaj {cf} (cílový), nepoužit {zf} (zdrojový)' - else: raise ValueError('Špatná odpověď, řešitel pravděpodobně neuložen') - # poznámku chceme nezahodit… - cilovy.poznamka += f'\nDEBUG: Merge: Původní poznámka: {zdrojovy.poznamka}' - print(f' Výsledný řešitel: {cilovy.__dict__}, ukládám') - cilovy.save() - - - # Přepojit všechny vazby ze zdrojového na cílového - print('Přepojuji vazby') - # Vazby: Škola (hotovo), Řešení_Řešitelé, Konfery_Účastníci, Soustředění_Účastníci, Osoba (vyřeší se později, nejde přepojit) - ct = m.Reseni_Resitele.objects.filter(resitele=zdrojovy).update(resitele=cilovy) - print(f' Přepojeno {ct} řešení') - ct = m.Konfery_Ucastnici.objects.filter(resitel=zdrojovy).update(resitel=cilovy) - print(f' Přepojeno {ct} konfer') - ct = m.Soustredeni_Ucastnici.objects.filter(resitel=zdrojovy).update(resitel=cilovy) - print(f' Přepojeno {ct} sousů') - - # Teď by na zdrojovém řešiteli nemělo nic viset, smazat ho, pamatujíce si jeho Osobu - zdrosoba = zdrojovy.osoba - print(f'Mažu zdrojového řešitele {zdrojovy.__dict__}') - zdrojovy.delete() - # Spojit osoby (separátní funkce). - merge_osoby(cilovy.osoba, zdrosoba) - - input("Potvrdit transakci řešitelů (^C pro zrušení) ") - -@transaction.atomic -def merge_osoby(cilova, zdrojova): - """ Spojí dvě osoby do cílové - - Nehlídá omezení typu "max 1 řešitel na osobu", to by měla hlídat databáze (OneToOneField).""" - # Sjednocení dat - print('Sjednocuji data osob') - # ID, User neřešíme, poznámku vyřešíme separátně. - fieldy = ['datum_narozeni', 'datum_registrace', 'datum_souhlasu_udaje', - 'datum_souhlasu_zasilani', 'email', 'foto', 'jmeno', 'mesto', - 'osloveni', 'prezdivka', 'prijmeni', 'psc', 'stat', 'telefon', 'ulice'] - for f in fieldy: - zf = getattr(zdrojova, f) - cf = getattr(cilova, f) - if cf == zf: - print(f' Údaj {f} je shodný ({zf})') - else: - if zf is None: - print(f' Údaj {f} je pouze v cílové, používám') - continue - if cf is None: - setattr(cilova, f, zf) - cilova.poznamka += f'\nDEBUG: Merge: doplnéný údaj {f} ze zdrojové: {zf}' - print(f" Přiřazuji {f} ze zdrojové: {zf}") - continue - # Jsou fakt různé… - # FIXME: chybí možnost na vlastní úpravu… - verdikt = input(f"\n\n Údaj {f} se u osoby {cilova} ({cilova.id}) liší:\n Zdrojový: {zf}\n Cílový: {cf}\n Který použít, [z]drojový, [c]ílový? ") - verdikt = verdikt[0].casefold() - if verdikt == 'z': - setattr(cilova, f, zf) - cilova.poznamka += f'\nDEBUG: Merge: pro {f} použit údaj {zf} (zdrojová), nepoužit {cf} (cílová)' - elif verdikt == 'c': - cilova.poznamka += f'\nDEBUG: Merge: pro {f} použit údaj {cf} (cílová), nepoužit {zf} (zdrojová)' - else: raise ValueError('Špatná odpověď, řešitel pravděpodobně neuložen') - # poznámku chceme nezahodit… - cilova.poznamka += f'\nDEBUG: Merge: Původní poznámka: {zdrojova.poznamka}' - print(f' Výsledná osoba: {cilova.__dict__}, ukládám') - cilova.save() - - # Vazby: Řešitel, User, Příjemce, Organizátor, Škola.kontaktní_osoba - print('Přepojuji vazby') - ct = m.Skola.objects.filter(kontaktni_osoba=zdrojova).update(kontaktni_osoba=cilova) - print(f' Přepojeno {ct} kontaktních osob') - # Ostatní vazby vyřeší OneToOneFieldy, ale někdy nemusí existovat… - ct = m.Resitel.objects.filter(osoba=zdrojova).update(osoba=cilova) - print(f' Přepojeno {ct} řešitelů') - ct = m.Prijemce.objects.filter(osoba=zdrojova).update(osoba=cilova) - print(f' Přepojeno {ct} příjemců') - ct = m.Organizator.objects.filter(osoba=zdrojova).update(osoba=cilova) - print(f' Přepojeno {ct} organizátorů') - # Uživatelé vedou opačným směrem, radši chceme zkontrolovat, že jsou různí ručně: - if zdrojova.user != cilova.user: - # Jeden z nich může být nenastavený… - if zdrojova.user is None: - print('Uživatel je již v cílové osobě') - elif cilova.user is None: - print('Používám uživatele zdrojové osoby') - cilova.user = zdrojova.user - # Teď nemůžeme uložit, protože kolize uživatelů. Ukládat cílovou budeme až po smazání zdrojové. - else: raise ValueError('Osoby mají obě uživatele, radši padám') - - # Uložení a mazání - print(f'Mažu zdrojovou osobu {zdrojova.__dict__}') - zdrojova.delete() - print(f'Ukládám cílovou osobu {cilova.__dict__}') - cilova.save() - - input("Potvrdit transakci osob (^C pro zrušení) ") - - diff --git a/seminar/views/__init__.py b/seminar/views/__init__.py deleted file mode 100644 index 8db4424b..00000000 --- a/seminar/views/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .views_all import * - -# Dočsasné views -from .docasne import * diff --git a/sifrovacka/urls.py b/sifrovacka/urls.py index 85f9c4cc..1357ef27 100644 --- a/sifrovacka/urls.py +++ b/sifrovacka/urls.py @@ -1,6 +1,6 @@ from django.urls import path -from seminar.utils import org_required, resitel_or_org_required +from personalni.utils import org_required, resitel_or_org_required from .views import SifrovackaView, SifrovackaListView, NapovedaView, NapovedaListView, PreskoceniView urlpatterns = [ diff --git a/sifrovacka/views.py b/sifrovacka/views.py index 0bfa994f..2a111faf 100644 --- a/sifrovacka/views.py +++ b/sifrovacka/views.py @@ -1,7 +1,7 @@ from django.urls import reverse from django.views.generic import FormView, ListView -from seminar.views import formularOKView +from various.views.pomocne import formularOKView from .forms import SifrovackaForm, NapovedaForm from .models import OdpovedUcastnika, SpravnaOdpoved, Napoveda, NapovezenoUcastnikovi from personalni.models import Resitel diff --git a/seminar/static/images/logomm.pdf b/soustredeni/static/soustredeni/logomm.pdf similarity index 100% rename from seminar/static/images/logomm.pdf rename to soustredeni/static/soustredeni/logomm.pdf diff --git a/soustredeni/testutils.py b/soustredeni/testutils.py new file mode 100644 index 00000000..52e81d1c --- /dev/null +++ b/soustredeni/testutils.py @@ -0,0 +1,69 @@ +import logging +import datetime +import random +from typing import Sequence + +import lorem + +from .models import Soustredeni, Konfera +import seminar.models.tvorba as am +import personalni.models as pm + +logger = logging.getLogger(__name__) + + +def gen_soustredeni( + size: int, + resitele: Sequence[pm.Resitel], + organizatori: Sequence[pm.Organizator], + rnd: random.Random = None, +) -> Sequence[Soustredeni]: + logger.info('Generuji soustředění (size={})...') + rnd = rnd or random.Random(x=42) + + soustredeni = [] + for _ in range(1, 10): # FIXME Tu range si změňte jak chcete, nevím, co přesně znamená size (asi Anet?) + datum_zacatku = datetime.date(rnd.randint(2000, 2020), rnd.randint(1, 12), rnd.randint(1, 28)) + working_sous = Soustredeni.objects.create( + rocnik=am.Rocnik.objects.order_by('?').first(), + verejne_db=rnd.choice([True, False]), + misto=rnd.choice(['Kremrolovice', 'Indiánov', 'U zmzliny', 'Vafláreň', 'Větrník', 'Horní Rakvička', 'Dolní cheesecake']), + typ=rnd.choice(['jarni', 'podzimni', 'vikend']), + datum_zacatku=datum_zacatku, + datum_konce=datum_zacatku + datetime.timedelta(days=7)) + ucastnici = rnd.sample(resitele, min(len(resitele), 20)) + working_sous.ucastnici.set(ucastnici) + orgove_vyber = rnd.sample(organizatori, min(len(organizatori), 20)) + working_sous.organizatori.set(orgove_vyber) + working_sous.save() + soustredeni.append(working_sous) + return soustredeni + + +def gen_konfery( + size: int, + organizatori: Sequence[pm.Organizator], + soustredeni: Sequence[Soustredeni], + resitele: Sequence[pm.Resitel] = None, + rnd: random.Random = None, +) -> Sequence[Konfera]: + logger.info('Generuji konfery (size={})...'.format(size)) + rnd = rnd or random.Random(x=42) + + konfery = [] + for _ in range(1, size): # FIXME Tu range si změňte jak chcete, nevím, co přesně znamená size (asi Anet?) + # Anet: size je parametr udávající velikost testovacích dat a dá se pomocí ní škálovat, + # kolik dat se nageneruje + konfera = Konfera.objects.create( + nazev=rnd.choice(['Pozorování', 'Zkoumání', 'Modelování', 'Počítání', 'Zkoušení']) + rnd.choice([' vlastností', ' jevů', ' charakteristik']) + rnd.choice([' vektorových prostorů', ' kinetické terorie látek', ' molekulární biologie', ' syntentických stromů']), + anotace=lorem.paragraph(), + abstrakt=lorem.paragraph(), + garant=rnd.choice(organizatori), + soustredeni=rnd.choice(soustredeni), + typ_prezentace=rnd.choice(['veletrh', 'prezentace'])) + ucastnici_sous = resitele if resitele else list(konfera.soustredeni.ucastnici.all()) + ucastnici = rnd.sample(ucastnici_sous, min(len(ucastnici_sous), rnd.randint(3, 6))) + konfera.ucastnici.set(ucastnici) + konfera.save() + konfery.append(konfera) + return konfery diff --git a/soustredeni/urls.py b/soustredeni/urls.py index 6d8de5e1..c961906a 100644 --- a/soustredeni/urls.py +++ b/soustredeni/urls.py @@ -1,6 +1,6 @@ from django.urls import path, include from . import views -from seminar.utils import org_required +from personalni.utils import org_required urlpatterns = [ path( diff --git a/soustredeni/views.py b/soustredeni/views.py index c7e17d24..5fc3bbc0 100644 --- a/soustredeni/views.py +++ b/soustredeni/views.py @@ -11,7 +11,7 @@ import subprocess from pathlib import Path import http -from seminar.views import obalkyView +import personalni.views class SoustredeniListView(generic.ListView): @@ -34,7 +34,7 @@ class SoustredeniListView(generic.ListView): def soustredeniObalkyView(request, soustredeni): soustredeni = get_object_or_404(Soustredeni, id=soustredeni) - return obalkyView(request, soustredeni.ucastnici.all()) + return personalni.views.obalkyView(request, soustredeni.ucastnici.all()) class SoustredeniUcastniciBaseView(generic.ListView): @@ -93,7 +93,7 @@ def soustredeniStvrzenkyView(request, soustredeni): with open(tempdir / "stvrzenky.tex", "w") as texfile: texfile.write(tex.decode()) - shutil.copy(find('images/logomm.pdf'), tempdir) + shutil.copy(find('soustredeni/logomm.pdf'), tempdir) subprocess.call(["pdflatex", "stvrzenky.tex"], cwd = tempdir, stdout=subprocess.DEVNULL) with open(tempdir / "stvrzenky.pdf", "rb") as pdffile: diff --git a/treenode/admin.py b/treenode/admin.py index 92c85cd5..8ffe4fc8 100644 --- a/treenode/admin.py +++ b/treenode/admin.py @@ -1,4 +1,6 @@ from django.contrib import admin +from django.db import models +from django.forms import widgets from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter @@ -86,3 +88,12 @@ class TextNodeAdmin(PolymorphicChildModelAdmin): show_in_index = True +class TextAdminInline(admin.TabularInline): + model = m.Text + formfield_overrides = { + models.TextField: {'widget': widgets.TextInput} + } + exclude = ['text_zkraceny_set', 'text_zkraceny'] + +admin.site.register(m.Text) +admin.site.register(m.Obrazek) diff --git a/treenode/templates/treenode/orphanage.html b/treenode/templates/treenode/orphanage.html index 53d4ed67..551ea544 100644 --- a/treenode/templates/treenode/orphanage.html +++ b/treenode/templates/treenode/orphanage.html @@ -1,4 +1,4 @@ -{% extends "seminar/archiv/base.html" %} +{% extends "tvorba/archiv/base.html" %} {% load static %} {% block custom_css %} diff --git a/seminar/management/__init__.py b/tvorba/__init__.py similarity index 100% rename from seminar/management/__init__.py rename to tvorba/__init__.py diff --git a/seminar/admin.py b/tvorba/admin.py similarity index 65% rename from seminar/admin.py rename to tvorba/admin.py index f8768ddf..e6c2c64b 100644 --- a/seminar/admin.py +++ b/tvorba/admin.py @@ -1,20 +1,20 @@ from django.contrib import admin -from django.db import models -from django.forms import widgets, ModelForm +from django.forms import ModelForm from django.core.exceptions import ValidationError from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter -from solo.admin import SingletonModelAdmin from django.utils.safestring import mark_safe # Todo: reversion -import seminar.models as m +import soustredeni.models -admin.site.register(m.Rocnik) -admin.site.register(m.ZmrazenaVysledkovka) +from seminar.models.tvorba import Rocnik, ZmrazenaVysledkovka, Deadline, Uloha, Problem, Tema, Clanek, Cislo -@admin.register(m.Deadline) +admin.site.register(Rocnik) +admin.site.register(ZmrazenaVysledkovka) + +@admin.register(Deadline) class DeadlineAdmin(admin.ModelAdmin): actions = ['pregeneruj_vysledkovku'] @@ -23,47 +23,49 @@ class DeadlineAdmin(admin.ModelAdmin): def pregeneruj_vysledkovku(self, req, qs): for deadline in qs: deadline.vygeneruj_vysledkovku() - + def has_bazmek_permission(self, request): # Boilerplate: potřebujeme nějakou permission, protože nějaká haluz v Djangu… return request.user.is_superuser - + + class DeadlineAdminInline(admin.TabularInline): - model = m.Deadline + model = Deadline extra = 0 + class CisloForm(ModelForm): class Meta: - model = m.Cislo + model = Cislo fields = '__all__' def clean(self): if self.cleaned_data.get('verejne_db') == False: return self.cleaned_data - # cn = m.CisloNode.objects.get(cislo=self.instance) + # cn = CisloNode.objects.get(cislo=self.instance) # errors = [] # for ch in tl.all_children(cn): - # if isinstance(ch, m.TemaVCisleNode): + # if isinstance(ch, TemaVCisleNode): # if ch.tema.stav not in \ - # (m.Problem.STAV_ZADANY, m.Problem.STAV_VYRESENY): + # (Problem.STAV_ZADANY, Problem.STAV_VYRESENY): # errors.append(ValidationError('Téma %(tema)s není zadané ani vyřešené', params={'tema':ch.tema})) # - # if isinstance(ch, m.UlohaZadaniNode) or isinstance(ch, m.UlohaVzorakNode): + # if isinstance(ch, UlohaZadaniNode) or isinstance(ch, UlohaVzorakNode): # if ch.uloha.stav not in \ - # (m.Problem.STAV_ZADANY, m.Problem.STAV_VYRESENY): + # (Problem.STAV_ZADANY, Problem.STAV_VYRESENY): # errors.append(ValidationError('Úloha %(uloha)s není zadaná ani vyřešená', params={'uloha':ch.uloha})) - # if isinstance(ch, m.ReseniNode): + # if isinstance(ch, ReseniNode): # for problem in ch.reseni.problem_set: # if problem not in \ - # (m.Problem.STAV_ZADANY, m.Problem.STAV_VYRESENY): + # (Problem.STAV_ZADANY, Problem.STAV_VYRESENY): # errors.append(ValidationError('Problém %s není zadaný ani vyřešený', code=problem)) # if errors: # errors.append(ValidationError(mark_safe('Pokud chceš učinit všechny problémy, co nejsou zadané ani vyřešené, zadanými a číslo zveřejnit, můžeš to udělat pomocí akce v seznamu čísel'))) # raise ValidationError(errors) errors = [] - for ch in m.Uloha.objects.filter(cislo_zadani=self.instance): - if ch.stav not in (m.Problem.STAV_ZADANY, m.Problem.STAV_VYRESENY): + for ch in Uloha.objects.filter(cislo_zadani=self.instance): + if ch.stav not in (Problem.STAV_ZADANY, Problem.STAV_VYRESENY): errors.append( ValidationError('Úloha %(uloha)s není zadaná ani vyřešená', params={'uloha': ch})) if errors: @@ -78,7 +80,7 @@ class CisloForm(ModelForm): return self.cleaned_data -@admin.register(m.Cislo) +@admin.register(Cislo) class CisloAdmin(admin.ModelAdmin): form = CisloForm actions = ['force_publish', 'pregeneruj_vysledkovky'] @@ -86,31 +88,31 @@ class CisloAdmin(admin.ModelAdmin): def force_publish(self,request,queryset): for cislo in queryset: - # cn = m.CisloNode.objects.get(cislo=cislo) + # cn = CisloNode.objects.get(cislo=cislo) # for ch in tl.all_children(cn): - # if isinstance(ch, m.TemaVCisleNode): - # if ch.tema.stav not in (m.Problem.STAV_ZADANY, m.Problem.STAV_VYRESENY): - # ch.tema.stav = m.Problem.STAV_ZADANY + # if isinstance(ch, TemaVCisleNode): + # if ch.tema.stav not in (Problem.STAV_ZADANY, Problem.STAV_VYRESENY): + # ch.tema.stav = Problem.STAV_ZADANY # ch.tema.save() # - # if isinstance(ch, m.UlohaZadaniNode) or isinstance(ch, m.UlohaVzorakNode): - # if ch.uloha.stav not in (m.Problem.STAV_ZADANY, m.Problem.STAV_VYRESENY): - # ch.uloha.stav = m.Problem.STAV_ZADANY + # if isinstance(ch, UlohaZadaniNode) or isinstance(ch, UlohaVzorakNode): + # if ch.uloha.stav not in (Problem.STAV_ZADANY, Problem.STAV_VYRESENY): + # ch.uloha.stav = Problem.STAV_ZADANY # ch.uloha.save() - # if isinstance(ch, m.ReseniNode): + # if isinstance(ch, ReseniNode): # for problem in ch.reseni.problem_set: - # if problem not in (m.Problem.STAV_ZADANY, m.Problem.STAV_VYRESENY): - # problem.stav = m.Problem.STAV_ZADANY + # if problem not in (Problem.STAV_ZADANY, Problem.STAV_VYRESENY): + # problem.stav = Problem.STAV_ZADANY # problem.save() - for ch in m.Uloha.objects.filter(cislo_zadani=cislo): - if ch.stav not in (m.Problem.STAV_ZADANY, m.Problem.STAV_VYRESENY): - ch.stav = m.Problem.STAV_ZADANY + for ch in Uloha.objects.filter(cislo_zadani=cislo): + if ch.stav not in (Problem.STAV_ZADANY, Problem.STAV_VYRESENY): + ch.stav = Problem.STAV_ZADANY ch.save() hp = ch.hlavni_problem - if hp.stav not in (m.Problem.STAV_ZADANY, m.Problem.STAV_VYRESENY): - hp.stav = m.Problem.STAV_ZADANY + if hp.stav not in (Problem.STAV_ZADANY, Problem.STAV_VYRESENY): + hp.stav = Problem.STAV_ZADANY hp.save() # TODO Řešení, vzoráky? @@ -127,24 +129,25 @@ class CisloAdmin(admin.ModelAdmin): for cislo in qs: for deadline in cislo.deadline_v_cisle.all(): deadline.vygeneruj_vysledkovku() - + def has_bazmek_permission(self, request): # Boilerplate: potřebujeme nějakou permission, protože nějaká haluz v Djangu… return request.user.is_superuser -@admin.register(m.Problem) +@admin.register(Problem) class ProblemAdmin(PolymorphicParentModelAdmin): - base_model = m.Problem + base_model = Problem child_models = [ - m.Tema, - m.Clanek, - m.Uloha, - m.Konfera, - ] + Tema, + Clanek, + Uloha, + soustredeni.models.Konfera, + ] # Pokud chceme orezavat na aktualni rocnik, musime do modelu pridat odkaz na rocnik. Zatim bere vse. search_fields = ['nazev'] + # V ProblemAdmin to nejde, protoze se to nepropise do deti class ProblemAdminMixin(object): show_in_index = True @@ -152,32 +155,23 @@ class ProblemAdminMixin(object): filter_horizontal = ['opravovatele'] -@admin.register(m.Tema) +@admin.register(Tema) class TemaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin): - base_model = m.Tema + base_model = Tema -@admin.register(m.Clanek) -class ClanekAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin): - base_model = m.Clanek -@admin.register(m.Uloha) -class UlohaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin): - base_model = m.Uloha +@admin.register(Clanek) +class ClanekAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin): + base_model = Clanek -@admin.register(m.Konfera) -class KonferaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin): - base_model = m.Konfera +@admin.register(Uloha) +class UlohaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin): + base_model = Uloha -class TextAdminInline(admin.TabularInline): - model = m.Text - formfield_overrides = { - models.TextField: {'widget': widgets.TextInput} - } - exclude = ['text_zkraceny_set','text_zkraceny'] -admin.site.register(m.Text) +@admin.register(soustredeni.models.Konfera) +class KonferaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin): + base_model = soustredeni.models.Konfera # admin.site.register(m.Pohadka) -admin.site.register(m.Obrazek) -admin.site.register(m.Nastaveni, SingletonModelAdmin) diff --git a/tvorba/apps.py b/tvorba/apps.py new file mode 100644 index 00000000..b04cb04c --- /dev/null +++ b/tvorba/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class TvorbaConfig(AppConfig): + name = 'tvorba' + verbose_name = 'Tvorba' diff --git a/seminar/management/commands/__init__.py b/tvorba/migrations/__init__.py similarity index 100% rename from seminar/management/commands/__init__.py rename to tvorba/migrations/__init__.py diff --git a/seminar/static/images/no-picture.png b/tvorba/static/tvorba/no-picture.png similarity index 100% rename from seminar/static/images/no-picture.png rename to tvorba/static/tvorba/no-picture.png diff --git a/seminar/static/images/tema-bez-obrazku.png b/tvorba/static/tvorba/tema-bez-obrazku.png similarity index 100% rename from seminar/static/images/tema-bez-obrazku.png rename to tvorba/static/tvorba/tema-bez-obrazku.png diff --git a/seminar/templates/seminar/archiv/cisla.html b/tvorba/templates/tvorba/archiv/cisla.html similarity index 100% rename from seminar/templates/seminar/archiv/cisla.html rename to tvorba/templates/tvorba/archiv/cisla.html diff --git a/seminar/templates/seminar/archiv/cislo.html b/tvorba/templates/tvorba/archiv/cislo.html similarity index 100% rename from seminar/templates/seminar/archiv/cislo.html rename to tvorba/templates/tvorba/archiv/cislo.html diff --git a/seminar/templates/seminar/archiv/cislo_vysledkovka.tex b/tvorba/templates/tvorba/archiv/cislo_vysledkovka.tex similarity index 100% rename from seminar/templates/seminar/archiv/cislo_vysledkovka.tex rename to tvorba/templates/tvorba/archiv/cislo_vysledkovka.tex diff --git a/seminar/templates/seminar/archiv/odmeny.html b/tvorba/templates/tvorba/archiv/odmeny.html similarity index 100% rename from seminar/templates/seminar/archiv/odmeny.html rename to tvorba/templates/tvorba/archiv/odmeny.html diff --git a/seminar/templates/seminar/archiv/prispevek.html b/tvorba/templates/tvorba/archiv/prispevek.html similarity index 100% rename from seminar/templates/seminar/archiv/prispevek.html rename to tvorba/templates/tvorba/archiv/prispevek.html diff --git a/seminar/templates/seminar/archiv/problem.html b/tvorba/templates/tvorba/archiv/problem.html similarity index 100% rename from seminar/templates/seminar/archiv/problem.html rename to tvorba/templates/tvorba/archiv/problem.html diff --git a/seminar/templates/seminar/archiv/problem_clanek.html b/tvorba/templates/tvorba/archiv/problem_clanek.html similarity index 100% rename from seminar/templates/seminar/archiv/problem_clanek.html rename to tvorba/templates/tvorba/archiv/problem_clanek.html diff --git a/seminar/templates/seminar/archiv/problem_tema.html b/tvorba/templates/tvorba/archiv/problem_tema.html similarity index 100% rename from seminar/templates/seminar/archiv/problem_tema.html rename to tvorba/templates/tvorba/archiv/problem_tema.html diff --git a/seminar/templates/seminar/archiv/problem_uloha.html b/tvorba/templates/tvorba/archiv/problem_uloha.html similarity index 100% rename from seminar/templates/seminar/archiv/problem_uloha.html rename to tvorba/templates/tvorba/archiv/problem_uloha.html diff --git a/seminar/templates/seminar/archiv/problem_uloha_tema.html b/tvorba/templates/tvorba/archiv/problem_uloha_tema.html similarity index 100% rename from seminar/templates/seminar/archiv/problem_uloha_tema.html rename to tvorba/templates/tvorba/archiv/problem_uloha_tema.html diff --git a/seminar/templates/seminar/archiv/rocnik.html b/tvorba/templates/tvorba/archiv/rocnik.html similarity index 96% rename from seminar/templates/seminar/archiv/rocnik.html rename to tvorba/templates/tvorba/archiv/rocnik.html index a49c1a89..77141998 100644 --- a/seminar/templates/seminar/archiv/rocnik.html +++ b/tvorba/templates/tvorba/archiv/rocnik.html @@ -33,7 +33,7 @@ {% if c.titulka_nahled %} {{ c.kod }} {% else %} - {% load static %} no-picture + {% load static %} no-picture {% endif %} @@ -79,7 +79,7 @@ {% if c.titulka_nahled %} {{ c.kod }} {% else %} - {% load static %} no-picture + {% load static %} no-picture {% endif %} diff --git a/seminar/templates/seminar/archiv/rocnik_vysledkovka.tex b/tvorba/templates/tvorba/archiv/rocnik_vysledkovka.tex similarity index 100% rename from seminar/templates/seminar/archiv/rocnik_vysledkovka.tex rename to tvorba/templates/tvorba/archiv/rocnik_vysledkovka.tex diff --git a/seminar/templates/seminar/archiv/temata.html b/tvorba/templates/tvorba/archiv/temata.html similarity index 100% rename from seminar/templates/seminar/archiv/temata.html rename to tvorba/templates/tvorba/archiv/temata.html diff --git a/seminar/templates/seminar/archiv/tituly.tex b/tvorba/templates/tvorba/archiv/tituly.tex similarity index 100% rename from seminar/templates/seminar/archiv/tituly.tex rename to tvorba/templates/tvorba/archiv/tituly.tex diff --git a/seminar/templates/seminar/clanky/organizatorske_clanky.html b/tvorba/templates/tvorba/clanky/organizatorske_clanky.html similarity index 100% rename from seminar/templates/seminar/clanky/organizatorske_clanky.html rename to tvorba/templates/tvorba/clanky/organizatorske_clanky.html diff --git a/seminar/templates/seminar/clanky/resitelske_clanky.html b/tvorba/templates/tvorba/clanky/resitelske_clanky.html similarity index 100% rename from seminar/templates/seminar/clanky/resitelske_clanky.html rename to tvorba/templates/tvorba/clanky/resitelske_clanky.html diff --git a/seminar/templates/seminar/tematka/rozcestnik.html b/tvorba/templates/tvorba/tematka/rozcestnik.html similarity index 95% rename from seminar/templates/seminar/tematka/rozcestnik.html rename to tvorba/templates/tvorba/tematka/rozcestnik.html index 52101483..08da4eaf 100644 --- a/seminar/templates/seminar/tematka/rozcestnik.html +++ b/tvorba/templates/tvorba/tematka/rozcestnik.html @@ -34,7 +34,7 @@ {% if tematko.obrazek %} {{ tematko.nazev }} {% else %} {# pokud témátko nemá fotku, zobrazuje se defaultní obrázek #} - {% load static %} {{ tematko.nazev }} + {% load static %} {{ tematko.nazev }} {% endif %} diff --git a/seminar/templates/seminar/tematka/toaletak.html b/tvorba/templates/tvorba/tematka/toaletak.html similarity index 100% rename from seminar/templates/seminar/tematka/toaletak.html rename to tvorba/templates/tvorba/tematka/toaletak.html diff --git a/seminar/templates/seminar/zadani/AktualniVysledkovka.html b/tvorba/templates/tvorba/zadani/AktualniVysledkovka.html similarity index 100% rename from seminar/templates/seminar/zadani/AktualniVysledkovka.html rename to tvorba/templates/tvorba/zadani/AktualniVysledkovka.html diff --git a/seminar/templates/seminar/zadani/AktualniZadani.html b/tvorba/templates/tvorba/zadani/AktualniZadani.html similarity index 100% rename from seminar/templates/seminar/zadani/AktualniZadani.html rename to tvorba/templates/tvorba/zadani/AktualniZadani.html diff --git a/seminar/templates/seminar/zadani/Temata.html b/tvorba/templates/tvorba/zadani/Temata.html similarity index 100% rename from seminar/templates/seminar/zadani/Temata.html rename to tvorba/templates/tvorba/zadani/Temata.html diff --git a/seminar/templatetags/__init__.py b/tvorba/templatetags/__init__.py similarity index 100% rename from seminar/templatetags/__init__.py rename to tvorba/templatetags/__init__.py diff --git a/seminar/templatetags/deadliny.py b/tvorba/templatetags/deadliny.py similarity index 100% rename from seminar/templatetags/deadliny.py rename to tvorba/templatetags/deadliny.py diff --git a/tvorba/testutils.py b/tvorba/testutils.py new file mode 100644 index 00000000..18440679 --- /dev/null +++ b/tvorba/testutils.py @@ -0,0 +1,506 @@ +# FIXME vypreparovat treenode + +import datetime + +import lorem +import django.contrib.auth +import logging + +from seminar.models import Rocnik, Cislo, Deadline, Problem, Tema, Uloha, TextNode, UlohaVzorakNode, RocnikNode, CisloNode, TemaVCisleNode, Text, UlohaZadaniNode +import seminar.models as m + +from treenode.treelib import all_children, insert_last_child, all_children_of_type, create_node_after + +from odevzdavatko.testutils import gen_reseni_ulohy + +logger = logging.getLogger(__name__) + +User = django.contrib.auth.get_user_model() + + +def gen_zadani_ulohy(rnd, cisla, organizatori, pocet_oboru, poradi_cisla, poradi_problemu): + + # Proměnné pro náhodné generování názvů a zadání. + jaka = ["Šachová", "Černá", "Větrná", "Dlouhá", "Křehká", "Rychlá", + "Zákeřná", "Fyzikální"] + co = ["kostka", "smršť", "díra", "zrada", "toulka", "tyč", + "úloha", "blecha"] + sloveso = ["Najděte", "Spočítejte", "Zapište", "Změřte", "Odhadněte"] + koho = ["délku", "počet", "množství", "dílky"] + ceho = ["všech", "správných", "konstatních", "zelených"] + jmeno = ["řešení", "tahů", "čísel", "kalhot", "koulí", "hadů"] + kde = ["na zemi", "ve vesmíru", "ve vzduchu", "na šňůře", "v letadle"] + obory = ["M", "F", "I", "O", "B"] + + p = Uloha.objects.create( + # atributy třídy Problem + nazev=" ".join([rnd.choice(jaka), rnd.choice(co)]), + stav=Problem.STAV_ZADANY, + zamereni=rnd.sample(obory, pocet_oboru), + autor=rnd.choice(organizatori), + garant=rnd.choice(organizatori), + kod=str(poradi_problemu), + # atributy třídy Uloha + cislo_zadani=cisla[poradi_cisla-2-1], + cislo_reseni=cisla[poradi_cisla-1], + cislo_deadline=cisla[poradi_cisla-1], + max_body = rnd.randint(1, 8) + ) + + text = " ".join( + [rnd.choice(sloveso), + rnd.choice(koho), + rnd.choice(ceho), + rnd.choice(jmeno), + rnd.choice(kde)] + ) + text_zadani = Text.objects.create( + na_web = text, + do_cisla = text, + ) + zad = TextNode.objects.create(text = text_zadani, root = p.cislo_zadani.rocnik.rocniknode) + uloha_zadani = UlohaZadaniNode.objects.create(uloha = p, first_child = zad, root = p.cislo_zadani.rocnik.rocniknode) + p.ulohazadaninode = uloha_zadani + otec_syn(cisla[poradi_cisla-2-1].cislonode, uloha_zadani) + + return p + +def gen_vzoroveho_reseni_ulohy(rnd, organizatori, uloha, pocet_opravovatelu): + reseni = ["to je přece jasné", "triviální", "omlouváme se," + "otevřený problém", "neřešitelné", "triviálně triviální", + "použitím věty z prvního semestru na matfyzu", + "jednoduše pomocí látky z druhého semestru na matfyzu", + "netriviální aplikace diferenciálních rovnic", "zadání je vnitřně" + "sporné", "nepopsatelně jednoduché", "pokud jste na to nepřišli," + "tak jste fakt hloupí"] + + # Generování vzorového řešení. + obsah = rnd.choice(reseni) + text_vzoraku = Text.objects.create( + na_web = obsah, + do_cisla = obsah + ) + vzorak = TextNode.objects.create(text = text_vzoraku, root = uloha.cislo_zadani.rocnik.rocniknode) + uloha_vzorak = UlohaVzorakNode.objects.create(uloha = uloha, first_child = vzorak, root = uloha.cislo_zadani.rocnik.rocniknode) + uloha.ulohavzoraknode = uloha_vzorak + + uloha.opravovatele.set(rnd.sample(organizatori, pocet_opravovatelu)) + uloha.save() + return uloha_vzorak + + +def gen_ulohy_do_cisla(rnd, organizatori, resitele, rocnik_cisla, rocniky, size): + logger.info('Generuji úlohy do čísla (size={})...'.format(size)) + + k = 0 + for rocnik in rocniky: + k += 1 + print("Generuji {}. číslo.".format(k)) + cisla = rocnik_cisla[k - 1] + for ci in range(3, len(cisla) + 1): # pro všechna čísla + resitele_size = round(7/8 * 30 * size) # očekáváný celkový počet řešitelů + poc_res = rnd.randint(resitele_size//8, resitele_size//4) + # dané číslo řeší něco mezi osminou a čtvrtinou všech řešitelů + # (náhodná hausnumera, možno změnit) + # účelem je, aby se řešení generovala z menší množiny řešitelů a tedy + # bylo více řešení od jednoho řešitele daného čísla + resitele_cisla = rnd.sample(resitele, poc_res) + for pi in range(1, ((size + 1) // 2) + 1): # počet problémů + + poc_op = rnd.randint(1, 4) # počet opravovatelů + poc_oboru = rnd.randint(1, 2) + + # Generování zadání úlohy a UlohaZadaniNode, + # přivěšení pod dané číslo + p = gen_zadani_ulohy(rnd, cisla, organizatori, poc_oboru, ci, pi) + # Generování vzorového řešení + uloha_vzorak = gen_vzoroveho_reseni_ulohy(rnd, organizatori, + p, poc_op) + insert_last_child(cisla[ci-1].cislonode, uloha_vzorak) + + # Generování řešení a hodnocení k úloze + gen_reseni_ulohy(rnd, cisla, p, poc_res, ci, + resitele_cisla, resitele) + + return + + +def gen_rocniky(last_rocnik, size): + logger.info('Generuji ročníky (size={})...'.format(size)) + + rocniky = [] + node = None + for ri in range(min(last_rocnik - size, 1), last_rocnik + 1): + rocnik = Rocnik.objects.create(prvni_rok = 1993 + ri, rocnik = ri) + node2 = RocnikNode.objects.create(rocnik = rocnik, succ = node) + rocnik.save() + node = node2 + rocniky.append(rocnik) + return rocniky + + +def gen_cisla(rnd, rocniky): + logger.info('Generuji čísla...') + + rocnik_cisla = [] + for rocnik in rocniky: + otec = True + cisla = [] + cisel = rnd.randint(4, 8) + node = None + for ci in range(1, cisel + 1): + # první číslo vydáváme typicky okolo prázdnin + # (ci - 1)*2 zaručuje první číslo v červnu a všechna + # další po dvou měsících (což je rozumná aproximace) + mesic_vydani = (ci - 1)*2 + 6 + # celociselné dělení mi řekne, jestli to je první nebo druhý rok ročníku + vydano = datetime.date(rocnik.prvni_rok + mesic_vydani // 12, + (mesic_vydani - 1) % 12 + 1, + rnd.randint(1, 28)) + deadline = datetime.date(rocnik.prvni_rok + (mesic_vydani + 2) // 12, + (mesic_vydani + 1) % 12 + 1, + rnd.randint(1, 28)) + + cislo = Cislo.objects.create( + rocnik = rocnik, + poradi = str(ci), + datum_vydani=vydano, + verejne_db=True, + ) + node2 = CisloNode.objects.get(cislo = cislo) + node2.succ = node + node2.root = rocnik.rocniknode + cislo.save() + deadline = Deadline.objects.create( + cislo=cislo, + deadline=deadline, + typ=Deadline.TYP_CISLA, + verejna_vysledkovka=True, + ) + deadline.save() + node = node2 + if otec: + otec = False + rocnik.rocniknode.first_child = node + rocnik.save() + + cisla.append(cislo) + rocnik_cisla.append(cisla) + return rocnik_cisla + +def add_first_child(node, child): + node.first_child = child + node.save() + return + +def get_text(): + odstavec = lorem.paragraph() + return Text.objects.create(na_web = odstavec, do_cisla = odstavec) + +def gen_dlouhe_tema(rnd, organizatori, rocnik, nazev, obor, kod): + tema = Tema.objects.create( + nazev=nazev, + stav=Problem.STAV_ZADANY, + zamereni="M", + autor=rnd.choice(organizatori), + garant=rnd.choice(organizatori), + kod=str(kod), + tema_typ=rnd.choice(Tema.TEMA_CHOICES)[0], + rocnik=rocnik, + abstrakt = lorem.paragraph() + ) + + # Generování struktury k tématu + cisla = sorted(rocnik.cisla.all(), key=lambda cislo: cislo.poradi) + for cislo in cisla: + # Přidáme TemaVCisleNode do daného čísla + cislo_node = cislo.cislonode + tema_cislo_node = TemaVCisleNode.objects.create(tema = tema, root = cislo_node.root) + insert_last_child(cislo_node, tema_cislo_node) + + # Přidávání obsahu do čísla + cast_node = m.CastNode.objects.create(nadpis = "Příspěvek k číslu {}".format(cislo.kod), root=cislo_node.root) + add_first_child(tema_cislo_node, cast_node) + + text_node = TextNode.objects.create(text = get_text(), root=cislo_node.root) + add_first_child(cast_node, text_node) + + cast_node2 = m.CastNode.objects.create(nadpis = "První podproblém", root=cislo_node.root) + add_first_child(text_node, cast_node2) + + text_node2 = TextNode.objects.create(text = get_text(), root=cislo_node.root) + add_first_child(cast_node2, text_node2) + + cast_node3 = m.CastNode.objects.create(nadpis = "Druhý podproblém", root=cislo_node.root) + add_first_child(text_node2, cast_node3) + + text_node3 = TextNode.objects.create(text = get_text(), root=cislo_node.root) + add_first_child(cast_node3, text_node3) + + cast_node4 = m.CastNode.objects.create(nadpis = "Třetí podproblém", root=cislo_node.root) + add_first_child(text_node3, cast_node4) + + text_node4 = TextNode.objects.create(text = get_text(), root=cislo_node.root) + add_first_child(cast_node3, text_node4) + + cast_node3a = m.CastNode.objects.create(nadpis = "Podproblém paralelní s " + "druhým podproblémem", root=cislo_node.root) + cast_node3.succ = cast_node3a + cast_node3.save() + + text_node3a = TextNode.objects.create(text = get_text(), root=cislo_node.root) + add_first_child(cast_node3a, text_node3a) + + # Občas přidáme mezičíslo + if rnd.randint(1, 3) == 1: + create_node_after(cislo_node, m.MezicisloNode, root=cislo_node.root) + mezicislo_node = cislo_node.succ + + cast_node_mezicislo = m.CastNode.objects.create( + nadpis = "Příspěvek k mezičíslu".format(cislo.kod), root=cislo_node.root) + add_first_child(mezicislo_node, cast_node_mezicislo) + + odstavec = lorem.paragraph() + text_mezicislo = Text.objects.create(na_web = odstavec, do_cisla = odstavec) + text_node_mezicislo = TextNode.objects.create(text = text_mezicislo, root=cislo_node.root) + add_first_child(cast_node_mezicislo, text_node_mezicislo) + + return tema + +def gen_temata(rnd, rocniky, rocnik_cisla, organizatori): + logger.info('Generuji témata...') + + jake = ["Hravé", "Fyzikální", "Nejlepší", "Totálně masakrální", + "Šokující", "Magnetické", "Modré", "Překvapivé", + "Plasmatické", "Novoroční"] + co = ["téma", "záření", "stavení", "jiskření", "jelito", + "drama", "kuře", "moře", "klání", "proudění", "čekání"] + poc_oboru = rnd.randint(1, 2) + + rocnik_temata = [] + # Věříme, že rocnik_cisla je pole polí čísel podle ročníků, tak si necháme dát + # vždycky jeden ročník a k němu příslušná čísla. + for rocnik, cisla in zip(rocniky, rocnik_cisla): + kod = 1 + letosni_temata = [] + # Do každého ročníku vymyslíme tři (zatím) témata, v každém z prvních čísel jedno + for zacatek_tematu in range(1, 3): + # Vygenerujeme téma + t = Tema.objects.create( + # atributy třídy Problem + nazev=" ".join([rnd.choice(jake), rnd.choice(co)]), + stav=Problem.STAV_ZADANY, + zamereni=rnd.sample(["M", "F", "I", "O", "B"], poc_oboru), + autor=rnd.choice(organizatori), + garant=rnd.choice(organizatori), + kod=str(kod), + # atributy třídy Téma + tema_typ=rnd.choice(Tema.TEMA_CHOICES)[0], + rocnik=rocnik, + abstrakt = "Abstrakt tematka {}".format(kod) + ) + kod += 1 + + # Vymyslíme, kdy skončí + konec_tematu = min(rnd.randint(zacatek_tematu, 7), len(cisla)) + + # Vyrobíme TemaVCisleNody pro obsah + for i in range(zacatek_tematu, konec_tematu+1): + node = TemaVCisleNode.objects.create(tema = t,root=rocnik.rocniknode) + # FIXME: Není to off-by-one? + otec = cisla[i-1].cislonode + otec_syn(otec, node) + + # Vymyslíme, kdo to bude opravovat + poc_opravovatelu = rnd.randint(1, 3) + t.opravovatele.set(rnd.sample(organizatori, poc_opravovatelu)) + + # Uložíme všechno + t.save() + letosni_temata.append((zacatek_tematu, konec_tematu, t)) + rocnik_temata.append(letosni_temata) + return rocnik_temata + +def gen_ulohy_tematu(rnd, organizatori, resitele, tema, kod, cislo, cislo_se_vzorakem): + """ Generování úlohy k danému tématu. """ + + # Proměnné pro náhodné generování názvů a zadání. + jaka = ["Šachová", "Černá", "Větrná", "Dlouhá", "Křehká", "Rychlá", + "Zákeřná", "Fyzikální"] + co = ["kostka", "smršť", "díra", "zrada", "toulka", "tyč", + "úloha", "blecha"] + sloveso = ["Najděte", "Spočítejte", "Zapište", "Změřte", "Odhadněte"] + koho = ["délku", "počet", "množství", "dílky"] + ceho = ["všech", "správných", "konstatních", "zelených"] + jmeno = ["řešení", "tahů", "čísel", "kalhot", "koulí", "hadů"] + kde = ["na zemi", "ve vesmíru", "ve vzduchu", "na šňůře", "v letadle"] + obory = ["M", "F", "I", "O", "B"] + + uloha = Uloha.objects.create( + nazev=": ".join([tema.nazev, + "úloha {}.".format(kod)]), + nadproblem=tema, + stav=Problem.STAV_ZADANY, + zamereni=tema.zamereni, + autor=tema.autor, + garant=tema.garant, + kod=str(kod), + cislo_zadani=cislo, + cislo_reseni=cislo_se_vzorakem, + cislo_deadline=cislo_se_vzorakem, + max_body = rnd.randint(1, 8) + ) + + # Samotný obsah následně vzniklého Textu zadání + obsah = " ".join( + [rnd.choice(sloveso), + rnd.choice(koho), + rnd.choice(ceho), + rnd.choice(jmeno), + rnd.choice(kde)] + ) + text_zadani = Text.objects.create( + na_web = obsah, + do_cisla = obsah, + ) + zad = TextNode.objects.create(text = text_zadani, root=tema.temavcislenode_set.first().root) + uloha_zadani = UlohaZadaniNode.objects.create(uloha=uloha, first_child = zad, root=tema.temavcislenode_set.first().root) + uloha.ulohazadaninode = uloha_zadani + + # Generování řešení a hodnocení k úloze + gen_reseni_ulohy(rnd, [cislo], uloha, len(resitele)//4, 1, + resitele, resitele) + + return uloha, uloha_zadani + + +def gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori, resitele): + logger.info('Generuji úlohy k tématům...') + + # Ke každému ročníku si vezmeme příslušná čísla a témata + for rocnik, cisla, temata in zip(rocniky, rocnik_cisla, rocnik_temata): + # Do každého čísla nagenerujeme ke každému témátku pár úložek + for cislo in cisla: + print("Generuji úložky do {}-tého čísla".format(cislo.poradi)) + # Vzorák bude o dvě čísla dál + cislo_se_vzorakem = Cislo.objects.filter( + rocnik=rocnik, + poradi=str(int(cislo.poradi) + 2), + ) + # Pokud není číslo pro vzorák, tak se dá do posledního čísla + # (i kdyby tam mělo být zadání i řešení...) + # Tohle sice umožňuje vygenerovat vzorák do čísla dávno po konci témátka, + # ale to nám pro jednoduchost nevadí. + if len(cislo_se_vzorakem) == 0: + cislo_se_vzorakem = cisla[-1] + else: + cislo_se_vzorakem = cislo_se_vzorakem.first() + + for tema_node in all_children_of_type(cislo.cislonode, TemaVCisleNode): + tema = tema_node.tema + + # Pokud už témátko skončilo, žádné úložky negenerujeme + # FIXME: Bylo by hezčí, kdyby se čísla předávala jako Cislo a ne + # jako int v té trojici (start, konec, tema) + if not temata[int(tema.kod)-1][1] >= int(cislo_se_vzorakem.poradi): + continue + + # Generujeme 1 až 4 úložky k tomuto témátku do tohoto čísla. + for kod in range(1, rnd.randint(1, 4)): + u, uz = gen_ulohy_tematu(rnd, organizatori, resitele, tema, kod, + cislo, cislo_se_vzorakem) + + insert_last_child(tema_node, uz) + + poc_op = rnd.randint(1, 4) + uvz = gen_vzoroveho_reseni_ulohy(rnd, organizatori, + u, poc_op) + + # Najdeme správný TemaVCisleNode pro vložení vzoráku + res_tema_node = None; + for node in all_children(cislo_se_vzorakem.cislonode): + if isinstance(node, TemaVCisleNode): + if node.tema == tema: + res_tema_node = node + if res_tema_node is None: + raise LookupError("Nenalezen Node pro vložení vzoráku") + insert_last_child(res_tema_node, uvz) + u.save() + return + + +def otec_syn(otec, syn): + bratr = otec.first_child + syn.succ = bratr + otec.first_child = syn + syn.save() + otec.save() + +def gen_clanek(rnd, organizatori, resitele): + logger.info("Generuji článek do čísla 22.2") + clanek = m.Clanek.objects.create( + nazev="Článek o Lorem ipsum", + nadproblem=None, + stav='vyreseny', + zamereni=['I'], + garant=rnd.choice(organizatori), + kod='cl', + ) + clanek.save() + + reseni = m.Reseni.objects.create( + zverejneno=True, + ) + reseni.resitele.add(rnd.choice(resitele)) + reseni.save() + + cislo = m.Cislo.objects.get(rocnik__rocnik=22, poradi=2) + cislonode = cislo.cislonode + + hodnoceni = m.Hodnoceni.objects.create( + body=15.0, + cislo_body=cislo, + reseni=reseni, + problem=clanek, + ) + hodnoceni.save() + + reseninode = m.ReseniNode.objects.create( + reseni=reseni + ) + reseninode.save() + + # Bude to celý text + reseni.text_cely = reseninode + reseni.save() + + from treenode.treelib import insert_last_child, create_child + insert_last_child(cislonode, reseninode) + + # Vyrobíme nějaký obsah + # FIXME: Ten, kdo vymyslel TreeLib (mj. týž, kdo psal tenhle kód), + # nevyrobil vhodnou funkci, takže to postavíme pozpátku pomocí create_child + # (které vyrábí _prvního_ syna) + create_child(reseninode, m.CastNode, nadpis="Lorem ipsum") + # Taky ten člověk nevyrobil vracení nových věcí... + castnode = reseninode.first_child + + # Úvodní odstaveček + obsah = "Tohle je zamyšlení o textu lorem ipsum. Začneme a skončíme ukázkou." + text = m.Text.objects.create( + na_web=obsah, + do_cisla=obsah, + ) + text.save() + create_child(reseninode, m.TextNode, text=text) + + # Několik odstavců lorem ipsum + for _ in range(rnd.randint(3, 7)): + lipsum = lorem.paragraph() + text = m.Text.objects.create( + na_web=lipsum, + do_cisla=lipsum, + ) + text.save() + create_child(castnode, m.TextNode, text=text) + logger.info(f"Článek vygenerován (reseni={reseni.id}, treenode={reseninode.id})") diff --git a/seminar/urls.py b/tvorba/urls.py similarity index 82% rename from seminar/urls.py rename to tvorba/urls.py index f740e6a4..e662491c 100644 --- a/seminar/urls.py +++ b/tvorba/urls.py @@ -1,15 +1,11 @@ from django.urls import path, include, re_path from . import views -from .utils import org_required +from personalni.utils import org_required urlpatterns = [ # path('aktualni/temata/', views.TemataRozcestnikView), # path('/t/', views.TematkoView), - # Organizatori - path('o-nas/organizatori/', views.CojemamOrganizatoriView.as_view(), name='organizatori'), - path('o-nas/organizatori/organizovali/', views.CojemamOrganizatoriStariView.as_view(), name='stari_organizatori'), - # Archiv path('archiv/rocniky/', views.ArchivView.as_view(), name="seminar_archiv_rocniky"), path('archiv/temata/', views.ArchivTemataView.as_view(), name="seminar_archiv_temata"), @@ -65,20 +61,11 @@ urlpatterns = [ org_required(views.TitulyView), name='seminar_cislo_titul' ), - path( - 'stav', - org_required(views.StavDatabazeView), - name='stav_databaze' - ), path( 'cislo/./odmeny/./', org_required(views.OdmenyView.as_view()), name="seminar_archiv_odmeny"), - path('', views.TitulniStranaView.as_view(), name='titulni_strana'), - path('jak-resit/', views.JakResitView.as_view(), name='jak_resit'), - path('stare-novinky/', views.StareNovinkyView.as_view(), name='stare_novinky'), - # Dočasné & neodladěné: path( 'hidden/hromadne_pridani', diff --git a/tvorba/utils.py b/tvorba/utils.py new file mode 100644 index 00000000..ba0c5d5b --- /dev/null +++ b/tvorba/utils.py @@ -0,0 +1,89 @@ +from django.core.exceptions import ObjectDoesNotExist + +import personalni.models + +import seminar.models as m + + +def resi_v_rocniku(rocnik, cislo=None): + """ Vrátí seznam řešitelů, co vyřešili nějaký problém v daném ročníku, do daného čísla. + Parametry: + rocnik (typu Rocnik) ročník, ze kterého chci řešitele, co něco odevzdali + cislo (typu Cislo) číslo, do kterého včetně se počítá, že v daném + ročníku řešitel něco poslal. + Pokud není zadané, počítají se všechna řešení z daného ročníku. + Výstup: + QuerySet objektů typu Resitel """ + + if cislo is None: + # filtrujeme pouze podle ročníku + return personalni.models.Resitel.objects.filter( + rok_maturity__gte=rocnik.druhy_rok(), + reseni__hodnoceni__deadline_body__cislo__rocnik=rocnik + ).distinct() + else: # filtrujeme podle ročníku i čísla + return personalni.models.Resitel.objects.filter( + rok_maturity__gte=rocnik.druhy_rok(), + reseni__hodnoceni__deadline_body__cislo__rocnik=rocnik, + reseni__hodnoceni__deadline_body__cislo__poradi__lte=cislo.poradi + ).distinct() + + +def aktivniResitele(cislo, pouze_letosni=False): + """ Vrací QuerySet aktivních řešitelů, což jsou ti, co ještě neodmaturovali + a letos něco poslali (anebo loni něco poslali, pokud jde o první tři čísla). + Parametry: + cislo (typu Cislo) číslo, o které se jedná + pouze_letosni jen řešitelé, kteří tento rok něco poslali + + """ + letos = cislo.rocnik + + # detekujeme, zda jde o první tři čísla či nikoli (tj. zda spamovat řešitele z minulého roku) + zacatek_rocniku = True + try: + if int(cislo.poradi) > 3: + zacatek_rocniku = False + except ValueError: + # if cislo.poradi != '7-8': + # raise ValueError(f'{cislo} je neplatné číslo čísla (není int a není 7-8)') + zacatek_rocniku = False + + # nehledě na číslo chceme jen řešitele, kteří letos něco odevzdali + if pouze_letosni: + zacatek_rocniku = False + + try: + loni = m.Rocnik.objects.get(rocnik=letos.rocnik - 1) + except ObjectDoesNotExist: + # Pro první ročník neexistuje ročník předchozí + zacatek_rocniku = False + + if not zacatek_rocniku: + return resi_v_rocniku(letos, cislo).filter(rok_maturity__gte=letos.druhy_rok()) + else: + # spojíme querysety s řešiteli loni a letos do daného čísla + return (resi_v_rocniku(loni) | resi_v_rocniku(letos, cislo))\ + .distinct().filter(rok_maturity__gte=letos.druhy_rok()) + + +# Pozor: zarovnáno velmi netradičně pro přehlednost +roman_numerals = zip((1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1), # noqa + ('M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I')) # noqa + + +def roman(num): + res = "" + for i, n in roman_numerals: + res += n * (num // i) + num %= i + return res + + +def from_roman(rom): + if not rom: + return 0 + for i, n in roman_numerals: + if rom.upper().startswith(n): + return i + from_roman(rom[len(n):]) + raise Exception('Invalid roman numeral: "%s"', rom) diff --git a/tvorba/views/__init__.py b/tvorba/views/__init__.py new file mode 100644 index 00000000..823ddd96 --- /dev/null +++ b/tvorba/views/__init__.py @@ -0,0 +1,584 @@ +# 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) diff --git a/seminar/views/docasne.py b/tvorba/views/docasne.py similarity index 100% rename from seminar/views/docasne.py rename to tvorba/views/docasne.py diff --git a/seminar/views/views_all.py b/tvorba/views/views_all.py similarity index 76% rename from seminar/views/views_all.py rename to tvorba/views/views_all.py index 31cfbe17..f960aac8 100644 --- a/seminar/views/views_all.py +++ b/tvorba/views/views_all.py @@ -8,15 +8,13 @@ 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 -from django.contrib.staticfiles.finders import find import seminar.models as s import seminar.models as m from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, \ - Organizator, Resitel, Novinky, Tema, Clanek, \ + Resitel, Novinky, Tema, Clanek, \ Deadline # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci #from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva -from seminar import utils from treenode import treelib import treenode.templatetags as tnltt import treenode.serializers as vr @@ -24,22 +22,18 @@ from vysledkovky.utils import body_resitelu, VysledkovkaCisla, \ VysledkovkaRocniku, VysledkovkaDoTeXu from datetime import date, datetime -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 logging import time -from collections.abc import Sequence -import http -from seminar.utils import aktivniResitele +import personalni.views + +from .. import utils # ze starého modelu #def verejna_temata(rocnik): @@ -88,7 +82,7 @@ def get_problemy_k_tematu(tema): #class AktualniZadaniView(generic.TemplateView): -# template_name = 'seminar/treenode.html' +# 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): @@ -106,7 +100,7 @@ def get_problemy_k_tematu(tema): def AktualniZadaniView(request): nastaveni = get_object_or_404(Nastaveni) verejne = nastaveni.aktualni_cislo.verejne() - return render(request, 'seminar/zadani/AktualniZadani.html', + return render(request, 'tvorba/zadani/AktualniZadani.html', {'nastaveni': nastaveni, 'verejne': verejne, }, @@ -117,7 +111,7 @@ def ZadaniTemataView(request): 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', + return render(request, 'tvorba/tematka/rozcestnik.html', { 'tematka': temata, 'verejne': verejne, @@ -132,7 +126,7 @@ def ZadaniTemataView(request): # 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', +# return render(request, 'tvorba/zadani/Temata.html', # { # 'temata': temata, # } @@ -151,7 +145,7 @@ def ZadaniTemataView(request): # if node.isinstance(node, s.PohadkaNode): # Mohu ignorovat, má pod sebou # pass # -# return render(request, 'seminar/tematka/toaletak.html', {}) +# return render(request, 'tvorba/tematka/toaletak.html', {}) # # #def TemataRozcestnikView(request): @@ -187,7 +181,7 @@ def ZadaniTemataView(request): # "obrazek": tematko_object.obrazek, # "cisla" : cisla # }) -# return render(request, 'seminar/tematka/rozcestnik.html', {"tematka": tematka, "rocnik" : nastaveni.aktualni_rocnik().rocnik}) +# return render(request, 'tvorba/tematka/rozcestnik.html', {"tematka": tematka, "rocnik" : nastaveni.aktualni_rocnik().rocnik}) # def ZadaniAktualniVysledkovkaView(request): @@ -211,25 +205,13 @@ def ZadaniAktualniVysledkovkaView(request): context['rocnik'] = rocnik return render( request, - 'seminar/zadani/AktualniVysledkovka.html', + 'tvorba/zadani/AktualniVysledkovka.html', context ) ### 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. @@ -237,73 +219,12 @@ def aktualni_temata(rocnik): return Tema.objects.filter(rocnik=rocnik, stav='zadany').order_by('kod') -class TitulniStranaView(generic.ListView): - template_name= 'seminar/titulnistrana/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 = m.Deadline.objects.filter(deadline__gte=timezone.now()).order_by("deadline").first() - context['nejblizsi_deadline'] = deadline - - # 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' + template_name = 'tvorba/archiv/cisla.html' def get_context_data(self, **kwargs): context = super(ArchivView, self).get_context_data(**kwargs) @@ -316,7 +237,7 @@ class ArchivView(generic.ListView): for i, c in enumerate(cisla): # Výchozí nastavení if c.rocnik not in urls: - urls[c.rocnik] = op.join(settings.STATIC_URL, "images", "no-picture.png") + 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 @@ -331,7 +252,7 @@ class ArchivView(generic.ListView): class RocnikView(generic.DetailView): model = Rocnik - template_name = 'seminar/archiv/rocnik.html' + template_name = 'tvorba/archiv/rocnik.html' # Vlastni ziskavani objektu z databaze podle (Rocnik.rocnik) def get_object(self, queryset=None): @@ -371,7 +292,7 @@ def resiteleRocnikuCsvExportView(request, rocnik): # s.Clanek: "clanek", # } # context = super().get_context_data(**kwargs) -# return ['seminar/archiv/problem_' + spravne_templaty[context['object'].__class__] + '.html'] +# return ['tvorba/archiv/problem_' + spravne_templaty[context['object'].__class__] + '.html'] # # def get_context_data(self, **kwargs): # context = super().get_context_data(**kwargs) @@ -387,7 +308,7 @@ def resiteleRocnikuCsvExportView(request, rocnik): class CisloView(generic.DetailView): # FIXME zobrazování témátek a vůbec, teď je tam jen odkaz na číslo v pdf model = Cislo - template_name = 'seminar/archiv/cislo.html' + template_name = 'tvorba/archiv/cislo.html' # Vlastni ziskavani objektu z databaze podle (Rocnik.rocnik) def get_object(self, queryset=None): @@ -430,7 +351,7 @@ class CisloView(generic.DetailView): class ArchivTemataView(generic.ListView): model = Problem - template_name = 'seminar/archiv/temata.html' + 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): @@ -441,13 +362,13 @@ class ArchivTemataView(generic.ListView): return ctx class OdmenyView(generic.TemplateView): - template_name = 'seminar/archiv/odmeny.html' + template_name = 'tvorba/archiv/odmeny.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) fromcislo = get_object_or_404(Cislo, rocnik=self.kwargs.get('frocnik'), poradi=self.kwargs.get('fcislo')) tocislo = get_object_or_404(Cislo, rocnik=self.kwargs.get('trocnik'), poradi=self.kwargs.get('tcislo')) - resitele = aktivniResitele(tocislo) + resitele = utils.aktivniResitele(tocislo) def get_diff(from_deadline: Deadline, to_deadline: Deadline): frombody = body_resitelu(resitele=resitele, jen_verejne=False, do=from_deadline) @@ -487,7 +408,7 @@ 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' + 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' @@ -518,7 +439,7 @@ class PosledniCisloVysledkovkaView(generic.DetailView): """View vytvořené pro zobrazení výsledkovky posledního čísla v TeXu.""" model = Rocnik - template_name = 'seminar/archiv/cislo_vysledkovka.tex' + template_name = 'tvorba/archiv/cislo_vysledkovka.tex' content_type = 'text/plain; charset=UTF8' def get_object(self, queryset=None): @@ -552,7 +473,7 @@ class PosledniCisloVysledkovkaView(generic.DetailView): 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' + 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' @@ -560,30 +481,8 @@ class RocnikVysledkovkaView(RocnikView): def cisloObalkyView(request, rocnik, cislo): realne_cislo = get_object_or_404(Cislo, poradi=cislo, rocnik__rocnik=rocnik) - return obalkyView(request, aktivniResitele(realne_cislo)) - - -def obalkyView(request, resitele): - if len(resitele) == 0: - return HttpResponse( - render(request, 'universal.html', { - 'title': 'Není pro koho vyrobit obálky.', - 'text': 'Právě ses pokusil/a vygenerovat obálky pro prázdnou množinu lidí. Můžeš to zkusit změnit, případně se zeptej webařů :-)', - }), - status=http.HTTPStatus.NOT_FOUND, - ) - - tex = render(request,'seminar/archiv/obalky.tex', {'resitele': resitele}).content + return personalni.views.obalkyView(request, utils.aktivniResitele(realne_cislo)) - with tempfile.TemporaryDirectory() as tempdir: - with open(tempdir+"/obalky.tex","w") as texfile: - texfile.write(tex.decode()) - shutil.copy(find('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') - return response ### Tituly @@ -618,7 +517,7 @@ def TitulyView(request, rocnik, cislo): else: jmenovci = True - return render(request, 'seminar/archiv/tituly.tex', + return render(request, 'tvorba/archiv/tituly.tex', {'resitele': resitele,'jmenovci':jmenovci},content_type="text/plain") @@ -649,7 +548,7 @@ def group_by_rocnik(clanky): # 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' + template_name = 'tvorba/clanky/resitelske_clanky.html' # FIXME: QuerySet není pole! def get_queryset(self): @@ -665,51 +564,11 @@ class ClankyResitelView(generic.ListView): # 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' +# 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') -### Status - -def StavDatabazeView(request): -# nastaveni = Nastaveni.objects.get() - problemy = utils.seznam_problemu() - muzi = Resitel.objects.filter(osoba__osloveni=m.Osoba.OSLOVENI_MUZSKE) - zeny = Resitel.objects.filter(osoba__osloveni=m.Osoba.OSLOVENI_ZENSKE) - 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]), - }) - - -# Interní, nemá se nikdy objevit v urls (jinak to účastníci vytrolí) -def formularOKView(request, text='', dalsi_odkazy: Sequence[tuple[str, str]] = ()): - template_name = 'seminar/formular_ok.html' - odkazy = list(dalsi_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, - 'text': text, - } - 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/jakresit/jak-resit.html' - def get_queryset(self): - return None class AktualniRocnikRedirectView(RedirectView): permanent=False diff --git a/various/admin.py b/various/admin.py index 694323fa..cd7104c2 100644 --- a/various/admin.py +++ b/various/admin.py @@ -1 +1,6 @@ +from solo.admin import SingletonModelAdmin from django.contrib import admin + +from .models import Nastaveni + +admin.site.register(Nastaveni, SingletonModelAdmin) diff --git a/various/management/__init__.py b/various/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/various/management/commands/__init__.py b/various/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/seminar/management/commands/generate_thumbnails.py b/various/management/commands/generate_thumbnails.py similarity index 100% rename from seminar/management/commands/generate_thumbnails.py rename to various/management/commands/generate_thumbnails.py diff --git a/seminar/management/commands/load_org_permissions.py b/various/management/commands/load_org_permissions.py similarity index 100% rename from seminar/management/commands/load_org_permissions.py rename to various/management/commands/load_org_permissions.py diff --git a/seminar/management/commands/nukedb.py b/various/management/commands/nukedb.py similarity index 100% rename from seminar/management/commands/nukedb.py rename to various/management/commands/nukedb.py diff --git a/seminar/management/commands/pregeneruj_zmrazene_vysledkovky.py b/various/management/commands/pregeneruj_zmrazene_vysledkovky.py similarity index 100% rename from seminar/management/commands/pregeneruj_zmrazene_vysledkovky.py rename to various/management/commands/pregeneruj_zmrazene_vysledkovky.py diff --git a/seminar/management/commands/save_org_permissions.py b/various/management/commands/save_org_permissions.py similarity index 100% rename from seminar/management/commands/save_org_permissions.py rename to various/management/commands/save_org_permissions.py diff --git a/seminar/management/commands/testdata.py b/various/management/commands/testdata.py similarity index 95% rename from seminar/management/commands/testdata.py rename to various/management/commands/testdata.py index d9ce8cfb..8f591fa5 100644 --- a/seminar/management/commands/testdata.py +++ b/various/management/commands/testdata.py @@ -1,13 +1,11 @@ -import datetime import os -import random from django.core.management.base import BaseCommand from django.core.management import call_command from django.conf import settings from seminar.models import Skola, Resitel, Rocnik, Cislo, Problem, Reseni, PrilohaReseni, Nastaveni -from seminar.testutils import create_test_data +from various.testutils import create_test_data import django.contrib.auth User = django.contrib.auth.get_user_model() diff --git a/seminar/templates/seminar/formular_ok.html b/various/templates/various/formular_ok.html similarity index 100% rename from seminar/templates/seminar/formular_ok.html rename to various/templates/various/formular_ok.html diff --git a/various/templates/various/jakresit/jak-resit.html b/various/templates/various/jakresit/jak-resit.html new file mode 100644 index 00000000..8aba9597 --- /dev/null +++ b/various/templates/various/jakresit/jak-resit.html @@ -0,0 +1,16 @@ +{% extends 'base.html' %} + +{% load humanize %} +{% load static %} + + +{% block content %} + +
+ +{% include 'various/jakresit/jakresit_1.svg' %} +{% include 'various/jakresit/jakresit_2.svg' %} +{% include 'various/jakresit/jakresit_3.svg' %} + +
+{% endblock %} diff --git a/seminar/templates/seminar/jakresit/jakresit_1.svg b/various/templates/various/jakresit/jakresit_1.svg similarity index 100% rename from seminar/templates/seminar/jakresit/jakresit_1.svg rename to various/templates/various/jakresit/jakresit_1.svg diff --git a/seminar/templates/seminar/jakresit/jakresit_2.svg b/various/templates/various/jakresit/jakresit_2.svg similarity index 100% rename from seminar/templates/seminar/jakresit/jakresit_2.svg rename to various/templates/various/jakresit/jakresit_2.svg diff --git a/seminar/templates/seminar/jakresit/jakresit_3.svg b/various/templates/various/jakresit/jakresit_3.svg similarity index 100% rename from seminar/templates/seminar/jakresit/jakresit_3.svg rename to various/templates/various/jakresit/jakresit_3.svg diff --git a/seminar/templates/seminar/pracuje_se.html b/various/templates/various/pracuje_se.html similarity index 100% rename from seminar/templates/seminar/pracuje_se.html rename to various/templates/various/pracuje_se.html diff --git a/seminar/templates/seminar/stav_databaze.html b/various/templates/various/stav_databaze.html similarity index 100% rename from seminar/templates/seminar/stav_databaze.html rename to various/templates/various/stav_databaze.html diff --git a/seminar/templates/seminar/titulnistrana/graph.svg b/various/templates/various/titulnistrana/graph.svg similarity index 100% rename from seminar/templates/seminar/titulnistrana/graph.svg rename to various/templates/various/titulnistrana/graph.svg diff --git a/seminar/templates/seminar/titulnistrana/titulnistrana.html b/various/templates/various/titulnistrana/titulnistrana.html similarity index 96% rename from seminar/templates/seminar/titulnistrana/titulnistrana.html rename to various/templates/various/titulnistrana/titulnistrana.html index e9e5c16d..3f0efca2 100644 --- a/seminar/templates/seminar/titulnistrana/titulnistrana.html +++ b/various/templates/various/titulnistrana/titulnistrana.html @@ -79,7 +79,7 @@ function sousdeadline() {
- {% include 'seminar/titulnistrana/graph.svg' %} + {% include 'various/titulnistrana/graph.svg' %}
@@ -95,7 +95,7 @@ function sousdeadline() { {# Novinky #}

Co je nového?

- {% include 'seminar/novinky.html' %} + {% include 'novinky/novinky.html' %} Archiv novinek diff --git a/seminar/templatetags/tex.py b/various/templatetags/tex.py similarity index 100% rename from seminar/templatetags/tex.py rename to various/templatetags/tex.py diff --git a/various/testutils.py b/various/testutils.py new file mode 100644 index 00000000..52411ef6 --- /dev/null +++ b/various/testutils.py @@ -0,0 +1,135 @@ +import datetime +import random +import logging + +import django.contrib.auth +from django.contrib.flatpages.models import FlatPage +from django.contrib.sites.models import Site +from django.db import transaction + +from seminar.models import Rocnik, Cislo, Nastaveni, Osoba, Organizator + +from korektury.testutils import create_test_pdf +from novinky.testutils import gen_novinky +from personalni.testutils import gen_organizatori, gen_osoby, gen_prijemci, gen_resitele, gen_skoly +from soustredeni.testutils import gen_soustredeni, gen_konfery +from tvorba.testutils import gen_cisla, gen_clanek, gen_dlouhe_tema, gen_rocniky, gen_temata, gen_ulohy_do_cisla, gen_ulohy_k_tematum + +logger = logging.getLogger(__name__) + +User = django.contrib.auth.get_user_model() + + +@transaction.atomic +def create_test_data(size=6, rnd=None): + logger.info('Vyrábím testovací data (size={})...'.format(size)) + + assert size >= 1 + # pevna pseudo-nahodnost + rnd = rnd or random.Random(x=42) + + # static URL stranky + # FIXME: nakopirovat sem vsechny z produkcni databaze + s = Site.objects.filter(name="example.com") + f = FlatPage.objects.create( + url="/", title="Seminář M&M", + content="

Vítejte na stránce semináře MaM!

", + ) + print(s) + f.sites.add(s[0]) + f.save() + + # users + admin = User.objects.create_superuser( + username='admin', email='', password='admin', + ) + os_admin = Osoba.objects.create( + user=admin, jmeno='admin', prijmeni='admin', + prezdivka='admin', osloveni='', email='admin@admin.admin', + telefon='123 456 789', datum_narozeni=datetime.date(2000, 1, 1), + ulice='admin', mesto='admin', psc='100 00', + datum_registrace=datetime.date(2020, 9, 6), + ) + or_admin = Organizator.objects.create( + osoba=os_admin, organizuje_od=None, organizuje_do=None, + strucny_popis_organizatora="Organizátor k uživateli Admin", + ) + + usernames = ['anet', 'bara', 'cyril', 'david', 'eva', 'filip'] + users = [] + for usr in usernames[:size]: + u = User.objects.create_user(username=usr, password=usr) + u.first_name = usr.capitalize() + u.save() + users.append(u) + print(users) + + # skoly + skoly = gen_skoly() + + # osoby + osoby = gen_osoby(rnd, size) + + # resitele a organizatori + last_rocnik = 25 + organizatori = gen_organizatori(rnd, osoby, last_rocnik) + resitele = gen_resitele(rnd, osoby, skoly) + + # generování novinek + novinky = gen_novinky(rnd, organizatori) + + # prijemci + prijemci = gen_prijemci(rnd, osoby) + + # rocniky + rocniky = gen_rocniky(last_rocnik, size) + + # cisla + # rocnik_cisla je pole polí čísel (typ Cislo), vnitřní pole odpovídají jednotlivým ročníkům. + rocnik_cisla = gen_cisla(rnd, rocniky) + + # generování obyčejných úloh do čísel + gen_ulohy_do_cisla(rnd, organizatori, resitele, rocnik_cisla, rocniky, size) + + # generování témat, zatím v prvních třech číslech po jednom + # FIXME: více témat + # rocnik_temata je pole polí trojic (první číslo :int, poslední číslo :int, téma:Tema), přičemž každé vnitřní pole odpovídá ročníku a FIXME: je to takhle fuj a když to někdo vidí poprvé, tak je z toho smutný, protože vůbec neví, co se děje a co má čekat. + rocnik_temata = gen_temata(rnd, rocniky, rocnik_cisla, organizatori) + + rocnik = Rocnik.objects.filter(rocnik=23).first() + dlouhe_tema = gen_dlouhe_tema( + rnd, organizatori, rocnik, "Strašně dlouhé téma", + "MFI", 8, + ) + + # generování úloh k tématům ve všech číslech + gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori, resitele) + + # generování soustředění + soustredeni = gen_soustredeni(size, resitele, organizatori, rnd=rnd) + + # generování konfer + konfery = gen_konfery(size, organizatori, soustredeni, rnd=rnd) + + # vytvoreni pdf ke korekturam + create_test_pdf(rnd, organizatori) + + # TODO: nastavi správně, kolik se čeho generuje, aby rozsahy přibližně odpovídaly + # FIXME: misto typu ruzne typy objektu a vnoreni do sebe (Tom nechápe, co je tímto fixme míněno) + # TODO: vytvorit temata s ruznymi vlakny + # TODO: nagenerovat starsim rocnikum pohadku + # TODO: nagenerovat články + # TODO: vecpat obrázky všude, kde to jde + # TODO: mezičíslo node + # TODO: přidat ke konferám řešení a dát je do čísel + + # Dohackované vytvoření jednoho článku + gen_clanek(rnd, organizatori, resitele) + + # TODO: přidat články včetně zařazení do struktury treenodů, + # a následně otestovat konsistency check databáze z utils.py + # pomocí stránky /stav + + # obecné nastavení semináře, musí být už přidané ročníky a čísla, jinak se nastaví divně + nastaveni = Nastaveni.objects.create( + aktualni_cislo=Cislo.objects.all()[1]) diff --git a/various/urls.py b/various/urls.py new file mode 100644 index 00000000..a3f03ade --- /dev/null +++ b/various/urls.py @@ -0,0 +1,9 @@ +from django.urls import path +from .views.final import TitulniStranaView, JakResitView, StavDatabazeView +from personalni.utils import org_required + +urlpatterns = [ + path('', TitulniStranaView.as_view(), name='titulni_strana'), + path('jak-resit/', JakResitView.as_view(), name='jak_resit'), + path('stav', org_required(StavDatabazeView), name='stav_databaze'), +] diff --git a/various/views/__init__.py b/various/views/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/various/views.py b/various/views/csrf.py similarity index 100% rename from various/views.py rename to various/views/csrf.py diff --git a/various/views/final.py b/various/views/final.py new file mode 100644 index 00000000..de23a718 --- /dev/null +++ b/various/views/final.py @@ -0,0 +1,160 @@ +""" +Stránky, které se mi nepovedlo lépe zařadit. + +Oproti `./pomocne.py` se tyto views používají přímo ve various +a naopak importují spoustu věcí odjinud +""" +import datetime + +from django.contrib.contenttypes.models import ContentType +from django.shortcuts import get_object_or_404, render +from django.utils import timezone +from django.views import generic + +import novinky.views +import treenode.treelib as t +import tvorba.views +from personalni.models import Resitel +from seminar import models as m + +from ..models import Nastaveni + + +class TitulniStranaView(generic.ListView): + template_name = 'various/titulnistrana/titulnistrana.html' + + def get_queryset(self): + return novinky.views.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 = m.Deadline.objects.filter( + deadline__gte=timezone.now()).order_by("deadline").first() + context['nejblizsi_deadline'] = deadline + + # Aktuální témata + nazvy_a_odkazy_na_aktualni_temata = [] + akt_temata = tvorba.views.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 + + return context + + +class JakResitView(generic.ListView): + template_name = 'various/jakresit/jak-resit.html' + + def get_queryset(self): + return None + + +### Status +def histogram(seznam): + d = {} + for i in seznam: + if i not in d: + d[i] = 0 + d[i] += 1 + return d + + +def seznam_problemu(): + """Funkce pro hledání nekonzistencí v databázi a dalších nežádoucích stavů webu/databáze. + + Nijak nesouvisí s Problémy zadanými řešitelům.""" + # FIXME: přejmenovat funkci? + problemy = [] + + # Pomocna fce k formatovani problemovych hlasek + def prb(cls, msg, objs=None): + s = '%s: %s' % (cls.__name__, msg) + if objs: + s += ' [' + for o in objs: + try: + url = o.admin_url() + except: + url = None + if url: + s += '%s, ' % (url, o.pk,) + else: + s += '%s, ' % (o.pk,) + s = s[:-2] + ']' + problemy.append(s) + + # Duplicita jmen + jmena = {} + for r in m.Resitel.objects.all(): + j = r.osoba.plne_jmeno() + if j not in jmena: + jmena[j] = [] + jmena[j].append(r) + for j in jmena: + if len(jmena[j]) > 1: + prb(m.Resitel, 'Duplicitní jméno "%s"' % (j,), jmena[j]) + + # Data maturity a narození + for r in m.Resitel.objects.all(): + if not r.rok_maturity: + prb(m.Resitel, 'Neznámý rok maturity', [r]) + if r.rok_maturity and (r.rok_maturity < 1990 or r.rok_maturity > datetime.date.today().year + 10): + prb(m.Resitel, 'Podezřelé datum maturity', [r]) + if r.osoba.datum_narozeni and ( + r.osoba.datum_narozeni.year < 1970 or r.osoba.datum_narozeni.year > datetime.date.today().year - 12): + prb(m.Resitel, 'Podezřelé datum narození', [r]) + # if not r.email: + # prb(Resitel, u'Neznámý email', [r]) + + ## Kontroly konzistence databáze a TreeNodů + + # Články + for clanek in m.Clanek.objects.all(): + # získáme řešení svázané se článkem a z něj node ve stromě + reseni = clanek.reseni_set + if (reseni.count() != 1): + raise ValueError("Článek k sobě má nejedno řešení!") + r = reseni.first() + clanek_node = r.text_cely # vazba na ReseniNode z Reseni + # content type je věc pomáhající rozeznávat různé typy objektů v django-polymorphic + # protože isinstance vrátí vždy jen TreeNode + # https://django-polymorphic.readthedocs.io/en/stable/migrating.html + cislonode_ct = ContentType.objects.get_for_model(m.CisloNode) + node = clanek_node + while node is not None: + node_ct = node.polymorphic_ctype + if node_ct == cislonode_ct: # dostali jsme se k CisloNode + # zkontrolujeme, že stromové číslo odpovídá atributu + # .cislonode je opačná vazba k treenode_ptr, abychom z TreeNode dostali + # CisloNode + if clanek.cislo != node.cislonode.cislo: + prb(m.Clanek, "Číslo otištění uložené u článku nesedí s " + "číslem otištění podle struktury treenodů.", [clanek]) + break + node = t.get_parent(node) + + return problemy + +def StavDatabazeView(request): + # nastaveni = Nastaveni.objects.get() + problemy = seznam_problemu() + muzi = Resitel.objects.filter(osoba__osloveni=m.Osoba.OSLOVENI_MUZSKE) + zeny = Resitel.objects.filter(osoba__osloveni=m.Osoba.OSLOVENI_ZENSKE) + return render(request, 'various/stav_databaze.html', { + # 'nastaveni': nastaveni, + 'problemy': problemy, + + 'resitele': Resitel.objects.all(), + 'muzi': muzi, + 'zeny': zeny, + 'jmena_muzu': histogram([r.osoba.jmeno for r in muzi]), + 'jmena_zen': histogram([r.osoba.jmeno for r in zeny]), + }) diff --git a/various/views/generic.py b/various/views/generic.py new file mode 100644 index 00000000..b18178fb --- /dev/null +++ b/various/views/generic.py @@ -0,0 +1,29 @@ +import django.views + + +def viewMethodSwitch(get, post): + """ + Vrátí view, který zavolá různé jiné views podle toho, kterou metodou je zavolán. + + Inspirováno https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#an-alternative-better-solution, jen jsem to udělal genericky. + + Parametry: + post view pro metodu POST + get view pro metodu GET + + V obou případech se míní už view jakožto funkce, takže u class-based views se už má použít .as_view() + + TODO: Podpora i pro metodu HEAD? A možná i pro FILES? + """ + + theGetView = get + thePostView = post + + class NewView(django.views.View): + def get(self, request, *args, **kwargs): + return theGetView(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + return thePostView(request, *args, **kwargs) + + return NewView.as_view() diff --git a/various/views/pomocne.py b/various/views/pomocne.py new file mode 100644 index 00000000..42547467 --- /dev/null +++ b/various/views/pomocne.py @@ -0,0 +1,26 @@ +""" +Stránky, které se mi nepovedlo lépe zařadit. + +Oproti `./final.py` se tyto views importují odjinud +tedy ideálně neimportovat sem nic od jinud +""" + +from typing import Sequence + +from django.shortcuts import render +from django.urls import reverse + + +# Interní, nemá se nikdy objevit v urls (jinak to účastníci vytrolí) +def formularOKView(request, text='', dalsi_odkazy: Sequence[tuple[str, str]] = ()): + template_name = 'seminar/formular_ok.html' + odkazy = list(dalsi_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, + 'text': text, + } + return render(request, template_name, context) diff --git a/vyroci/urls.py b/vyroci/urls.py index 69132f45..44215a46 100644 --- a/vyroci/urls.py +++ b/vyroci/urls.py @@ -1,6 +1,6 @@ from django.urls import path -from seminar.utils import org_required +from personalni.utils import org_required from .views import VyrociView, VyrociListView urlpatterns = [ diff --git a/vyroci/views.py b/vyroci/views.py index 207ed619..455d6e25 100644 --- a/vyroci/views.py +++ b/vyroci/views.py @@ -1,7 +1,7 @@ from django.views.generic import FormView, ListView from seminar.models import Osoba -from seminar.views import formularOKView +from various.views.pomocne import formularOKView from .forms import UcastnikVyrociForm from .models import UcastnikVyroci diff --git a/vysledkovky/utils.py b/vysledkovky/utils.py index 2036b9d3..7cd914f4 100644 --- a/vysledkovky/utils.py +++ b/vysledkovky/utils.py @@ -4,7 +4,7 @@ from typing import Union, Iterable # TODO: s pythonem 3.10 přepsat na '|' import seminar.models as m from django.db.models import Q, Sum -from seminar.utils import resi_v_rocniku +from tvorba.utils import resi_v_rocniku ROCNIK_ZRUSENI_TEMAT = 25