From 9920465f994aa5fc8d6c0a6b4ab1983cfc30afae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Sun, 4 Aug 2024 16:57:39 +0200 Subject: [PATCH 01/18] Split novinek --- mamweb/urls.py | 3 +++ novinky/__init__.py | 3 +++ .../templates/novinky}/novinky.html | 0 .../templates/novinky}/stare_novinky.html | 2 +- novinky/urls.py | 7 ++++++ novinky/views.py | 23 +++++++++++++++++++ .../seminar/titulnistrana/titulnistrana.html | 2 +- seminar/urls.py | 1 - seminar/views/views_all.py | 19 +-------------- 9 files changed, 39 insertions(+), 21 deletions(-) rename {seminar/templates/seminar => novinky/templates/novinky}/novinky.html (100%) rename {seminar/templates/seminar => novinky/templates/novinky}/stare_novinky.html (78%) create mode 100644 novinky/urls.py diff --git a/mamweb/urls.py b/mamweb/urls.py index 4b870fec..95fd90a4 100644 --- a/mamweb/urls.py +++ b/mamweb/urls.py @@ -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')), 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/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/seminar/templates/seminar/titulnistrana/titulnistrana.html b/seminar/templates/seminar/titulnistrana/titulnistrana.html index f79bbbf1..7a6e3185 100644 --- a/seminar/templates/seminar/titulnistrana/titulnistrana.html +++ b/seminar/templates/seminar/titulnistrana/titulnistrana.html @@ -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/urls.py b/seminar/urls.py index f740e6a4..d64320c1 100644 --- a/seminar/urls.py +++ b/seminar/urls.py @@ -77,7 +77,6 @@ urlpatterns = [ 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( diff --git a/seminar/views/views_all.py b/seminar/views/views_all.py index 31cfbe17..f58e8706 100644 --- a/seminar/views/views_all.py +++ b/seminar/views/views_all.py @@ -18,6 +18,7 @@ from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, \ #from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva from seminar import utils from treenode import treelib +from novinky.views import spravne_novinky import treenode.templatetags as tnltt import treenode.serializers as vr from vysledkovky.utils import body_resitelu, VysledkovkaCisla, \ @@ -218,18 +219,6 @@ def ZadaniAktualniVysledkovkaView(request): ### 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. @@ -264,12 +253,6 @@ class TitulniStranaView(generic.ListView): 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 -- 2.39.5 From 0cab9a828600d8ba06f47db241ce9489b3c01fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Sun, 4 Aug 2024 17:01:44 +0200 Subject: [PATCH 02/18] =?UTF-8?q?P=C5=99esun=20csrf=5Ferror?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mamweb/settings_common.py | 2 +- various/views/__init__.py | 0 various/{views.py => views/csrf.py} | 0 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 various/views/__init__.py rename various/{views.py => views/csrf.py} (100%) diff --git a/mamweb/settings_common.py b/mamweb/settings_common.py index f737be1e..30d5bedb 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 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 -- 2.39.5 From 5f7ec853fa6a8998822adbddd851c4c588c97ced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Sun, 4 Aug 2024 17:41:24 +0200 Subject: [PATCH 03/18] =?UTF-8?q?P=C5=99esun=20n=C3=A1hodn=C3=BDch=20views?= =?UTF-8?q?=20do=20various?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mamweb/urls.py | 3 + odevzdavatko/views.py | 2 +- personalni/views.py | 2 +- .../templates/seminar/jakresit/jak-resit.html | 16 ---- seminar/urls.py | 8 -- seminar/views/views_all.py | 69 ------------------ sifrovacka/views.py | 2 +- .../templates/various}/formular_ok.html | 0 .../templates/various/jakresit/jak-resit.html | 16 ++++ .../various}/jakresit/jakresit_1.svg | 0 .../various}/jakresit/jakresit_2.svg | 0 .../various}/jakresit/jakresit_3.svg | 0 .../templates/various}/pracuje_se.html | 2 +- .../templates/various}/stav_databaze.html | 0 .../various}/titulnistrana/graph.svg | 0 .../various}/titulnistrana/titulnistrana.html | 2 +- various/urls.py | 9 +++ various/views/final.py | 73 +++++++++++++++++++ various/views/pomocne.py | 26 +++++++ vyroci/views.py | 2 +- 20 files changed, 133 insertions(+), 99 deletions(-) delete mode 100644 seminar/templates/seminar/jakresit/jak-resit.html rename {seminar/templates/seminar => various/templates/various}/formular_ok.html (100%) create mode 100644 various/templates/various/jakresit/jak-resit.html rename {seminar/templates/seminar => various/templates/various}/jakresit/jakresit_1.svg (100%) rename {seminar/templates/seminar => various/templates/various}/jakresit/jakresit_2.svg (100%) rename {seminar/templates/seminar => various/templates/various}/jakresit/jakresit_3.svg (100%) rename {seminar/templates/seminar => various/templates/various}/pracuje_se.html (85%) rename {seminar/templates/seminar => various/templates/various}/stav_databaze.html (100%) rename {seminar/templates/seminar => various/templates/various}/titulnistrana/graph.svg (100%) rename {seminar/templates/seminar => various/templates/various}/titulnistrana/titulnistrana.html (97%) create mode 100644 various/urls.py create mode 100644 various/views/final.py create mode 100644 various/views/pomocne.py diff --git a/mamweb/urls.py b/mamweb/urls.py index 95fd90a4..9d438d93 100644 --- a/mamweb/urls.py +++ b/mamweb/urls.py @@ -51,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/odevzdavatko/views.py b/odevzdavatko/views.py index e5de47c2..9215d3f8 100644 --- a/odevzdavatko/views.py +++ b/odevzdavatko/views.py @@ -21,7 +21,7 @@ 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 various.views.pomocne import formularOKView logger = logging.getLogger(__name__) diff --git a/personalni/views.py b/personalni/views.py index c2712b30..96ef2405 100644 --- a/personalni/views.py +++ b/personalni/views.py @@ -17,7 +17,7 @@ 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 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/urls.py b/seminar/urls.py index d64320c1..afc2cd1d 100644 --- a/seminar/urls.py +++ b/seminar/urls.py @@ -65,19 +65,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'), - # Dočasné & neodladěné: path( 'hidden/hromadne_pridani', diff --git a/seminar/views/views_all.py b/seminar/views/views_all.py index f58e8706..3c3d4b16 100644 --- a/seminar/views/views_all.py +++ b/seminar/views/views_all.py @@ -18,7 +18,6 @@ from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, \ #from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva from seminar import utils from treenode import treelib -from novinky.views import spravne_novinky import treenode.templatetags as tnltt import treenode.serializers as vr from vysledkovky.utils import body_resitelu, VysledkovkaCisla, \ @@ -37,7 +36,6 @@ from django.conf import settings import unicodedata import logging import time -from collections.abc import Sequence import http from seminar.utils import aktivniResitele @@ -226,33 +224,6 @@ 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 - ### Co je M&M @@ -652,47 +623,7 @@ class ClankyResitelView(generic.ListView): # 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/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/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 85% rename from seminar/templates/seminar/pracuje_se.html rename to various/templates/various/pracuje_se.html index 1a396534..e80fea23 100644 --- a/seminar/templates/seminar/pracuje_se.html +++ b/various/templates/various/pracuje_se.html @@ -10,7 +10,7 @@

Na této stránce velmi intenzivně pracujeme. Za dočasnou nedostupnost se omlouváme. - Zkuste přejít na titulní stránku + Zkuste přejít na titulní stránku nebo se podívat na aktuální zadání.

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 97% rename from seminar/templates/seminar/titulnistrana/titulnistrana.html rename to various/templates/various/titulnistrana/titulnistrana.html index 7a6e3185..10354c2f 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' %}
diff --git a/various/urls.py b/various/urls.py new file mode 100644 index 00000000..ae2d3042 --- /dev/null +++ b/various/urls.py @@ -0,0 +1,9 @@ +from django.urls import path +from .views.final import TitulniStranaView, JakResitView, StavDatabazeView +from seminar.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/final.py b/various/views/final.py new file mode 100644 index 00000000..a4c06f74 --- /dev/null +++ b/various/views/final.py @@ -0,0 +1,73 @@ +""" +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 +""" + +from django.shortcuts import get_object_or_404, render +from django.utils import timezone +from django.views import generic + +import novinky.views +import seminar.utils +import seminar.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 = seminar.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 StavDatabazeView(request): + # nastaveni = Nastaveni.objects.get() + problemy = seminar.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, 'various/stav_databaze.html', { + # 'nastaveni': nastaveni, + 'problemy': problemy, + + 'resitele': Resitel.objects.all(), + 'muzi': muzi, + 'zeny': zeny, + 'jmena_muzu': seminar.utils.histogram([r.osoba.jmeno for r in muzi]), + 'jmena_zen': seminar.utils.histogram([r.osoba.jmeno for r in zeny]), + }) 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/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 -- 2.39.5 From a6eebb2d59d35dc61e5a49cbc41b28d94234aa72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Sun, 4 Aug 2024 18:15:01 +0200 Subject: [PATCH 04/18] =?UTF-8?q?Seznam=20organiz=C3=A1tor=C5=AF=20do=20pe?= =?UTF-8?q?rson=C3=A1ln=C3=ADho?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/personalni}/organizatori.html | 0 personalni/urls.py | 12 +++++++ personalni/views.py | 29 +++++++++++++++++ seminar/urls.py | 4 --- seminar/views/views_all.py | 31 +------------------ 5 files changed, 42 insertions(+), 34 deletions(-) rename {seminar/templates/seminar/cojemam => personalni/templates/personalni}/organizatori.html (100%) diff --git a/seminar/templates/seminar/cojemam/organizatori.html b/personalni/templates/personalni/organizatori.html similarity index 100% rename from seminar/templates/seminar/cojemam/organizatori.html rename to personalni/templates/personalni/organizatori.html diff --git a/personalni/urls.py b/personalni/urls.py index 73a6f720..eae46257 100644 --- a/personalni/urls.py +++ b/personalni/urls.py @@ -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/views.py b/personalni/views.py index 96ef2405..da3ed07e 100644 --- a/personalni/views.py +++ b/personalni/views.py @@ -8,6 +8,7 @@ from django.contrib.auth.models import User, Permission, Group, AnonymousUser from django.contrib.auth.mixins import LoginRequiredMixin 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 @@ -23,6 +24,34 @@ 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') + class OrgoRozcestnikView(TemplateView): """ Zobrazí organizátorský rozcestník.""" diff --git a/seminar/urls.py b/seminar/urls.py index afc2cd1d..275f64ba 100644 --- a/seminar/urls.py +++ b/seminar/urls.py @@ -6,10 +6,6 @@ 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"), diff --git a/seminar/views/views_all.py b/seminar/views/views_all.py index 3c3d4b16..f838481c 100644 --- a/seminar/views/views_all.py +++ b/seminar/views/views_all.py @@ -13,7 +13,7 @@ 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 @@ -24,7 +24,6 @@ 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 @@ -224,34 +223,6 @@ def aktualni_temata(rocnik): return Tema.objects.filter(rocnik=rocnik, stav='zadany').order_by('kod') -### 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 -- 2.39.5 From ba2ea74a049f0167770a5d385261bb6e257c287c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Sun, 4 Aug 2024 18:21:50 +0200 Subject: [PATCH 05/18] =?UTF-8?q?Ob=C3=A1lky=20do=20person=C3=A1ln=C3=ADho?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/personalni}/obalky.tex | 0 personalni/views.py | 31 +++++++++++++++++++ seminar/views/views_all.py | 30 ++---------------- soustredeni/views.py | 4 +-- 4 files changed, 35 insertions(+), 30 deletions(-) rename {seminar/templates/seminar/archiv => personalni/templates/personalni}/obalky.tex (100%) 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/personalni/views.py b/personalni/views.py index da3ed07e..a4c410be 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,6 +11,7 @@ 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 @@ -53,6 +59,31 @@ class CojemamOrganizatoriStariView(generic.ListView): ).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('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 + + class OrgoRozcestnikView(TemplateView): """ Zobrazí organizátorský rozcestník.""" diff --git a/seminar/views/views_all.py b/seminar/views/views_all.py index f838481c..9f115687 100644 --- a/seminar/views/views_all.py +++ b/seminar/views/views_all.py @@ -8,7 +8,6 @@ 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 @@ -26,18 +25,15 @@ from vysledkovky.utils import body_resitelu, VysledkovkaCisla, \ from datetime import date, datetime 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 -import http from seminar.utils import aktivniResitele +import personalni.views # ze starého modelu #def verejna_temata(rocnik): @@ -485,31 +481,9 @@ 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)) + return personalni.views.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 - - 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 def TitulyViewRocnik(request, rocnik): diff --git a/soustredeni/views.py b/soustredeni/views.py index f150b6b8..4b8fb91f 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): -- 2.39.5 From 31b7cbb8d742e7cc9bea5574caf62f5125ee4f0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Sun, 4 Aug 2024 18:53:35 +0200 Subject: [PATCH 06/18] Tvorba (templates, admin, views) --- api/tests/test_skola_autocomplete.py | 1 - mamweb/settings_common.py | 1 + mamweb/urls.py | 4 +- seminar/admin.py | 165 +--------------- treenode/templates/treenode/orphanage.html | 2 +- tvorba/__init__.py | 0 tvorba/admin.py | 177 ++++++++++++++++++ tvorba/apps.py | 6 + tvorba/migrations/__init__.py | 0 .../templates/tvorba}/archiv/cisla.html | 0 .../templates/tvorba}/archiv/cislo.html | 0 .../tvorba}/archiv/cislo_vysledkovka.tex | 0 .../templates/tvorba}/archiv/odmeny.html | 0 .../templates/tvorba}/archiv/prispevek.html | 0 .../templates/tvorba}/archiv/problem.html | 0 .../tvorba}/archiv/problem_clanek.html | 0 .../tvorba}/archiv/problem_tema.html | 0 .../tvorba}/archiv/problem_uloha.html | 0 .../tvorba}/archiv/problem_uloha_tema.html | 0 .../templates/tvorba}/archiv/rocnik.html | 0 .../tvorba}/archiv/rocnik_vysledkovka.tex | 0 .../templates/tvorba}/archiv/temata.html | 0 .../templates/tvorba}/archiv/tituly.tex | 0 .../tvorba}/clanky/organizatorske_clanky.html | 0 .../tvorba}/clanky/resitelske_clanky.html | 0 .../templates/tvorba}/tematka/rozcestnik.html | 0 .../templates/tvorba}/tematka/toaletak.html | 0 .../tvorba}/zadani/AktualniVysledkovka.html | 0 .../tvorba}/zadani/AktualniZadani.html | 0 .../templates/tvorba}/zadani/Temata.html | 0 {seminar => tvorba}/urls.py | 2 +- {seminar => tvorba}/views/__init__.py | 0 {seminar => tvorba}/views/docasne.py | 0 {seminar => tvorba}/views/views_all.py | 38 ++-- various/admin.py | 5 + various/views/final.py | 4 +- 36 files changed, 215 insertions(+), 190 deletions(-) create mode 100644 tvorba/__init__.py create mode 100644 tvorba/admin.py create mode 100644 tvorba/apps.py create mode 100644 tvorba/migrations/__init__.py rename {seminar/templates/seminar => tvorba/templates/tvorba}/archiv/cisla.html (100%) rename {seminar/templates/seminar => tvorba/templates/tvorba}/archiv/cislo.html (100%) rename {seminar/templates/seminar => tvorba/templates/tvorba}/archiv/cislo_vysledkovka.tex (100%) rename {seminar/templates/seminar => tvorba/templates/tvorba}/archiv/odmeny.html (100%) rename {seminar/templates/seminar => tvorba/templates/tvorba}/archiv/prispevek.html (100%) rename {seminar/templates/seminar => tvorba/templates/tvorba}/archiv/problem.html (100%) rename {seminar/templates/seminar => tvorba/templates/tvorba}/archiv/problem_clanek.html (100%) rename {seminar/templates/seminar => tvorba/templates/tvorba}/archiv/problem_tema.html (100%) rename {seminar/templates/seminar => tvorba/templates/tvorba}/archiv/problem_uloha.html (100%) rename {seminar/templates/seminar => tvorba/templates/tvorba}/archiv/problem_uloha_tema.html (100%) rename {seminar/templates/seminar => tvorba/templates/tvorba}/archiv/rocnik.html (100%) rename {seminar/templates/seminar => tvorba/templates/tvorba}/archiv/rocnik_vysledkovka.tex (100%) rename {seminar/templates/seminar => tvorba/templates/tvorba}/archiv/temata.html (100%) rename {seminar/templates/seminar => tvorba/templates/tvorba}/archiv/tituly.tex (100%) rename {seminar/templates/seminar => tvorba/templates/tvorba}/clanky/organizatorske_clanky.html (100%) rename {seminar/templates/seminar => tvorba/templates/tvorba}/clanky/resitelske_clanky.html (100%) rename {seminar/templates/seminar => tvorba/templates/tvorba}/tematka/rozcestnik.html (100%) rename {seminar/templates/seminar => tvorba/templates/tvorba}/tematka/toaletak.html (100%) rename {seminar/templates/seminar => tvorba/templates/tvorba}/zadani/AktualniVysledkovka.html (100%) rename {seminar/templates/seminar => tvorba/templates/tvorba}/zadani/AktualniZadani.html (100%) rename {seminar/templates/seminar => tvorba/templates/tvorba}/zadani/Temata.html (100%) rename {seminar => tvorba}/urls.py (98%) rename {seminar => tvorba}/views/__init__.py (100%) rename {seminar => tvorba}/views/docasne.py (100%) rename {seminar => tvorba}/views/views_all.py (94%) diff --git a/api/tests/test_skola_autocomplete.py b/api/tests/test_skola_autocomplete.py index 36df97e8..f69669f0 100644 --- a/api/tests/test_skola_autocomplete.py +++ b/api/tests/test_skola_autocomplete.py @@ -1,7 +1,6 @@ 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 @tag('stejny-model-na-produkci') diff --git a/mamweb/settings_common.py b/mamweb/settings_common.py index 30d5bedb..078c3d4d 100644 --- a/mamweb/settings_common.py +++ b/mamweb/settings_common.py @@ -131,6 +131,7 @@ INSTALLED_APPS = ( # MaMweb 'mamweb', 'seminar', + 'tvorba', 'galerie', 'korektury', 'prednasky', diff --git a/mamweb/urls.py b/mamweb/urls.py index 9d438d93..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')), diff --git a/seminar/admin.py b/seminar/admin.py index f8768ddf..b806edab 100644 --- a/seminar/admin.py +++ b/seminar/admin.py @@ -1,173 +1,11 @@ from django.contrib import admin from django.db import models -from django.forms import widgets, 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 +from django.forms import widgets # Todo: reversion import seminar.models as m -admin.site.register(m.Rocnik) -admin.site.register(m.ZmrazenaVysledkovka) - -@admin.register(m.Deadline) -class DeadlineAdmin(admin.ModelAdmin): - actions = ['pregeneruj_vysledkovku'] - - # Nikomu nezobrazovat, ale superuživatelům se může hodit :-) - @admin.action(permissions=['bazmek'], description= 'Přegeneruj výsledkovky vybraných deadlinů') - 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 - extra = 0 - -class CisloForm(ModelForm): - class Meta: - model = m.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) - # errors = [] - # 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): - # 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 ch.uloha.stav not in \ - # (m.Problem.STAV_ZADANY, m.Problem.STAV_VYRESENY): - # errors.append(ValidationError('Úloha %(uloha)s není zadaná ani vyřešená', params={'uloha':ch.uloha})) - # if isinstance(ch, m.ReseniNode): - # for problem in ch.reseni.problem_set: - # if problem not in \ - # (m.Problem.STAV_ZADANY, m.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): - errors.append( - ValidationError('Úloha %(uloha)s není zadaná ani vyřešená', params={'uloha': ch})) - 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'))) - if self.cleaned_data.get('datum_vydani') == None: - self.add_error('datum_vydani','Číslo určené ke zveřejnění nemá nastavené datum vydání') - - if errors: - raise ValidationError(errors) - - return self.cleaned_data - - -@admin.register(m.Cislo) -class CisloAdmin(admin.ModelAdmin): - form = CisloForm - actions = ['force_publish', 'pregeneruj_vysledkovky'] - inlines = (DeadlineAdminInline,) - - def force_publish(self,request,queryset): - for cislo in queryset: - # cn = m.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 - # 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 - # ch.uloha.save() - # if isinstance(ch, m.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 - # 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 - 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 - hp.save() - - # TODO Řešení, vzoráky? - # TODO Konfera/Článek? - - cislo.verejne_db = True - cislo.save() - - force_publish.short_description = 'Zveřejnit vybraná čísla a všechny návrhy úloh v nich učinit zadanými' - - # Jen pro superuživatele - @admin.action(permissions=['bazmek'], description='Přegenerovat výsledkovky všech deadlinů vybraných čísel') - def pregeneruj_vysledkovky(self, req, qs): - 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) -class ProblemAdmin(PolymorphicParentModelAdmin): - base_model = m.Problem - child_models = [ - m.Tema, - m.Clanek, - m.Uloha, - m.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 - autocomplete_fields = ['nadproblem','autor','garant'] - filter_horizontal = ['opravovatele'] - - -@admin.register(m.Tema) -class TemaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin): - base_model = m.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(m.Konfera) -class KonferaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin): - base_model = m.Konfera - class TextAdminInline(admin.TabularInline): model = m.Text @@ -180,4 +18,3 @@ admin.site.register(m.Text) # admin.site.register(m.Pohadka) admin.site.register(m.Obrazek) -admin.site.register(m.Nastaveni, SingletonModelAdmin) 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/tvorba/__init__.py b/tvorba/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tvorba/admin.py b/tvorba/admin.py new file mode 100644 index 00000000..817c16d3 --- /dev/null +++ b/tvorba/admin.py @@ -0,0 +1,177 @@ +from django.contrib import admin +from django.db import models +from django.forms import widgets, ModelForm +from django.core.exceptions import ValidationError + +from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter +from django.utils.safestring import mark_safe + +# Todo: reversion + +import soustredeni.models + +from seminar.models.tvorba import Rocnik, ZmrazenaVysledkovka, Deadline, Uloha, Problem, Tema, Clanek, Cislo + +admin.site.register(Rocnik) +admin.site.register(ZmrazenaVysledkovka) + +@admin.register(Deadline) +class DeadlineAdmin(admin.ModelAdmin): + actions = ['pregeneruj_vysledkovku'] + + # Nikomu nezobrazovat, ale superuživatelům se může hodit :-) + @admin.action(permissions=['bazmek'], description= 'Přegeneruj výsledkovky vybraných deadlinů') + 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 = Deadline + extra = 0 + + +class CisloForm(ModelForm): + class Meta: + model = Cislo + fields = '__all__' + + def clean(self): + if self.cleaned_data.get('verejne_db') == False: + return self.cleaned_data + # cn = CisloNode.objects.get(cislo=self.instance) + # errors = [] + # for ch in tl.all_children(cn): + # if isinstance(ch, TemaVCisleNode): + # if ch.tema.stav not in \ + # (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, UlohaZadaniNode) or isinstance(ch, UlohaVzorakNode): + # if ch.uloha.stav not in \ + # (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, ReseniNode): + # for problem in ch.reseni.problem_set: + # if problem not in \ + # (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 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: + 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'))) + if self.cleaned_data.get('datum_vydani') == None: + self.add_error('datum_vydani','Číslo určené ke zveřejnění nemá nastavené datum vydání') + + if errors: + raise ValidationError(errors) + + return self.cleaned_data + + +@admin.register(Cislo) +class CisloAdmin(admin.ModelAdmin): + form = CisloForm + actions = ['force_publish', 'pregeneruj_vysledkovky'] + inlines = (DeadlineAdminInline,) + + def force_publish(self,request,queryset): + for cislo in queryset: + # cn = CisloNode.objects.get(cislo=cislo) + # for ch in tl.all_children(cn): + # 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, 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, ReseniNode): + # for problem in ch.reseni.problem_set: + # if problem not in (Problem.STAV_ZADANY, Problem.STAV_VYRESENY): + # problem.stav = Problem.STAV_ZADANY + # problem.save() + + 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 (Problem.STAV_ZADANY, Problem.STAV_VYRESENY): + hp.stav = Problem.STAV_ZADANY + hp.save() + + # TODO Řešení, vzoráky? + # TODO Konfera/Článek? + + cislo.verejne_db = True + cislo.save() + + force_publish.short_description = 'Zveřejnit vybraná čísla a všechny návrhy úloh v nich učinit zadanými' + + # Jen pro superuživatele + @admin.action(permissions=['bazmek'], description='Přegenerovat výsledkovky všech deadlinů vybraných čísel') + def pregeneruj_vysledkovky(self, req, qs): + 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(Problem) +class ProblemAdmin(PolymorphicParentModelAdmin): + base_model = Problem + child_models = [ + 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 + autocomplete_fields = ['nadproblem','autor','garant'] + filter_horizontal = ['opravovatele'] + + +@admin.register(Tema) +class TemaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin): + base_model = Tema + + +@admin.register(Clanek) +class ClanekAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin): + base_model = Clanek + + +@admin.register(Uloha) +class UlohaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin): + base_model = Uloha + + +@admin.register(soustredeni.models.Konfera) +class KonferaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin): + base_model = soustredeni.models.Konfera + 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/tvorba/migrations/__init__.py b/tvorba/migrations/__init__.py new file mode 100644 index 00000000..e69de29b 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 100% rename from seminar/templates/seminar/archiv/rocnik.html rename to tvorba/templates/tvorba/archiv/rocnik.html 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 100% rename from seminar/templates/seminar/tematka/rozcestnik.html rename to tvorba/templates/tvorba/tematka/rozcestnik.html 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/urls.py b/tvorba/urls.py similarity index 98% rename from seminar/urls.py rename to tvorba/urls.py index 275f64ba..b5ebed98 100644 --- a/seminar/urls.py +++ b/tvorba/urls.py @@ -1,6 +1,6 @@ from django.urls import path, include, re_path from . import views -from .utils import org_required +from seminar.utils import org_required urlpatterns = [ # path('aktualni/temata/', views.TemataRozcestnikView), diff --git a/seminar/views/__init__.py b/tvorba/views/__init__.py similarity index 100% rename from seminar/views/__init__.py rename to tvorba/views/__init__.py 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 94% rename from seminar/views/views_all.py rename to tvorba/views/views_all.py index 9f115687..ac8b5477 100644 --- a/seminar/views/views_all.py +++ b/tvorba/views/views_all.py @@ -82,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): @@ -100,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, }, @@ -111,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, @@ -126,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, # } @@ -145,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): @@ -181,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): @@ -205,7 +205,7 @@ def ZadaniAktualniVysledkovkaView(request): context['rocnik'] = rocnik return render( request, - 'seminar/zadani/AktualniVysledkovka.html', + 'tvorba/zadani/AktualniVysledkovka.html', context ) @@ -224,7 +224,7 @@ def aktualni_temata(rocnik): 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) @@ -252,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): @@ -292,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) @@ -308,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): @@ -351,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): @@ -362,7 +362,7 @@ 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) @@ -408,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' @@ -439,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): @@ -473,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' @@ -517,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") @@ -548,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): @@ -564,7 +564,7 @@ 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') 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/views/final.py b/various/views/final.py index a4c06f74..12a18250 100644 --- a/various/views/final.py +++ b/various/views/final.py @@ -11,7 +11,7 @@ from django.views import generic import novinky.views import seminar.utils -import seminar.views +import tvorba.views from personalni.models import Resitel from seminar import models as m @@ -34,7 +34,7 @@ class TitulniStranaView(generic.ListView): # Aktuální témata nazvy_a_odkazy_na_aktualni_temata = [] - akt_temata = seminar.views.aktualni_temata(nastaveni.aktualni_rocnik) + 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 -- 2.39.5 From 731c795ee68fd814ae23edea4659ca3884dd7726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Sun, 4 Aug 2024 19:01:09 +0200 Subject: [PATCH 07/18] Text a Obrazek --- seminar/admin.py | 20 -------------------- treenode/admin.py | 11 +++++++++++ tvorba/admin.py | 4 ++-- 3 files changed, 13 insertions(+), 22 deletions(-) delete mode 100644 seminar/admin.py diff --git a/seminar/admin.py b/seminar/admin.py deleted file mode 100644 index b806edab..00000000 --- a/seminar/admin.py +++ /dev/null @@ -1,20 +0,0 @@ -from django.contrib import admin -from django.db import models -from django.forms import widgets - -# Todo: reversion - -import seminar.models as m - - -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.Pohadka) -admin.site.register(m.Obrazek) 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/tvorba/admin.py b/tvorba/admin.py index 817c16d3..e6c2c64b 100644 --- a/tvorba/admin.py +++ b/tvorba/admin.py @@ -1,6 +1,5 @@ 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 @@ -175,3 +174,4 @@ class UlohaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin): class KonferaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin): base_model = soustredeni.models.Konfera +# admin.site.register(m.Pohadka) -- 2.39.5 From 95ab0ee1dcabbe06f6b6f0ad6ad489f1cc2e62fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Sun, 4 Aug 2024 19:08:24 +0200 Subject: [PATCH 08/18] =?UTF-8?q?Commandy=20zat=C3=ADm=20do=20various?= =?UTF-8?q?=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- {seminar => various}/management/__init__.py | 0 {seminar => various}/management/commands/__init__.py | 0 {seminar => various}/management/commands/generate_thumbnails.py | 0 {seminar => various}/management/commands/load_org_permissions.py | 0 {seminar => various}/management/commands/nukedb.py | 0 .../management/commands/pregeneruj_zmrazene_vysledkovky.py | 0 {seminar => various}/management/commands/save_org_permissions.py | 0 {seminar => various}/management/commands/testdata.py | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename {seminar => various}/management/__init__.py (100%) rename {seminar => various}/management/commands/__init__.py (100%) rename {seminar => various}/management/commands/generate_thumbnails.py (100%) rename {seminar => various}/management/commands/load_org_permissions.py (100%) rename {seminar => various}/management/commands/nukedb.py (100%) rename {seminar => various}/management/commands/pregeneruj_zmrazene_vysledkovky.py (100%) rename {seminar => various}/management/commands/save_org_permissions.py (100%) rename {seminar => various}/management/commands/testdata.py (100%) diff --git a/seminar/management/__init__.py b/various/management/__init__.py similarity index 100% rename from seminar/management/__init__.py rename to various/management/__init__.py diff --git a/seminar/management/commands/__init__.py b/various/management/commands/__init__.py similarity index 100% rename from seminar/management/commands/__init__.py rename to various/management/commands/__init__.py 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 100% rename from seminar/management/commands/testdata.py rename to various/management/commands/testdata.py -- 2.39.5 From 18364eb53122c2d9ac8c4aed1b91eebe7a577f38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Sun, 4 Aug 2024 19:15:15 +0200 Subject: [PATCH 09/18] seminar/static --- .../static/personalni}/lisak.pdf | Bin .../static/personalni}/no-photo.png | Bin personalni/templates/personalni/organizatori.html | 2 +- personalni/views.py | 2 +- .../static/soustredeni}/logomm.pdf | Bin soustredeni/views.py | 2 +- .../images => tvorba/static/tvorba}/no-picture.png | Bin .../static/tvorba}/tema-bez-obrazku.png | Bin tvorba/templates/tvorba/archiv/rocnik.html | 4 ++-- tvorba/templates/tvorba/tematka/rozcestnik.html | 2 +- tvorba/views/views_all.py | 2 +- 11 files changed, 7 insertions(+), 7 deletions(-) rename {seminar/static/seminar => personalni/static/personalni}/lisak.pdf (100%) rename {seminar/static/images => personalni/static/personalni}/no-photo.png (100%) rename {seminar/static/images => soustredeni/static/soustredeni}/logomm.pdf (100%) rename {seminar/static/images => tvorba/static/tvorba}/no-picture.png (100%) rename {seminar/static/images => tvorba/static/tvorba}/tema-bez-obrazku.png (100%) 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/personalni/templates/personalni/organizatori.html b/personalni/templates/personalni/organizatori.html index 799bbe49..56fb351e 100644 --- a/personalni/templates/personalni/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/views.py b/personalni/views.py index a4c410be..12768c34 100644 --- a/personalni/views.py +++ b/personalni/views.py @@ -76,7 +76,7 @@ def obalkyView(request, resitele): with tempfile.TemporaryDirectory() as tempdir: with open(tempdir+"/obalky.tex", "w") as texfile: texfile.write(tex.decode()) - shutil.copy(find('seminar/lisak.pdf'), tempdir) + shutil.copy(find('personalni/lisak.pdf'), tempdir) subprocess.call(["pdflatex", "obalky.tex"], cwd=tempdir) with open(tempdir+"/obalky.pdf", "rb") as pdffile: 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/views.py b/soustredeni/views.py index 4b8fb91f..bbcf67c4 100644 --- a/soustredeni/views.py +++ b/soustredeni/views.py @@ -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/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/tvorba/templates/tvorba/archiv/rocnik.html b/tvorba/templates/tvorba/archiv/rocnik.html index fd2a99b6..9e070e82 100644 --- a/tvorba/templates/tvorba/archiv/rocnik.html +++ b/tvorba/templates/tvorba/archiv/rocnik.html @@ -34,7 +34,7 @@ {% if c.titulka_nahled %} {{ c.kod }} {% else %} - {% load static %} no-picture + {% load static %} no-picture {% endif %} @@ -80,7 +80,7 @@ {% if c.titulka_nahled %} {{ c.kod }} {% else %} - {% load static %} no-picture + {% load static %} no-picture {% endif %} diff --git a/tvorba/templates/tvorba/tematka/rozcestnik.html b/tvorba/templates/tvorba/tematka/rozcestnik.html index 605a6549..fcfba59e 100644 --- a/tvorba/templates/tvorba/tematka/rozcestnik.html +++ b/tvorba/templates/tvorba/tematka/rozcestnik.html @@ -35,7 +35,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/tvorba/views/views_all.py b/tvorba/views/views_all.py index ac8b5477..b4bbad92 100644 --- a/tvorba/views/views_all.py +++ b/tvorba/views/views_all.py @@ -237,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 -- 2.39.5 From 85c3969c50eac28758479d1dd942a322c82fc12c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Sun, 4 Aug 2024 19:21:46 +0200 Subject: [PATCH 10/18] seminar/templatetags --- .../templates/odevzdavatko/tabulka.html | 1 - seminar/templatetags/utils.py | 19 ------------------- {seminar => tvorba}/templatetags/__init__.py | 0 {seminar => tvorba}/templatetags/deadliny.py | 0 {seminar => various}/templatetags/tex.py | 0 5 files changed, 20 deletions(-) delete mode 100644 seminar/templatetags/utils.py rename {seminar => tvorba}/templatetags/__init__.py (100%) rename {seminar => tvorba}/templatetags/deadliny.py (100%) rename {seminar => various}/templatetags/tex.py (100%) diff --git a/odevzdavatko/templates/odevzdavatko/tabulka.html b/odevzdavatko/templates/odevzdavatko/tabulka.html index 7ee90ea9..cfbe0e6f 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/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/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/seminar/templatetags/tex.py b/various/templatetags/tex.py similarity index 100% rename from seminar/templatetags/tex.py rename to various/templatetags/tex.py -- 2.39.5 From c34716e134491b406b2ab2d28332427d14ba60d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Mon, 5 Aug 2024 11:46:38 +0200 Subject: [PATCH 11/18] seminar/utils.py --- aesop/views.py | 2 +- api/tests/test_skola_autocomplete.py | 2 +- api/urls.py | 2 +- ...-12-06-testovani_dokumentace_codereview.md | 2 +- galerie/urls.py | 2 +- korektury/urls.py | 2 +- odevzdavatko/urls.py | 4 +- odevzdavatko/utils.py | 11 + odevzdavatko/views.py | 2 +- personalni/urls.py | 2 +- personalni/utils.py | 175 +++++++- prednasky/urls.py | 2 +- seminar/models/odevzdavatko.py | 2 +- seminar/models/tvorba.py | 3 +- seminar/utils.py | 387 ------------------ sifrovacka/urls.py | 2 +- soustredeni/urls.py | 2 +- tvorba/urls.py | 2 +- tvorba/utils.py | 89 ++++ tvorba/views/views_all.py | 8 +- various/urls.py | 2 +- various/views/final.py | 95 ++++- various/views/generic.py | 29 ++ vyroci/urls.py | 2 +- vysledkovky/utils.py | 2 +- 25 files changed, 417 insertions(+), 416 deletions(-) create mode 100644 odevzdavatko/utils.py delete mode 100644 seminar/utils.py create mode 100644 tvorba/utils.py create mode 100644 various/views/generic.py 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 f69669f0..75019983 100644 --- a/api/tests/test_skola_autocomplete.py +++ b/api/tests/test_skola_autocomplete.py @@ -1,7 +1,7 @@ from django.test import TestCase, tag from django.urls import reverse import seminar.models as m -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/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 9215d3f8..cbe9019e 100644 --- a/odevzdavatko/views.py +++ b/odevzdavatko/views.py @@ -20,7 +20,7 @@ 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 tvorba.utils import resi_v_rocniku from various.views.pomocne import formularOKView logger = logging.getLogger(__name__) diff --git a/personalni/urls.py b/personalni/urls.py index eae46257..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( 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/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/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/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/soustredeni/urls.py b/soustredeni/urls.py index 2e5a6136..92cfad18 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/tvorba/urls.py b/tvorba/urls.py index b5ebed98..e662491c 100644 --- a/tvorba/urls.py +++ b/tvorba/urls.py @@ -1,6 +1,6 @@ from django.urls import path, include, re_path from . import views -from seminar.utils import org_required +from personalni.utils import org_required urlpatterns = [ # path('aktualni/temata/', views.TemataRozcestnikView), 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/views_all.py b/tvorba/views/views_all.py index b4bbad92..f960aac8 100644 --- a/tvorba/views/views_all.py +++ b/tvorba/views/views_all.py @@ -15,7 +15,6 @@ 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 seminar import utils from treenode import treelib import treenode.templatetags as tnltt import treenode.serializers as vr @@ -32,9 +31,10 @@ import unicodedata import logging import time -from seminar.utils import aktivniResitele import personalni.views +from .. import utils + # ze starého modelu #def verejna_temata(rocnik): # """ @@ -368,7 +368,7 @@ class OdmenyView(generic.TemplateView): 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) @@ -481,7 +481,7 @@ class RocnikVysledkovkaView(RocnikView): def cisloObalkyView(request, rocnik, cislo): realne_cislo = get_object_or_404(Cislo, poradi=cislo, rocnik__rocnik=rocnik) - return personalni.views.obalkyView(request, aktivniResitele(realne_cislo)) + return personalni.views.obalkyView(request, utils.aktivniResitele(realne_cislo)) diff --git a/various/urls.py b/various/urls.py index ae2d3042..a3f03ade 100644 --- a/various/urls.py +++ b/various/urls.py @@ -1,6 +1,6 @@ from django.urls import path from .views.final import TitulniStranaView, JakResitView, StavDatabazeView -from seminar.utils import org_required +from personalni.utils import org_required urlpatterns = [ path('', TitulniStranaView.as_view(), name='titulni_strana'), diff --git a/various/views/final.py b/various/views/final.py index 12a18250..de23a718 100644 --- a/various/views/final.py +++ b/various/views/final.py @@ -4,13 +4,15 @@ 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 seminar.utils +import treenode.treelib as t import tvorba.views from personalni.models import Resitel from seminar import models as m @@ -56,9 +58,94 @@ class JakResitView(generic.ListView): ### 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 = seminar.utils.seznam_problemu() + 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', { @@ -68,6 +155,6 @@ def StavDatabazeView(request): 'resitele': Resitel.objects.all(), 'muzi': muzi, 'zeny': zeny, - 'jmena_muzu': seminar.utils.histogram([r.osoba.jmeno for r in muzi]), - 'jmena_zen': seminar.utils.histogram([r.osoba.jmeno for r in 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/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/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 -- 2.39.5 From 0b0a939de5c33110b0870e0d48942a0a9cd1230b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Fri, 2 Aug 2024 19:38:06 +0200 Subject: [PATCH 12/18] =?UTF-8?q?Odd=C4=9Blen=C3=AD=20generov=C3=A1n=C3=AD?= =?UTF-8?q?=20testdat=20k=20sous=20v=C4=9Bcem?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/testutils.py | 52 +++-------------------------------- soustredeni/testutils.py | 59 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 48 deletions(-) create mode 100644 soustredeni/testutils.py diff --git a/seminar/testutils.py b/seminar/testutils.py index be7f3677..51f3f362 100644 --- a/seminar/testutils.py +++ b/seminar/testutils.py @@ -10,13 +10,15 @@ 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 +from seminar.models import Skola, Resitel, Rocnik, Cislo, Deadline, Problem, Reseni, PrilohaReseni, Nastaveni, Osoba, Organizator, Prijemce, Tema, Uloha, 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 +from soustredeni.testutils import gen_soustredeni, gen_konfery + User = django.contrib.auth.get_user_model() zlinska = None # tohle bude speciální škola, které později dodáme kontaktní osobu @@ -352,30 +354,6 @@ def gen_ulohy_do_cisla(rnd, organizatori, resitele, rocnik_cisla, rocniky, size) 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)) @@ -390,28 +368,6 @@ def gen_rocniky(last_rocnik, size): 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...') @@ -881,7 +837,7 @@ def create_test_data(size = 6, rnd = None): gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori, resitele) #generování soustředění - soustredeni = gen_soustredeni(rnd, resitele, organizatori) + soustredeni = gen_soustredeni(size, rnd, resitele, organizatori) #generování konfer konfery = gen_konfery(size, rnd, organizatori, resitele, soustredeni) diff --git a/soustredeni/testutils.py b/soustredeni/testutils.py new file mode 100644 index 00000000..12a48378 --- /dev/null +++ b/soustredeni/testutils.py @@ -0,0 +1,59 @@ +import logging +import datetime + +import lorem + +from .models import Soustredeni, Konfera +import seminar.models.tvorba as am + +logger = logging.getLogger(__name__) + + +def gen_soustredeni(size, 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=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) + # 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_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 -- 2.39.5 From 6a781323e0e01f4b791be44f2289c0a925c91848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Fri, 2 Aug 2024 20:05:19 +0200 Subject: [PATCH 13/18] =?UTF-8?q?Typov=C3=A9=20anotace=20a=20dal=C5=A1?= =?UTF-8?q?=C3=AD=20detaily=20v=20generov=C3=A1n=C3=AD=20testdat=20k=20sou?= =?UTF-8?q?stredeni?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/testutils.py | 4 ++-- soustredeni/testutils.py | 22 +++++++++++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/seminar/testutils.py b/seminar/testutils.py index 51f3f362..b7bd61e2 100644 --- a/seminar/testutils.py +++ b/seminar/testutils.py @@ -837,10 +837,10 @@ def create_test_data(size = 6, rnd = None): gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori, resitele) #generování soustředění - soustredeni = gen_soustredeni(size, rnd, resitele, organizatori) + soustredeni = gen_soustredeni(size, resitele, organizatori, rnd=rnd) #generování konfer - konfery = gen_konfery(size, rnd, organizatori, resitele, soustredeni) + konfery = gen_konfery(size, organizatori, resitele, soustredeni, rnd=rnd) # vytvoreni pdf ke korekturam create_test_pdf(rnd, organizatori) diff --git a/soustredeni/testutils.py b/soustredeni/testutils.py index 12a48378..51bbb135 100644 --- a/soustredeni/testutils.py +++ b/soustredeni/testutils.py @@ -1,16 +1,25 @@ 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, rnd, resitele, organizatori): - logger.info('Generuji soustředění...') +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?) @@ -35,8 +44,15 @@ def gen_soustredeni(size, rnd, resitele, organizatori): return soustredeni -def gen_konfery(size, rnd, organizatori, resitele, soustredeni): +def gen_konfery( + size: int, + organizatori: Sequence[pm.Organizator], + resitele: Sequence[pm.Resitel], + soustredeni: Sequence[Soustredeni], + 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?) -- 2.39.5 From bf748b55ee7145ee7e06a87e6adee40647aa41d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Fri, 2 Aug 2024 20:15:02 +0200 Subject: [PATCH 14/18] =?UTF-8?q?Odstran=C4=9Bn=20zakomentovan=C3=BD=20zby?= =?UTF-8?q?te=C4=8Dn=C4=9B=20slo=C5=BEit=C3=BD=20k=C3=B3d?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- soustredeni/testutils.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/soustredeni/testutils.py b/soustredeni/testutils.py index 51bbb135..6e406530 100644 --- a/soustredeni/testutils.py +++ b/soustredeni/testutils.py @@ -33,12 +33,8 @@ def gen_soustredeni( 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 @@ -68,8 +64,6 @@ def gen_konfery( 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 -- 2.39.5 From d952ab13a5c8bbd39b1c660400c4cd6f0ba9b81c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Fri, 2 Aug 2024 20:17:31 +0200 Subject: [PATCH 15/18] =?UTF-8?q?Generov=C3=A1n=C3=AD=20konfer=20s=20konkr?= =?UTF-8?q?=C3=A9tn=C3=ADmi=20=C5=99e=C5=A1iteli?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/testutils.py | 2 +- soustredeni/testutils.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/seminar/testutils.py b/seminar/testutils.py index b7bd61e2..18bbbe2e 100644 --- a/seminar/testutils.py +++ b/seminar/testutils.py @@ -840,7 +840,7 @@ def create_test_data(size = 6, rnd = None): soustredeni = gen_soustredeni(size, resitele, organizatori, rnd=rnd) #generování konfer - konfery = gen_konfery(size, organizatori, resitele, soustredeni, rnd=rnd) + konfery = gen_konfery(size, organizatori, soustredeni, rnd=rnd) # vytvoreni pdf ke korekturam create_test_pdf(rnd, organizatori) diff --git a/soustredeni/testutils.py b/soustredeni/testutils.py index 6e406530..52e81d1c 100644 --- a/soustredeni/testutils.py +++ b/soustredeni/testutils.py @@ -43,8 +43,8 @@ def gen_soustredeni( def gen_konfery( size: int, organizatori: Sequence[pm.Organizator], - resitele: Sequence[pm.Resitel], soustredeni: Sequence[Soustredeni], + resitele: Sequence[pm.Resitel] = None, rnd: random.Random = None, ) -> Sequence[Konfera]: logger.info('Generuji konfery (size={})...'.format(size)) @@ -61,7 +61,7 @@ def gen_konfery( garant=rnd.choice(organizatori), soustredeni=rnd.choice(soustredeni), typ_prezentace=rnd.choice(['veletrh', 'prezentace'])) - ucastnici_sous = list(konfera.soustredeni.ucastnici.all()) + 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() -- 2.39.5 From 348096024eb89ca1797b64c82fb88feb95714ffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Tue, 6 Aug 2024 02:33:53 +0200 Subject: [PATCH 16/18] seminar/testutils.py --- novinky/testutils.py | 27 ++ odevzdavatko/testutils.py | 40 +++ personalni/testutils.py | 235 +++++++++++++++ {seminar => tvorba}/testutils.py | 370 +----------------------- various/management/commands/testdata.py | 4 +- various/testutils.py | 135 +++++++++ 6 files changed, 443 insertions(+), 368 deletions(-) create mode 100644 novinky/testutils.py create mode 100644 odevzdavatko/testutils.py create mode 100644 personalni/testutils.py rename {seminar => tvorba}/testutils.py (53%) create mode 100644 various/testutils.py 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/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/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/seminar/testutils.py b/tvorba/testutils.py similarity index 53% rename from seminar/testutils.py rename to tvorba/testutils.py index 18bbbe2e..18440679 100644 --- a/seminar/testutils.py +++ b/tvorba/testutils.py @@ -1,216 +1,22 @@ +# FIXME vypreparovat treenode + 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, Osoba, Organizator, Prijemce, Tema, Uloha, TextNode, UlohaVzorakNode, RocnikNode, CisloNode, TemaVCisleNode, Text, Hodnoceni, UlohaZadaniNode, Novinky, TreeNode +from seminar.models import Rocnik, Cislo, Deadline, Problem, Tema, Uloha, TextNode, UlohaVzorakNode, RocnikNode, CisloNode, TemaVCisleNode, Text, UlohaZadaniNode 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 -from soustredeni.testutils import gen_soustredeni, gen_konfery - - -User = django.contrib.auth.get_user_model() -zlinska = None # tohle bude speciální škola, které později dodáme kontaktní osobu +from odevzdavatko.testutils import gen_reseni_ulohy 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 = [] +User = django.contrib.auth.get_user_model() - - 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): @@ -282,42 +88,6 @@ def gen_vzoroveho_reseni_ulohy(rnd, organizatori, uloha, 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)) @@ -658,22 +428,6 @@ def gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori, 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 @@ -750,117 +504,3 @@ def gen_clanek(rnd, organizatori, resitele): 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(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/management/commands/testdata.py b/various/management/commands/testdata.py index d9ce8cfb..8f591fa5 100644 --- a/various/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/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]) -- 2.39.5 From 8ff66cb63115b750af25da0f50122a22e4fe4ea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Tue, 22 Oct 2024 20:15:58 +0200 Subject: [PATCH 17/18] WTF? --- various/templates/various/pracuje_se.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/various/templates/various/pracuje_se.html b/various/templates/various/pracuje_se.html index e80fea23..1a396534 100644 --- a/various/templates/various/pracuje_se.html +++ b/various/templates/various/pracuje_se.html @@ -10,7 +10,7 @@

Na této stránce velmi intenzivně pracujeme. Za dočasnou nedostupnost se omlouváme. - Zkuste přejít na titulní stránku + Zkuste přejít na titulní stránku nebo se podívat na aktuální zadání.

-- 2.39.5 From e443ecf33d519bfd0fd7b6acf39d2c023c012ab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Tue, 22 Oct 2024 21:23:11 +0200 Subject: [PATCH 18/18] views_all do __init__ --- tvorba/views/__init__.py | 584 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 582 insertions(+), 2 deletions(-) diff --git a/tvorba/views/__init__.py b/tvorba/views/__init__.py index 8db4424b..823ddd96 100644 --- a/tvorba/views/__init__.py +++ b/tvorba/views/__init__.py @@ -1,4 +1,584 @@ -from .views_all import * - # Dočsasné views from .docasne import * + +# Zbytek + +from django.shortcuts import get_object_or_404, render +from django.http import HttpResponse +from django.urls import reverse +from django.core.exceptions import ObjectDoesNotExist +from django.views import generic +from django.utils.translation import gettext as _ +from django.http import Http404 +from django.db.models import Q, Sum, Count +from django.views.generic.base import RedirectView +from django.core.exceptions import PermissionDenied + +import seminar.models as s +import seminar.models as m +from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, \ + Resitel, Novinky, Tema, Clanek, \ + Deadline # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci +#from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva +from treenode import treelib +import treenode.templatetags as tnltt +import treenode.serializers as vr +from vysledkovky.utils import body_resitelu, VysledkovkaCisla, \ + VysledkovkaRocniku, VysledkovkaDoTeXu + +from datetime import date, datetime +from itertools import groupby +from collections import OrderedDict +import os +import os.path as op +from django.conf import settings +import unicodedata +import logging +import time + +import personalni.views + +from .. import utils + +# ze starého modelu +#def verejna_temata(rocnik): +# """ +# Vrací queryset zveřejněných témat v daném ročníku. +# """ +# return Problem.objects.filter(typ=Problem.TYP_TEMA, cislo_zadani__rocnik=rocnik, cislo_zadani__verejne_db=True).order_by('kod') +# +#def temata_v_rocniku(rocnik): +# return Problem.objects.filter(typ=Problem.TYP_TEMA, rocnik=rocnik) + +logger = logging.getLogger(__name__) + +def get_problemy_k_tematu(tema): + return Problem.objects.filter(nadproblem = tema) + + +# FIXME: Pozor, níž je ještě jeden ProblemView! +#class ProblemView(generic.DetailView): +# model = s.Problem +# # Zkopírujeme template_name od TreeNodeView, protože jsme prakticky jen trošku upravený TreeNodeView +# template_name = TreeNodeView.template_name +# +# def get_context_data(self, **kwargs): +# context = super().get_context_data(**kwargs) +# user = self.request.user +# # Teď potřebujeme doplnit tnldata do kontextu. +# # Ošklivý type switch, hezčí by bylo udělat to polymorfni. FIXME. +# if False: +# # Hezčí formátování zbytku :-P +# pass +# elif isinstance(self.object, s.Clanek) or isinstance(self.object, s.Konfera): +# # Tyhle Problémy mají ŘešeníNode +# context['tnldata'] = TNLData.from_treenode(self.object.reseninode,user) +# elif isinstance(self.object, s.Uloha): +# # FIXME: Teď vždycky zobrazujeme i vzorák! Možná by bylo hezčí/lepší mít to stejně jako pro Téma: procházet jen dosažitelné z Ročníku / čísla / whatever +# tnl_zadani = TNLData.from_treenode(self.object.ulohazadaninode,user) +# tnl_vzorak = TNLData.from_treenode(self.object.ulohavzoraknode,user) +# context['tnldata'] = TNLData.from_tnldata_list([tnl_zadani, tnl_vzorak]) +# elif isinstance(self.object, s.Tema): +# rocniknode = self.object.rocnik.rocniknode +# context['tnldata'] = TNLData.filter_treenode(rocniknode, lambda x: isinstance(x, s.TemaVCisleNode)) +# else: +# raise ValueError("Obecný problém nejde zobrazit.") +# return context + + +#class AktualniZadaniView(generic.TemplateView): +# template_name = 'treenode/treenode.html' + +# TODO Co chceme vlastně zobrazovat na této stránce? Zatím je zde aktuální číslo, ale může tu být cokoli jiného... +#class AktualniZadaniView(TreeNodeView): +# def get_object(self): +# nastaveni = get_object_or_404(Nastaveni) +# return nastaveni.aktualni_cislo.cislonode +# +# def get_context_data(self,**kwargs): +# nastaveni = get_object_or_404(Nastaveni) +# context = super().get_context_data(**kwargs) +# verejne = nastaveni.aktualni_cislo.verejne() +# context['verejne'] = verejne +# return context + +def AktualniZadaniView(request): + nastaveni = get_object_or_404(Nastaveni) + verejne = nastaveni.aktualni_cislo.verejne() + return render(request, 'tvorba/zadani/AktualniZadani.html', + {'nastaveni': nastaveni, + 'verejne': verejne, + }, + ) + +def ZadaniTemataView(request): + nastaveni = get_object_or_404(Nastaveni) + verejne = nastaveni.aktualni_cislo.verejne() + akt_rocnik = nastaveni.aktualni_cislo.rocnik + temata = s.Tema.objects.filter(rocnik=akt_rocnik, stav='zadany') + return render(request, 'tvorba/tematka/rozcestnik.html', + { + 'tematka': temata, + 'verejne': verejne, + }, + ) + + +# nastaveni = get_object_or_404(Nastaveni) +# temata = verejna_temata(nastaveni.aktualni_rocnik) +# for t in temata: +# if request.user.is_staff: +# t.prispevky = t.prispevek_set.filter(problem=t) +# else: +# t.prispevky = t.prispevek_set.filter(problem=t, zverejnit=True) +# return render(request, 'tvorba/zadani/Temata.html', +# { +# 'temata': temata, +# } +# ) +# +# +# +#def TematkoView(request, rocnik, tematko): +# nastaveni = s.Nastaveni.objects.first() +# rocnik_object = s.Rocnik.objects.filter(rocnik=rocnik) +# tematko_object = s.Tema.objects.filter(rocnik=rocnik_object[0], kod=tematko) +# seznam = vytahniZLesaSeznam(tematko_object[0], nastaveni.aktualni_rocnik().rocniknode) +# for node, depth in seznam: +# if node.isinstance(node, s.KonferaNode): +# raise Exception("Not implemented yet") +# if node.isinstance(node, s.PohadkaNode): # Mohu ignorovat, má pod sebou +# pass +# +# return render(request, 'tvorba/tematka/toaletak.html', {}) +# +# +#def TemataRozcestnikView(request): +# print("=============================================") +# nastaveni = s.Nastaveni.objects.first() +# tematka_objects = s.Tema.objects.filter(rocnik=nastaveni.aktualni_rocnik()) +# tematka = [] #List tematka obsahuje pro kazde tematko object a list vsech TemaVCisleNodu - implementované pomocí slovníku +# for tematko_object in tematka_objects: +# print("AKTUALNI TEMATKO") +# print(tematko_object.id) +# odkazy = vytahniZLesaSeznam(tematko_object, nastaveni.aktualni_rocnik().rocniknode, pouze_zajimave = True) #Odkazy jsou tuply (node, depth) v listu +# print(odkazy) +# cisla = [] # List tuplů (nazev cisla, list odkazů) +# vcisle = [] +# cislo = None +# for odkaz in odkazy: +# if odkaz[1] == 0: +# if cislo != None: +# cisla.append((cislo, vcisle)) +# cislo = (odkaz[0].getOdkazStr(), odkaz[0].getOdkaz()) +# vcisle = [] +# else: +# print(odkaz[0].getOdkaz()) +# vcisle.append((odkaz[0].getOdkazStr(), odkaz[0].getOdkaz())) +# if cislo != None: +# cisla.append((cislo, vcisle)) +# +# print(cisla) +# tematka.append({ +# "kod" : tematko_object.kod, +# "nazev" : tematko_object.nazev, +# "abstrakt" : tematko_object.abstrakt, +# "obrazek": tematko_object.obrazek, +# "cisla" : cisla +# }) +# return render(request, 'tvorba/tematka/rozcestnik.html', {"tematka": tematka, "rocnik" : nastaveni.aktualni_rocnik().rocnik}) +# + +def ZadaniAktualniVysledkovkaView(request): + nastaveni = get_object_or_404(Nastaveni) + # Aktualni verejna vysledkovka + rocnik = nastaveni.aktualni_rocnik + context = {'vysledkovka': VysledkovkaRocniku(rocnik, True)} + + # kdyz neni verejna vysledkovka, tak zobraz starou + if len(context['vysledkovka'].cisla_rocniku) == 0: + try: + minuly_rocnik = Rocnik.objects.get( + rocnik=(rocnik.rocnik-1)) + rocnik = minuly_rocnik + + # Přepíšeme prázdnou výsledkovku výsledkovkou z minulého ročníku + context['vysledkovka'] = VysledkovkaRocniku(rocnik, True) + except ObjectDoesNotExist: + pass + + context['rocnik'] = rocnik + return render( + request, + 'tvorba/zadani/AktualniVysledkovka.html', + context + ) + + +### Titulni strana + +def aktualni_temata(rocnik): + """ + Vrací PolymorphicQuerySet témat v daném ročníku, ke kterým se aktuálně dá něco odevzdat. + """ + return Tema.objects.filter(rocnik=rocnik, stav='zadany').order_by('kod') + + +### Archiv + + +class ArchivView(generic.ListView): + model = Rocnik + template_name = 'tvorba/archiv/cisla.html' + + def get_context_data(self, **kwargs): + context = super(ArchivView, self).get_context_data(**kwargs) + + cisla = Cislo.objects.filter(poradi=1) + if not self.request.user.je_org: + cisla = cisla.filter(verejne_db=True) + urls ={} + + for i, c in enumerate(cisla): + # Výchozí nastavení + if c.rocnik not in urls: + urls[c.rocnik] = op.join(settings.STATIC_URL, "tvorba", "no-picture.png") + # NOTE: tohle možná nastavuje poslední titulku + if c.titulka_nahled: + urls[c.rocnik] = c.titulka_nahled.url + + context["object_list"] = urls + + return context + + + + + +class RocnikView(generic.DetailView): + model = Rocnik + template_name = 'tvorba/archiv/rocnik.html' + + # Vlastni ziskavani objektu z databaze podle (Rocnik.rocnik) + def get_object(self, queryset=None): + if queryset is None: + queryset = self.get_queryset() + + return get_object_or_404(queryset,rocnik=self.kwargs.get('rocnik')) + + def get_context_data(self, **kwargs): + context = super(RocnikView, self).get_context_data(**kwargs) + context["vysledkovka"] = VysledkovkaRocniku(context["rocnik"], True) + context["neprazdna_vysledkovka"] = len(context['vysledkovka'].cisla_rocniku) != 0 + context["vysledkovka_neverejna"] = VysledkovkaRocniku(context["rocnik"], False) + return context + +def resiteleRocnikuCsvExportView(request, rocnik): + from personalni.views import dataResiteluCsvResponse + assert request.method in ('GET', 'HEAD') + return dataResiteluCsvResponse( + utils.resi_v_rocniku( + get_object_or_404(m.Rocnik, rocnik=rocnik) + ) + ) + + +# FIXME: Pozor, výš je ještě jeden ProblemView! +#class ProblemView(generic.DetailView): +# model = Problem +# +# # Používáme funkci, protože přímo template_name neumí mít v přiřazení dost logiky. Ledaže by se to udělalo polymorfně... +# def get_template_names(self, **kwargs): +# # FIXME: Switch podle typu není hezký, ale nechtělo se mi to přepisovat celé. Správně by se tohle mělo řešit polymorfismem. +# spravne_templaty = { +# s.Uloha: "uloha", +# s.Tema: "tema", +# s.Konfera: "konfera", +# s.Clanek: "clanek", +# } +# context = super().get_context_data(**kwargs) +# return ['tvorba/archiv/problem_' + spravne_templaty[context['object'].__class__] + '.html'] +# +# def get_context_data(self, **kwargs): +# context = super().get_context_data(**kwargs) +# # Musí se používat context['object'], protože nevíme, jestli dostaneme úložku, téma, článek, .... a tyhle věci vyrábějí různé klíče. +# if not context['object'].verejne() and not self.request.user.je_org: +# raise PermissionDenied() +# if isinstance(context['object'], Clanek): +# context['reseni'] = Reseni.objects.filter(problem=context['object']).select_related('resitel').order_by('resitel__prijmeni') +# return context + + + +class CisloView(generic.DetailView): + # FIXME zobrazování témátek a vůbec, teď je tam jen odkaz na číslo v pdf + model = Cislo + template_name = 'tvorba/archiv/cislo.html' + + # Vlastni ziskavani objektu z databaze podle (Rocnik.rocnik) + def get_object(self, queryset=None): + if queryset is None: + queryset = self.get_queryset() + rocnik_arg = self.kwargs.get('rocnik') + poradi_arg = self.kwargs.get('cislo') + queryset = queryset.filter(rocnik__rocnik=rocnik_arg, poradi=poradi_arg) + + try: + obj = queryset.get() + except queryset.model.DoesNotExist: + raise Http404(_("No %(verbose_name)s found matching the query") % + {'verbose_name': queryset.model._meta.verbose_name}) + return obj + + def get_context_data(self, **kwargs): + context = super(CisloView, self).get_context_data(**kwargs) + + cislo = context['cislo'] + context['prevcislo'] = Cislo.objects.filter((Q(rocnik__lt=self.object.rocnik) | Q(poradi__lt=self.object.poradi))&Q(rocnik__lte=self.object.rocnik)).first() + + deadliny = Deadline.objects.filter(cislo=cislo).reverse() + deadliny_s_vysledkovkami = [] + + nadpisy = { + m.Deadline.TYP_CISLA: "Výsledkovka", + m.Deadline.TYP_PRVNI: "Výsledkovka do prvního deadlinu", + m.Deadline.TYP_PRVNI_A_SOUS: "Výsledkovka do prvního deadlinu a deadlinu pro účast na soustředění", + m.Deadline.TYP_SOUS: "Výsledkovka do deadlinu pro účast na soustředění", + } + + for deadline in deadliny: + if self.request.user.je_org | deadline.verejna_vysledkovka: + deadliny_s_vysledkovkami.append((deadline, nadpisy[deadline.typ], VysledkovkaCisla(cislo, not self.request.user.je_org, deadline))) + + context['deadliny_s_vysledkovkami'] = deadliny_s_vysledkovkami + return context + + +class ArchivTemataView(generic.ListView): + model = Problem + template_name = 'tvorba/archiv/temata.html' + queryset = Tema.objects.filter(stav=Problem.STAV_ZADANY).select_related('rocnik').order_by('rocnik', 'kod') + + def get_context_data(self, *args, **kwargs): + ctx = super().get_context_data(*args, **kwargs) + ctx['rocniky'] = OrderedDict() + for rocnik, temata in groupby(ctx['object_list'], lambda tema: tema.rocnik): + ctx['rocniky'][rocnik] = list(temata) + return ctx + +class OdmenyView(generic.TemplateView): + template_name = 'tvorba/archiv/odmeny.html' + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + fromcislo = get_object_or_404(Cislo, rocnik=self.kwargs.get('frocnik'), poradi=self.kwargs.get('fcislo')) + tocislo = get_object_or_404(Cislo, rocnik=self.kwargs.get('trocnik'), poradi=self.kwargs.get('tcislo')) + resitele = utils.aktivniResitele(tocislo) + + def get_diff(from_deadline: Deadline, to_deadline: Deadline): + frombody = body_resitelu(resitele=resitele, jen_verejne=False, do=from_deadline) + tobody = body_resitelu(resitele=resitele, jen_verejne=False, do=to_deadline) + outlist = [] + for resitel in resitele: + fbody = frombody.get(resitel.id, 0) + tbody = tobody.get(resitel.id, 0) + ftitul = resitel.get_titul(fbody) + ttitul = resitel.get_titul(tbody) + if ftitul != ttitul: + outlist.append({'jmeno': resitel.osoba.plne_jmeno(), 'ftitul': ftitul, 'ttitul': ttitul}) + return outlist + + def posledni_deadline_oprava(cislo: Cislo) -> Deadline: + posledni_deadline = cislo.posledni_deadline + if posledni_deadline is None: + return Deadline.objects.filter(Q(cislo__poradi__lt=cislo.poradi, cislo__rocnik=cislo.rocnik) | Q(cislo__rocnik__rocnik__lt=cislo.rocnik.rocnik)).order_by("deadline").last() + return posledni_deadline + + context["from_cislo"] = fromcislo + context["to_cislo"] = tocislo + from_deadline = posledni_deadline_oprava(fromcislo) + to_deadline = posledni_deadline_oprava(tocislo) + context["from_deadline"] = from_deadline + context["to_deadline"] = to_deadline + context["zmeny"] = get_diff(from_deadline, to_deadline) + + return context + + + + +### Generovani vysledkovky + +class CisloVysledkovkaView(CisloView): + """View vytvořené pro stránku zobrazující výsledkovku čísla v TeXu.""" + + model = Cislo + template_name = 'tvorba/archiv/cislo_vysledkovka.tex' + #content_type = 'application/x-tex; charset=UTF8' + #umozni rovnou stahnout TeXovsky dokument + content_type = 'text/plain; charset=UTF8' + #vypise na stranku textovy obsah vyTeXane vysledkovky k okopirovani + + def get_context_data(self, **kwargs): + context = super(CisloVysledkovkaView, self).get_context_data() + cislo = context['cislo'] + + cislopred = cislo.predchozi() + if cislopred is not None: + context['vysledkovka'] = VysledkovkaDoTeXu( + cislo, + od_vyjma=cislopred.zlomovy_deadline_pro_papirove_cislo(), + do_vcetne=cislo.zlomovy_deadline_pro_papirove_cislo(), + ) + else: + context['vysledkovka'] = VysledkovkaCisla( + cislo, + jen_verejne=False, + do_deadlinu=cislo.zlomovy_deadline_pro_papirove_cislo(), + ) + return context + + +# Podle předchozího +class PosledniCisloVysledkovkaView(generic.DetailView): + """View vytvořené pro zobrazení výsledkovky posledního čísla v TeXu.""" + + model = Rocnik + template_name = 'tvorba/archiv/cislo_vysledkovka.tex' + content_type = 'text/plain; charset=UTF8' + + def get_object(self, queryset=None): + if queryset is None: + queryset = self.get_queryset() + rocnik_arg = self.kwargs.get('rocnik') + queryset = queryset.filter(rocnik=rocnik_arg) + + try: + obj = queryset.get() + except queryset.model.DoesNotExist: + raise Http404(_("No %(verbose_name)s found matching the query") % + {'verbose_name': queryset.model._meta.verbose_name}) + return obj + + def get_context_data(self, **kwargs): + context = super(PosledniCisloVysledkovkaView, self).get_context_data() + rocnik = context['rocnik'] + cislo = rocnik.cisla.order_by("poradi").filter(deadline_v_cisle__isnull=False).last() + if cislo is None: + raise Http404(f"Ročník {rocnik.rocnik} nemá číslo s deadlinem.") + cislopred = cislo.predchozi() + context['vysledkovka'] = VysledkovkaDoTeXu( + cislo, + od_vyjma=cislopred.zlomovy_deadline_pro_papirove_cislo(), + do_vcetne=cislo.deadline_v_cisle.order_by("deadline").last(), + ) + return context + + +class RocnikVysledkovkaView(RocnikView): + """ View vytvořené pro stránku zobrazující výsledkovku ročníku v TeXu.""" + model = Rocnik + template_name = 'tvorba/archiv/rocnik_vysledkovka.tex' + #content_type = 'application/x-tex; charset=UTF8' + #umozni rovnou stahnout TeXovsky dokument + content_type = 'text/plain; charset=UTF8' +#vypise na stranku textovy obsah vyTeXane vysledkovky k okopirovani + +def cisloObalkyView(request, rocnik, cislo): + realne_cislo = get_object_or_404(Cislo, poradi=cislo, rocnik__rocnik=rocnik) + return personalni.views.obalkyView(request, utils.aktivniResitele(realne_cislo)) + + + +### Tituly +def TitulyViewRocnik(request, rocnik): + return TitulyView(request, rocnik, None) + + +def TitulyView(request, rocnik, cislo): + """ View pro stažení makra titulů v TeXu.""" + rocnik_obj = get_object_or_404(Rocnik, rocnik = rocnik) + resitele = Resitel.objects.filter(rok_maturity__gte = rocnik_obj.prvni_rok) + + asciijmena = [] + jmenovci = False # detekuje, zda jsou dva řešitelé jmenovci (modulo nabodeníčka), + # pokud ano, vrátí se jako true + if cislo is not None: + cislo_obj = get_object_or_404(Cislo, rocnik=rocnik_obj, poradi=cislo) + slovnik_s_body = body_resitelu(do=cislo_obj.zlomovy_deadline_pro_papirove_cislo(), jen_verejne=False) + else: + slovnik_s_body = body_resitelu(do=Deadline.objects.filter(cislo__rocnik=rocnik_obj).last(), jen_verejne=False) + + for resitel in resitele: + resitel.titul = resitel.get_titul(slovnik_s_body[resitel.id]) + jmeno = resitel.osoba.jmeno+resitel.osoba.prijmeni + # převedeme jména a příjmení řešitelů do ASCII + ascii_jmeno_bytes = unicodedata.normalize('NFKD', jmeno).encode("ascii","ignore") + # vrátí se byte string, převedeme na standardní string + ascii_jmeno_divnoznaky = str(ascii_jmeno_bytes, "utf-8", "ignore").replace(" ","") + resitel.ascii = ''.join(a for a in ascii_jmeno_divnoznaky if a.isalnum()) + if resitel.ascii not in asciijmena: + asciijmena.append(resitel.ascii) + else: + jmenovci = True + + return render(request, 'tvorba/archiv/tituly.tex', + {'resitele': resitele,'jmenovci':jmenovci},content_type="text/plain") + + +### Články +def group_by_rocnik(clanky): + ''' Vezme zadaný seznam článků a seskupí je podle ročníku. + Vrátí seznam seznamů článků ze stejného ročníku.''' + if len(clanky) == 0: + return clanky + clanky.order_by('cislo__rocnik__rocnik') + skupiny_clanku = [] + skupina = [] + + rocnik = clanky.first().cislo.rocnik.rocnik # první ročník + for clanek in clanky: + if clanek.cislo.rocnik.rocnik == rocnik: + skupina.append(clanek) + else: + skupiny_clanku.append(skupina) + skupina = [] + skupina.append(clanek) + rocnik = clanek.cislo.rocnik.rocnik + skupiny_clanku.append(skupina) + return skupiny_clanku + + +# FIXME: clanky jsou vsechny, pokud budou i neresitelske, tak se take zobrazi +# FIXME: Původně tu byl kód přímo v těle třídy, což rozbíjelo migrace. Opravil jsem, ale vůbec nevím, jestli to funguje. +class ClankyResitelView(generic.ListView): + model = Problem + template_name = 'tvorba/clanky/resitelske_clanky.html' + + # FIXME: QuerySet není pole! + def get_queryset(self): + clanky = Clanek.objects.filter(stav=Problem.STAV_VYRESENY).select_related('cislo__rocnik').order_by('-cislo__rocnik__rocnik') + queryset = [] + skupiny_clanku = group_by_rocnik(clanky) + for skupina in skupiny_clanku: + skupina.sort(key=lambda clanek: clanek.kod_v_rocniku) + for clanek in skupina: + queryset.append(clanek) + return queryset + +# FIXME: pokud chceme orgoclanky, tak nejak zavest do modelu a podle toho odkomentovat a upravit +#class ClankyOrganizatorView(generic.ListView): +# model = Problem +# template_name = 'tvorba/clanky/organizatorske_clanky.html' +# queryset = Problem.objects.filter(stav=Problem.STAV_ZADANY).select_related('cislo_zadani__rocnik').order_by('-cislo_zadani__rocnik__rocnik', 'kod') + + + + +class AktualniRocnikRedirectView(RedirectView): + permanent=False + pattern_name = 'seminar_rocnik' + + def get_redirect_url(self, *args, **kwargs): + aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik.rocnik + return super().get_redirect_url(rocnik=aktualni_rocnik, *args, **kwargs) -- 2.39.5