Rozstřílení seminářové aplikace #60

Merged
zelvuska merged 19 commits from split into master 2 months ago
  1. 2
      aesop/views.py
  2. 3
      api/tests/test_skola_autocomplete.py
  3. 2
      api/urls.py
  4. 2
      docs/zapisy/2021-12-06-testovani_dokumentace_codereview.md
  5. 2
      galerie/urls.py
  6. 2
      korektury/urls.py
  7. 3
      mamweb/settings_common.py
  8. 10
      mamweb/urls.py
  9. 3
      novinky/__init__.py
  10. 0
      novinky/templates/novinky/novinky.html
  11. 2
      novinky/templates/novinky/stare_novinky.html
  12. 27
      novinky/testutils.py
  13. 7
      novinky/urls.py
  14. 23
      novinky/views.py
  15. 1
      odevzdavatko/templates/odevzdavatko/tabulka.html
  16. 40
      odevzdavatko/testutils.py
  17. 4
      odevzdavatko/urls.py
  18. 11
      odevzdavatko/utils.py
  19. 4
      odevzdavatko/views.py
  20. 0
      personalni/static/personalni/lisak.pdf
  21. 0
      personalni/static/personalni/no-photo.png
  22. 0
      personalni/templates/personalni/obalky.tex
  23. 2
      personalni/templates/personalni/organizatori.html
  24. 235
      personalni/testutils.py
  25. 14
      personalni/urls.py
  26. 175
      personalni/utils.py
  27. 62
      personalni/views.py
  28. 2
      prednasky/urls.py
  29. 2
      seminar/models/odevzdavatko.py
  30. 3
      seminar/models/tvorba.py
  31. 16
      seminar/templates/seminar/jakresit/jak-resit.html
  32. 19
      seminar/templatetags/utils.py
  33. 910
      seminar/testutils.py
  34. 387
      seminar/utils.py
  35. 4
      seminar/views/__init__.py
  36. 2
      sifrovacka/urls.py
  37. 2
      sifrovacka/views.py
  38. 0
      soustredeni/static/soustredeni/logomm.pdf
  39. 69
      soustredeni/testutils.py
  40. 2
      soustredeni/urls.py
  41. 6
      soustredeni/views.py
  42. 11
      treenode/admin.py
  43. 2
      treenode/templates/treenode/orphanage.html
  44. 0
      tvorba/__init__.py
  45. 116
      tvorba/admin.py
  46. 6
      tvorba/apps.py
  47. 0
      tvorba/migrations/__init__.py
  48. 0
      tvorba/static/tvorba/no-picture.png
  49. 0
      tvorba/static/tvorba/tema-bez-obrazku.png
  50. 0
      tvorba/templates/tvorba/archiv/cisla.html
  51. 0
      tvorba/templates/tvorba/archiv/cislo.html
  52. 0
      tvorba/templates/tvorba/archiv/cislo_vysledkovka.tex
  53. 0
      tvorba/templates/tvorba/archiv/odmeny.html
  54. 0
      tvorba/templates/tvorba/archiv/prispevek.html
  55. 0
      tvorba/templates/tvorba/archiv/problem.html
  56. 0
      tvorba/templates/tvorba/archiv/problem_clanek.html
  57. 0
      tvorba/templates/tvorba/archiv/problem_tema.html
  58. 0
      tvorba/templates/tvorba/archiv/problem_uloha.html
  59. 0
      tvorba/templates/tvorba/archiv/problem_uloha_tema.html
  60. 4
      tvorba/templates/tvorba/archiv/rocnik.html
  61. 0
      tvorba/templates/tvorba/archiv/rocnik_vysledkovka.tex
  62. 0
      tvorba/templates/tvorba/archiv/temata.html
  63. 0
      tvorba/templates/tvorba/archiv/tituly.tex
  64. 0
      tvorba/templates/tvorba/clanky/organizatorske_clanky.html
  65. 0
      tvorba/templates/tvorba/clanky/resitelske_clanky.html
  66. 2
      tvorba/templates/tvorba/tematka/rozcestnik.html
  67. 0
      tvorba/templates/tvorba/tematka/toaletak.html
  68. 0
      tvorba/templates/tvorba/zadani/AktualniVysledkovka.html
  69. 0
      tvorba/templates/tvorba/zadani/AktualniZadani.html
  70. 0
      tvorba/templates/tvorba/zadani/Temata.html
  71. 0
      tvorba/templatetags/__init__.py
  72. 0
      tvorba/templatetags/deadliny.py
  73. 506
      tvorba/testutils.py
  74. 15
      tvorba/urls.py
  75. 89
      tvorba/utils.py
  76. 584
      tvorba/views/__init__.py
  77. 0
      tvorba/views/docasne.py
  78. 193
      tvorba/views/views_all.py
  79. 5
      various/admin.py
  80. 0
      various/management/__init__.py
  81. 0
      various/management/commands/__init__.py
  82. 0
      various/management/commands/generate_thumbnails.py
  83. 0
      various/management/commands/load_org_permissions.py
  84. 0
      various/management/commands/nukedb.py
  85. 0
      various/management/commands/pregeneruj_zmrazene_vysledkovky.py
  86. 0
      various/management/commands/save_org_permissions.py
  87. 4
      various/management/commands/testdata.py
  88. 0
      various/templates/various/formular_ok.html
  89. 16
      various/templates/various/jakresit/jak-resit.html
  90. 0
      various/templates/various/jakresit/jakresit_1.svg
  91. 0
      various/templates/various/jakresit/jakresit_2.svg
  92. 0
      various/templates/various/jakresit/jakresit_3.svg
  93. 0
      various/templates/various/pracuje_se.html
  94. 0
      various/templates/various/stav_databaze.html
  95. 0
      various/templates/various/titulnistrana/graph.svg
  96. 4
      various/templates/various/titulnistrana/titulnistrana.html
  97. 0
      various/templatetags/tex.py
  98. 135
      various/testutils.py
  99. 9
      various/urls.py
  100. 0
      various/views/__init__.py

2
aesop/views.py

@ -8,7 +8,7 @@ from django.utils.encoding import force_str
from .utils import default_ovvpfile from .utils import default_ovvpfile
from seminar.models import Rocnik, Soustredeni from seminar.models import Rocnik, Soustredeni
from vysledkovky import utils from vysledkovky import utils
from seminar.utils import aktivniResitele from tvorba.utils import aktivniResitele
class ExportIndexView(generic.View): class ExportIndexView(generic.View):
def get(self, request): def get(self, request):

3
api/tests/test_skola_autocomplete.py

@ -1,8 +1,7 @@
from django.test import TestCase, tag from django.test import TestCase, tag
from django.urls import reverse from django.urls import reverse
import seminar.models as m import seminar.models as m
import seminar.views as v from personalni.utils import sync_skoly
from seminar.utils import sync_skoly
@tag('stejny-model-na-produkci') @tag('stejny-model-na-produkci')
class OrgSkolyAutocompleteTestCase(TestCase): class OrgSkolyAutocompleteTestCase(TestCase):

2
api/urls.py

@ -1,6 +1,6 @@
from django.urls import path from django.urls import path
from . import views from . import views
from seminar.utils import org_required from personalni.utils import org_required
urlpatterns = [ urlpatterns = [
# Export škol # Export škol

2
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ý - Nesmí být striktně vynucovaný
- Musel by být hodně nastavitelný - Musel by být hodně nastavitelný
- Nechceme mít kód plný `#NOQA: WTF42` - 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 ☺) - 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ě - __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? - Potenciálně by šlo aplikovat jen lokálně na změny?

2
galerie/urls.py

@ -1,5 +1,5 @@
from django.urls import path from django.urls import path
from seminar.utils import org_required from personalni.utils import org_required
from . import views from . import views
urlpatterns = [ urlpatterns = [

2
korektury/urls.py

@ -1,5 +1,5 @@
from django.urls import path from django.urls import path
from seminar.utils import org_required from personalni.utils import org_required
from . import views from . import views
urlpatterns = [ urlpatterns = [

3
mamweb/settings_common.py

@ -54,7 +54,7 @@ SESSION_EXPIRE_AT_BROWSER_CLOSE = True
DOBA_ODHLASENI_PRI_ZASKRTNUTI_NEODHLASOVAT = 365 * 24 * 3600 # rok DOBA_ODHLASENI_PRI_ZASKRTNUTI_NEODHLASOVAT = 365 * 24 * 3600 # rok
# View pro chybu s CSRF tokenem (např. se sušenkami) # 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 # Modules configuration
@ -131,6 +131,7 @@ INSTALLED_APPS = (
# MaMweb # MaMweb
'mamweb', 'mamweb',
'seminar', 'seminar',
'tvorba',
'galerie', 'galerie',
'korektury', 'korektury',
'prednasky', 'prednasky',

10
mamweb/urls.py

@ -17,8 +17,8 @@ urlpatterns = [
path('admin/', admin.site.urls), # NOQA path('admin/', admin.site.urls), # NOQA
path('ckeditor/', include('ckeditor_uploader.urls')), path('ckeditor/', include('ckeditor_uploader.urls')),
# Seminarova aplikace (ma vlastni podadresare) # Tvorba = ročníky, čísla, problémy atd. (ma vlastni podadresare)
path('', include('seminar.urls')), path('', include('tvorba.urls')),
# Odevzdavatko (ma vlastni podadresare) # Odevzdavatko (ma vlastni podadresare)
path('', include('odevzdavatko.urls')), path('', include('odevzdavatko.urls')),
@ -39,6 +39,9 @@ urlpatterns = [
# Autentizační aplikace (ma vlastni podadresare) # Autentizační aplikace (ma vlastni podadresare)
path('', include('various.autentizace.urls')), path('', include('various.autentizace.urls')),
# Novinková aplikace (ma vlastni podadresare)
path('', include('novinky.urls')),
# Api (ma vlastni podadresare) (autocomplete apod.) # Api (ma vlastni podadresare) (autocomplete apod.)
path('', include('api.urls')), path('', include('api.urls')),
@ -48,6 +51,9 @@ urlpatterns = [
# Aesop (ma vlastni podadresare) # Aesop (ma vlastni podadresare)
path('', include('aesop.urls')), path('', include('aesop.urls')),
# Various = co se nevešlo jinam
path('', include('various.urls')),
# REST API # REST API
# path('api/', include(router.urls)), # path('api/', include(router.urls)),

3
novinky/__init__.py

@ -0,0 +1,3 @@
"""
Obsahuje vše okolo novinek (zpráv Co je nového? na titulní straně).
"""

0
seminar/templates/seminar/novinky.html → novinky/templates/novinky/novinky.html

2
seminar/templates/seminar/stare_novinky.html → novinky/templates/novinky/stare_novinky.html

@ -8,6 +8,6 @@
{% endblock %} {% endblock %}
</h1> </h1>
{% include 'seminar/novinky.html' %} {% include 'novinky/novinky.html' %}
{% endblock %} {% endblock %}

27
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

7
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'),
]

23
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)

1
odevzdavatko/templates/odevzdavatko/tabulka.html

@ -1,6 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load utils %} {# Možná by mohlo být někde výš v hierarchii templatů... #}
{% load barvy_reseni %} {% load barvy_reseni %}
{% block content %} {% block content %}

40
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

4
odevzdavatko/urls.py

@ -1,7 +1,7 @@
from django.urls import path from django.urls import path
from seminar.utils import org_required, resitel_required, viewMethodSwitch, \ from personalni.utils import org_required, resitel_required, resitel_or_org_required
resitel_or_org_required from various.views.generic import viewMethodSwitch
from . import views from . import views
urlpatterns = [ urlpatterns = [

11
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)

4
odevzdavatko/views.py

@ -20,8 +20,8 @@ import logging
import seminar.models as m import seminar.models as m
from . import forms as f from . import forms as f
from .forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm from .forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm
from seminar.utils import resi_v_rocniku from tvorba.utils import resi_v_rocniku
from seminar.views import formularOKView from various.views.pomocne import formularOKView
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

0
seminar/static/seminar/lisak.pdf → personalni/static/personalni/lisak.pdf

0
seminar/static/images/no-photo.png → personalni/static/personalni/no-photo.png

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

0
seminar/templates/seminar/archiv/obalky.tex → personalni/templates/personalni/obalky.tex

2
seminar/templates/seminar/cojemam/organizatori.html → personalni/templates/personalni/organizatori.html

@ -46,7 +46,7 @@
{% if org.osoba.foto %} {% if org.osoba.foto %}
<a href="{{org.osoba.foto.url}}"><img src="{{org.osoba.foto_male.url}}" height="{{org.osoba.foto_male.height}}" alt="{{org.osoba.jmeno}} {{org.osoba.prijmeni}}"></a> <a href="{{org.osoba.foto.url}}"><img src="{{org.osoba.foto_male.url}}" height="{{org.osoba.foto_male.height}}" alt="{{org.osoba.jmeno}} {{org.osoba.prijmeni}}"></a>
{% else %} {# pokud osoba nemá fotku, zobrazuje se defaultní obrázek #} {% else %} {# pokud osoba nemá fotku, zobrazuje se defaultní obrázek #}
{% load static %} <img src="{% static 'images/no-photo.png' %}" height=200px alt="{{org.osoba.jmeno}} {{org.osoba.prijmeni}}"> {% load static %} <img src="{% static 'personalni/no-photo.png' %}" height=200px alt="{{org.osoba.jmeno}} {{org.osoba.prijmeni}}">
{% endif %} {% endif %}
</div> </div>

235
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

14
personalni/urls.py

@ -1,7 +1,7 @@
from django.urls import path from django.urls import path
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from . import views from . import views
from seminar.utils import org_required from personalni.utils import org_required
urlpatterns = [ urlpatterns = [
path( path(
@ -21,4 +21,16 @@ urlpatterns = [
# Obecný view na profil -- orgům dá rozcestník, řešitelům jejich stránku # Obecný view na profil -- orgům dá rozcestník, řešitelům jejich stránku
path('profil/', views.profilView, name='profil'), 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'
),
] ]

175
personalni/utils.py

@ -2,10 +2,183 @@ import seminar.models as m
from various.utils import bez_diakritiky_translate from various.utils import bez_diakritiky_translate
import re 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? # FIXME: Možná není potřeba vázat na model?
cele_jmeno = f'{o.jmeno} {o.prijmeni}' cele_jmeno = f'{o.jmeno} {o.prijmeni}'
cele_jmeno = cele_jmeno.translate(bez_diakritiky_translate) cele_jmeno = cele_jmeno.translate(bez_diakritiky_translate)
cele_jmeno = re.sub(r'[^a-zA-Z- ]', '', cele_jmeno) cele_jmeno = re.sub(r'[^a-zA-Z- ]', '', cele_jmeno)
return cele_jmeno return cele_jmeno
def sync_skoly(base_url):
"""Stáhne všechny školy z mamwebu na adrese <base_url> 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 """
# 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í) ")

62
personalni/views.py

@ -1,3 +1,8 @@
import tempfile
import subprocess
import shutil
import http
from django.shortcuts import render from django.shortcuts import render
from django.urls import reverse from django.urls import reverse
from django.views import generic from django.views import generic
@ -6,8 +11,10 @@ from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic.base import TemplateView from django.views.generic.base import TemplateView
from django.contrib.auth.models import User, Permission, Group, AnonymousUser from django.contrib.auth.models import User, Permission, Group, AnonymousUser
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.staticfiles.finders import find
from django.db import transaction from django.db import transaction
from django.http import HttpResponse from django.http import HttpResponse
from django.utils import timezone
import seminar.models as s import seminar.models as s
import seminar.models as m import seminar.models as m
@ -17,12 +24,65 @@ from datetime import date
import logging import logging
import csv import csv
from seminar.views import formularOKView from various.views.pomocne import formularOKView
from various.autentizace.views import LoginView from various.autentizace.views import LoginView
from various.autentizace.utils import posli_reset_hesla from various.autentizace.utils import posli_reset_hesla
from django.forms.models import model_to_dict from django.forms.models import model_to_dict
from .models import Organizator
def aktivniOrganizatori(datum=timezone.now()):
return Organizator.objects.exclude(
organizuje_do__isnull=False,
organizuje_do__lt=datum
).order_by('osoba__jmeno')
class CojemamOrganizatoriView(generic.ListView):
model = Organizator
template_name = 'personalni/organizatori.html'
queryset = aktivniOrganizatori()
def get_context_data(self, **kwargs):
context = super(CojemamOrganizatoriView, self).get_context_data(**kwargs)
context['aktivni'] = True
return context
class CojemamOrganizatoriStariView(generic.ListView):
model = Organizator
template_name = 'personalni/organizatori.html'
queryset = Organizator.objects.exclude(
id__in=aktivniOrganizatori()
).order_by('-organizuje_do')
def obalkyView(request, resitele):
if len(resitele) == 0:
return HttpResponse(
render(request, 'universal.html', {
'title': 'Není pro koho vyrobit obálky.',
'text': 'Právě ses pokusil/a vygenerovat obálky pro prázdnou množinu lidí. Můžeš to zkusit změnit, případně se zeptej webařů :-)',
}),
status=http.HTTPStatus.NOT_FOUND,
)
tex = render(request, 'personalni/obalky.tex', {
'resitele': resitele
}).content
with tempfile.TemporaryDirectory() as tempdir:
with open(tempdir+"/obalky.tex", "w") as texfile:
texfile.write(tex.decode())
shutil.copy(find('personalni/lisak.pdf'), tempdir)
subprocess.call(["pdflatex", "obalky.tex"], cwd=tempdir)
with open(tempdir+"/obalky.pdf", "rb") as pdffile:
response = HttpResponse(pdffile.read(), content_type='application/pdf')
return response
class OrgoRozcestnikView(TemplateView): class OrgoRozcestnikView(TemplateView):
""" Zobrazí organizátorský rozcestník.""" """ Zobrazí organizátorský rozcestník."""

2
prednasky/urls.py

@ -1,5 +1,5 @@
from django.urls import path 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 from . import views
urlpatterns = [ urlpatterns = [

2
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 treenode as tm
from seminar.models import base as bm 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 from personalni.models import Resitel

3
seminar/models/tvorba.py

@ -23,7 +23,7 @@ from taggit.managers import TaggableManager
from reversion import revisions as reversion from reversion import revisions as reversion
from seminar.utils import roman from tvorba.utils import roman, aktivniResitele
from treenode import treelib 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é) 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 polymorphic.models import PolymorphicModel
from django.core.mail import EmailMessage from django.core.mail import EmailMessage
from seminar.utils import aktivniResitele
from personalni.models import Prijemce, Organizator from personalni.models import Prijemce, Organizator

16
seminar/templates/seminar/jakresit/jak-resit.html

@ -1,16 +0,0 @@
{% extends 'base.html' %}
{% load humanize %}
{% load static %}
{% block content %}
<div class=jakresit>
{% include 'seminar/jakresit/jakresit_1.svg' %}
{% include 'seminar/jakresit/jakresit_2.svg' %}
{% include 'seminar/jakresit/jakresit_3.svg' %}
</div>
{% endblock %}

19
seminar/templatetags/utils.py

@ -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'<time datetime="{dt.isoformat()}" title="{dt.strftime("%d. %m. %Y %H:%M")}">{dt.day}.{dt.month}.<span style="text-decoration:overline">{dt.year%100}</time>'
return mark_safe(out)

910
seminar/testutils.py

@ -1,910 +0,0 @@
import datetime
from django.contrib.auth.models import Permission
from django.contrib.auth.models import Group
import random
import lorem
import django.contrib.auth
from django.db import transaction
import unidecode
import logging
from korektury.testutils import create_test_pdf
from seminar.models import Skola, Resitel, Rocnik, Cislo, Deadline, Problem, Reseni, PrilohaReseni, Nastaveni, Soustredeni, Soustredeni_Ucastnici, Soustredeni_Organizatori, Osoba, Organizator, Prijemce, Tema, Uloha, Konfera, TextNode, UlohaVzorakNode, RocnikNode, CisloNode, TemaVCisleNode, Text, Hodnoceni, UlohaZadaniNode, Novinky, TreeNode
import seminar.models as m
from django.contrib.flatpages.models import FlatPage
from django.contrib.sites.models import Site
from treenode.treelib import all_children, insert_last_child, all_children_of_type, create_node_after
User = django.contrib.auth.get_user_model()
zlinska = None # tohle bude speciální škola, které později dodáme kontaktní osobu
logger = logging.getLogger(__name__)
# testuje unikátnost vygenerovaného jména
def __unikatni_jmeno(osoby, jmeno, prijmeni):
for os in osoby:
if os.jmeno == jmeno and os.prijmeni == prijmeni:
return 0
else: return 1
def gen_osoby(rnd, size):
logger.info('Generuji osoby (size={})...'.format(size))
jmena_m = ['Aleš', 'Tomáš', 'Martin', 'Jakub', 'Petr', 'Lukáš', 'Cyril', 'Pavel Karel']
jmena_f = ['Eva', 'Karolína', 'Zuzana', 'Sylvie', 'Iva', 'Jana', 'Marie',
'Marta Iva', 'Shu Shan']
prijmeni_m = ['Novotný', 'Svoboda', 'Pecha', 'Kořen', 'Holan', 'Uhlíř', 'Chytráček',
'Pokora', 'Koch', 'Szegedy', 'Rudý', "von Neumann", "d'Este"]
prijmeni_f = ['Novotná', 'Svobodová', 'Machová', 'Zelená', 'Yu-Xin', 'Mlsná', 'Dubná',
'Mrkvová', 'Suchá', 'Lovelace', 'Holcová', 'Rui', "Nováčková Tydlitátová"]
prezdivky = ['Kaki', 'Hurdur', 'Maracuja', 'Bobbo', "", "", "", "", "",
"", "", 'Riki', 'Sapa', "", '', '---', 'Koko']
domain = ['example.com', 'dolujeme.eu', 'mff.cuni.cz', 'strcprstskrzkrk.cz',
'british.co.uk', 'splachni.to', 'haha.org']
seznam_ulic = ['Krátká', 'Vlhká', 'Jungmanova', '17. listopadu', '4. října', 'Roztocká',
'Forstova', 'Generála Františka Janouška', 'Náměstí Války',
'Svratecké náměstí', 'Zelená lhota', 'Z Plynu', 'K Jezeru', 'U Kocourkova',
'Uštěpačná', 'Ostrorepská', 'Zubří']
seznam_mest = ['Praha', 'Brno', 'Ostrava', 'Horní Jelení', 'Dolní Zábrdovice', 'Prdelkov',
'Stará myslivna', 'Kocourkov', 'Šalingrad', 'Medvědí hora', 'Basilej',
'Unterschiedlich', 'Old York', 'Lancastershire', 'Vóloďháza']
osoby = []
# 30 je náhodná konstanta, size je použité na víc místech a
# říká, jak velká asi chceme testovací data
for i in range(30 * size):
pohlavi_idx = rnd.randint(0,2) # 2 = nebinární
osloveni = [Osoba.OSLOVENI_MUZSKE, Osoba.OSLOVENI_ZENSKE, Osoba.OSLOVENI_ZADNE][pohlavi_idx]
jmeno = rnd.choice([jmena_m, jmena_f, jmena_m + jmena_f][pohlavi_idx])
prijmeni = rnd.choice([prijmeni_m, prijmeni_f, prijmeni_m + prijmeni_f][pohlavi_idx])
if pohlavi_idx == 2: logger.debug(f'Testdata: nebinární osoba: {jmeno} {prijmeni}.')
pokusy = 0
max_pokusy = 120*size
while (not __unikatni_jmeno and pokusy < max_pokusy):
# pokud jméno a příjmení není unikátní, zkoušíme generovat nová
# do daného limitu (abychom se nezacyklili do nekonečna při málo jménech a příjmeních
# ze kterých se generuje)
jmeno = rnd.choice([jmena_m, jmena_f, jmena_m + jmena_f][pohlavi_idx])
prijmeni = rnd.choice([prijmeni_m, prijmeni_f, prijmeni_m + prijmeni_f][pohlavi_idx])
pokusy = pokusy + 1
if pokusy >= max_pokusy:
print("Chyba, na danou velikost testovacích dat příliš málo možných"
" jmen a příjmení")
exit()
prezdivka = rnd.choice(prezdivky)
email = "@".join([unidecode.unidecode(jmeno), rnd.choice(domain)])
telefon = "".join([str(rnd.choice([k for k in range(10)])) for i in range(9)])
narozeni = datetime.date(rnd.randint(1980, datetime.datetime.now().year),
rnd.randint(1, 12), rnd.randint(1, 28))
ulic = rnd.choice(seznam_ulic)
cp = rnd.randint(1, 99)
ulice = " ".join([ulic, str(cp)])
mesto = rnd.choice(seznam_mest)
psc = "".join([str(rnd.choice([k for k in range(10)])) for i in range(5)])
osoby.append(Osoba.objects.create(jmeno = jmeno, prijmeni = prijmeni,
prezdivka = prezdivka, osloveni = osloveni, email = email,
telefon = telefon, datum_narozeni = narozeni, ulice = ulice,
mesto = mesto, psc = psc,
datum_registrace = datetime.date(rnd.randint(2019, 2029),
rnd.randint(1, 12), rnd.randint(1, 28))))
#TODO pridat foto male a velke. Jak?
# Pavel tvrdí, že to necháme a přidáme až do adminu
return osoby
def gen_skoly(): #TODO někdy to přepsat, aby jich bylo více
logger.info('Generuji školy...')
skoly = []
prvnizs = Skola.objects.create(mesto='Praha', stat='CZ', psc='101 00',
ulice='Krátká 5', nazev='První ZŠ', je_zs=True, je_ss=False)
skoly.append(prvnizs)
skoly.append(Skola.objects.create(mesto='Praha', stat='CZ', psc='101 00',
ulice='Krátká 5', nazev='První SŠ', je_zs=False, je_ss=True))
skoly.append(Skola.objects.create(mesto='Praha', stat='CZ', psc='102 00',
ulice='Dlouhá 5', nazev='Druhá SŠ', je_zs=False, je_ss=True))
skoly.append(Skola.objects.create(mesto='Praha', stat='CZ', psc='103 00',
ulice='Široká 3', nazev='Třetí SŠ a ZŠ', je_zs=True, je_ss=True))
skoly.append(Skola.objects.create(mesto='Ostrava', stat='CZ', psc='700 00',
ulice='Hluboká 42', nazev='Hutní gympl', je_zs=False, je_ss=True))
skoly.append(Skola.objects.create(mesto='Humenné', stat='SK', psc='012 34',
ulice='Pltká 1', nazev='Sredná škuola', je_zs=False, je_ss=True))
global zlinska
zlinska = Skola.objects.create(mesto = 'Zlín', stat='CZ', psc='76001',
ulice='náměstí T.G. Masaryka 2734-9',
nazev='Gymnázium a Střední jazyková škola s právem SJZ',
kratky_nazev="GaSJŠspSJZ", je_zs=True, je_ss=True)
skoly.append(zlinska)
return skoly
def gen_resitele(rnd, osoby, skoly):
logger.info('Generuji řešitele...')
resitele = []
x = 0
resitel_perm = Permission.objects.filter(codename__exact='resitel').first()
resitel_group = Group.objects.filter(name__exact='resitel').first()
for os in osoby:
rand = rnd.randint(0, 8)
if not (rand % 8 == 0):
if not os.user:
if x:
user = User.objects.create_user(username='r'+str(x), email=os.email, password='r')
else:
user = User.objects.create_user(username='r', email=os.email, password='r')
x += 1
os.user = user
os.save()
os.user.user_permissions.add(resitel_perm)
os.user.groups.add(resitel_group)
resitele.append(Resitel.objects.create(osoba=os, skola=rnd.choice(skoly),
rok_maturity=os.datum_narozeni.year + rnd.randint(18, 21),
zasilat=rnd.choice(Resitel.ZASILAT_CHOICES)[0]))
return resitele
def gen_prijemci(rnd, osoby, kolik=10):
logger.info('Generuji příjemce (kolik={})...'.format(kolik))
prijemci = []
for i in rnd.sample(osoby, kolik):
prijemci.append(Prijemce.objects.create(osoba=i))
return prijemci
def gen_organizatori(rnd, osoby, last_rocnik):
logger.info('Generuji organizátory...')
organizatori = []
seznam_konicku = ["vařím", "jezdím na kole", "řeším diferenciální rovnice", "koukám z okna",
"tancuji", "programuji", "jezdím vlakem", "nedělám nic"]
seznam_oboru = ["matematiku", "matematiku", "matematiku", "fyziku", "literaturu",
"informatiku", "informatiku", "běhání dokolečka"]
x = 0
org_perm = Permission.objects.filter(codename__exact='org').first()
org_group = Group.objects.filter(name__exact='org').first()
for os in osoby:
rand = rnd.randint(0, 8)
if (rand % 8 == 0):
pusobnost = rnd.randint(1, last_rocnik)
od = datetime.datetime(
year=1993 + pusobnost,
month=rnd.randint(1, 12),
day=rnd.randint(1, 28),
tzinfo=datetime.timezone.utc,
)
do = datetime.datetime(
year=od.year + rnd.randint(1, 6),
month=rnd.randint(1, 12),
day=rnd.randint(1, 28),
tzinfo=datetime.timezone.utc,
)
#aktualni organizatori jeste nemaji vyplnene organizuje_do
#popis orga
konicek1 = rnd.choice(seznam_konicku)
konicek2 = rnd.choice(seznam_konicku)
obor = rnd.choice(seznam_oboru)
popis_orga = "Ve volném čase " + konicek1 + " a také " + konicek2 + ". Studuji " + obor + " a moc mě to baví."
if do.year > datetime.datetime.now().year:
do = None
if not os.user:
if x:
user = User.objects.create_user(username='o'+str(x), email=os.email, password='o')
else:
user = User.objects.create_user(username='o', email=os.email, password='o')
x += 1
os.user = user
os.save()
os.user.user_permissions.add(org_perm)
os.user.groups.add(org_group)
os.user.is_staff = True
os.user.save()
organizatori.append(Organizator.objects.create(osoba=os,
organizuje_od=od, organizuje_do=do, strucny_popis_organizatora = popis_orga))
return organizatori
def gen_zadani_ulohy(rnd, cisla, organizatori, pocet_oboru, poradi_cisla, poradi_problemu):
# Proměnné pro náhodné generování názvů a zadání.
jaka = ["Šachová", "Černá", "Větrná", "Dlouhá", "Křehká", "Rychlá",
"Zákeřná", "Fyzikální"]
co = ["kostka", "smršť", "díra", "zrada", "toulka", "tyč",
"úloha", "blecha"]
sloveso = ["Najděte", "Spočítejte", "Zapište", "Změřte", "Odhadněte"]
koho = ["délku", "počet", "množství", "dílky"]
ceho = ["všech", "správných", "konstatních", "zelených"]
jmeno = ["řešení", "tahů", "čísel", "kalhot", "koulí", "hadů"]
kde = ["na zemi", "ve vesmíru", "ve vzduchu", "na šňůře", "v letadle"]
obory = ["M", "F", "I", "O", "B"]
p = Uloha.objects.create(
# atributy třídy Problem
nazev=" ".join([rnd.choice(jaka), rnd.choice(co)]),
stav=Problem.STAV_ZADANY,
zamereni=rnd.sample(obory, pocet_oboru),
autor=rnd.choice(organizatori),
garant=rnd.choice(organizatori),
kod=str(poradi_problemu),
# atributy třídy Uloha
cislo_zadani=cisla[poradi_cisla-2-1],
cislo_reseni=cisla[poradi_cisla-1],
cislo_deadline=cisla[poradi_cisla-1],
max_body = rnd.randint(1, 8)
)
text = " ".join(
[rnd.choice(sloveso),
rnd.choice(koho),
rnd.choice(ceho),
rnd.choice(jmeno),
rnd.choice(kde)]
)
text_zadani = Text.objects.create(
na_web = text,
do_cisla = text,
)
zad = TextNode.objects.create(text = text_zadani, root = p.cislo_zadani.rocnik.rocniknode)
uloha_zadani = UlohaZadaniNode.objects.create(uloha = p, first_child = zad, root = p.cislo_zadani.rocnik.rocniknode)
p.ulohazadaninode = uloha_zadani
otec_syn(cisla[poradi_cisla-2-1].cislonode, uloha_zadani)
return p
def gen_vzoroveho_reseni_ulohy(rnd, organizatori, uloha, pocet_opravovatelu):
reseni = ["to je přece jasné", "triviální", "omlouváme se,"
"otevřený problém", "neřešitelné", "triviálně triviální",
"použitím věty z prvního semestru na matfyzu",
"jednoduše pomocí látky z druhého semestru na matfyzu",
"netriviální aplikace diferenciálních rovnic", "zadání je vnitřně"
"sporné", "nepopsatelně jednoduché", "pokud jste na to nepřišli,"
"tak jste fakt hloupí"]
# Generování vzorového řešení.
obsah = rnd.choice(reseni)
text_vzoraku = Text.objects.create(
na_web = obsah,
do_cisla = obsah
)
vzorak = TextNode.objects.create(text = text_vzoraku, root = uloha.cislo_zadani.rocnik.rocniknode)
uloha_vzorak = UlohaVzorakNode.objects.create(uloha = uloha, first_child = vzorak, root = uloha.cislo_zadani.rocnik.rocniknode)
uloha.ulohavzoraknode = uloha_vzorak
uloha.opravovatele.set(rnd.sample(organizatori, pocet_opravovatelu))
uloha.save()
return uloha_vzorak
def gen_reseni_ulohy(rnd, cisla, uloha, pocet_resitelu, poradi_cisla, resitele_cisla, resitele):
pocet_reseni = rnd.randint(pocet_resitelu//4, pocet_resitelu * 4)
# generujeme náhodný počet řešení vzhledem k počtu řešitelů čísla
for _ in range(pocet_reseni):
#print("Generuji {}-té řešení".format(reseni))
if rnd.randint(1, 10) == 1:
# cca desetina řešení od více řešitelů
res_vyber = rnd.sample(resitele_cisla,
rnd.randint(2, 5))
else:
res_vyber = rnd.sample(resitele_cisla, 1)
if resitele[0] in res_vyber: # speciální řešitel, který nemá žádné body
res_vyber.remove(resitele[0])
# Vytvoření řešení.
if uloha.cislo_zadani.zlomovy_deadline_pro_papirove_cislo() is not None:
# combine, abychom dostali plný čas a ne jen datum
cas_doruceni = uloha.cislo_zadani.deadline_v_cisle.first().deadline - datetime.timedelta(days=random.randint(0, 40)) - datetime.timedelta(minutes=random.randint(0, 60*24))
# astimezone, protože jinak vyhazuje warning o nenastavené TZ
res = Reseni.objects.create(forma=rnd.choice(Reseni.FORMA_CHOICES)[0], cas_doruceni=cas_doruceni.astimezone(datetime.timezone.utc))
else:
res = Reseni.objects.create(forma=rnd.choice(Reseni.FORMA_CHOICES)[0])
# Problém a řešitele přiřadíme později, ManyToManyField
# se nedá vyplnit v create().
res.resitele.set(res_vyber)
res.save()
# Vytvoření hodnocení.
hod = Hodnoceni.objects.create(
body=rnd.randint(0, uloha.max_body),
cislo_body=cisla[poradi_cisla - 1],
reseni=res,
problem=uloha
)
return
def gen_ulohy_do_cisla(rnd, organizatori, resitele, rocnik_cisla, rocniky, size):
logger.info('Generuji úlohy do čísla (size={})...'.format(size))
k = 0
for rocnik in rocniky:
k += 1
print("Generuji {}. číslo.".format(k))
cisla = rocnik_cisla[k - 1]
for ci in range(3, len(cisla) + 1): # pro všechna čísla
resitele_size = round(7/8 * 30 * size) # očekáváný celkový počet řešitelů
poc_res = rnd.randint(resitele_size//8, resitele_size//4)
# dané číslo řeší něco mezi osminou a čtvrtinou všech řešitelů
# (náhodná hausnumera, možno změnit)
# účelem je, aby se řešení generovala z menší množiny řešitelů a tedy
# bylo více řešení od jednoho řešitele daného čísla
resitele_cisla = rnd.sample(resitele, poc_res)
for pi in range(1, ((size + 1) // 2) + 1): # počet problémů
poc_op = rnd.randint(1, 4) # počet opravovatelů
poc_oboru = rnd.randint(1, 2)
# Generování zadání úlohy a UlohaZadaniNode,
# přivěšení pod dané číslo
p = gen_zadani_ulohy(rnd, cisla, organizatori, poc_oboru, ci, pi)
# Generování vzorového řešení
uloha_vzorak = gen_vzoroveho_reseni_ulohy(rnd, organizatori,
p, poc_op)
insert_last_child(cisla[ci-1].cislonode, uloha_vzorak)
# Generování řešení a hodnocení k úloze
gen_reseni_ulohy(rnd, cisla, p, poc_res, ci,
resitele_cisla, resitele)
return
def gen_soustredeni(rnd, resitele, organizatori):
logger.info('Generuji soustředění...')
soustredeni = []
for _ in range(1, 10): #FIXME Tu range si změňte jak chcete, nevím, co přesně znamená size (asi Anet?)
datum_zacatku=datetime.date(rnd.randint(2000, 2020), rnd.randint(1, 12), rnd.randint(1, 28))
working_sous = Soustredeni.objects.create(
rocnik=Rocnik.objects.order_by('?').first(),
verejne_db=rnd.choice([True, False]),
misto=rnd.choice(['Kremrolovice', 'Indiánov', 'U zmzliny', 'Vafláreň', 'Větrník', 'Horní Rakvička', 'Dolní cheesecake']),
typ=rnd.choice(['jarni', 'podzimni', 'vikend']),
datum_zacatku=datum_zacatku,
datum_konce=datum_zacatku + datetime.timedelta(days=7))
ucastnici = rnd.sample(resitele, min(len(resitele), 20))
working_sous.ucastnici.set(ucastnici)
#for res in rnd.sample(resitele, min(len(resitele), 20)):
# Soustredeni_Ucastnici.objects.create(resitel=res, soutredeni=working_sous)
orgove_vyber = rnd.sample(organizatori, min(len(organizatori), 20))
working_sous.organizatori.set(orgove_vyber)
#for org in rnd.sample(organizatori, min(len(organizatori), 20)):
# Soustredeni_Organizatori.objects.create(organizator=org, soutredeni=working_sous)
working_sous.save()
soustredeni.append(working_sous)
return soustredeni
def gen_rocniky(last_rocnik, size):
logger.info('Generuji ročníky (size={})...'.format(size))
rocniky = []
node = None
for ri in range(min(last_rocnik - size, 1), last_rocnik + 1):
rocnik = Rocnik.objects.create(prvni_rok = 1993 + ri, rocnik = ri)
node2 = RocnikNode.objects.create(rocnik = rocnik, succ = node)
rocnik.save()
node = node2
rocniky.append(rocnik)
return rocniky
def gen_konfery(size, rnd, organizatori, resitele, soustredeni):
logger.info('Generuji konfery (size={})...'.format(size))
konfery = []
for _ in range(1, size): #FIXME Tu range si změňte jak chcete, nevím, co přesně znamená size (asi Anet?)
# Anet: size je parametr udávající velikost testovacích dat a dá se pomocí ní škálovat,
# kolik dat se nageneruje
konfera = Konfera.objects.create(
nazev=rnd.choice(['Pozorování', 'Zkoumání', 'Modelování', 'Počítání', 'Zkoušení']) + rnd.choice([' vlastností', ' jevů', ' charakteristik']) + rnd.choice([' vektorových prostorů', ' kinetické terorie látek', ' molekulární biologie', ' syntentických stromů']),
anotace=lorem.paragraph(),
abstrakt=lorem.paragraph(),
garant=rnd.choice(organizatori),
soustredeni=rnd.choice(soustredeni),
typ_prezentace=rnd.choice(['veletrh', 'prezentace']))
ucastnici_sous = list(konfera.soustredeni.ucastnici.all())
ucastnici = rnd.sample(ucastnici_sous, min(len(ucastnici_sous), rnd.randint(3, 6)))
konfera.ucastnici.set(ucastnici)
#for res in rnd.sample(ucastnici, min(len(ucastnici), rnd.randint(3, 6))):
# Konfery_Ucastnici.objects.create(resitel=res, konfera=konfera)
konfera.save()
konfery.append(konfera)
return konfery
def gen_cisla(rnd, rocniky):
logger.info('Generuji čísla...')
rocnik_cisla = []
for rocnik in rocniky:
otec = True
cisla = []
cisel = rnd.randint(4, 8)
node = None
for ci in range(1, cisel + 1):
# první číslo vydáváme typicky okolo prázdnin
# (ci - 1)*2 zaručuje první číslo v červnu a všechna
# další po dvou měsících (což je rozumná aproximace)
mesic_vydani = (ci - 1)*2 + 6
# celociselné dělení mi řekne, jestli to je první nebo druhý rok ročníku
vydano = datetime.date(rocnik.prvni_rok + mesic_vydani // 12,
(mesic_vydani - 1) % 12 + 1,
rnd.randint(1, 28))
deadline = datetime.date(rocnik.prvni_rok + (mesic_vydani + 2) // 12,
(mesic_vydani + 1) % 12 + 1,
rnd.randint(1, 28))
cislo = Cislo.objects.create(
rocnik = rocnik,
poradi = str(ci),
datum_vydani=vydano,
verejne_db=True,
)
node2 = CisloNode.objects.get(cislo = cislo)
node2.succ = node
node2.root = rocnik.rocniknode
cislo.save()
deadline = Deadline.objects.create(
cislo=cislo,
deadline=deadline,
typ=Deadline.TYP_CISLA,
verejna_vysledkovka=True,
)
deadline.save()
node = node2
if otec:
otec = False
rocnik.rocniknode.first_child = node
rocnik.save()
cisla.append(cislo)
rocnik_cisla.append(cisla)
return rocnik_cisla
def add_first_child(node, child):
node.first_child = child
node.save()
return
def get_text():
odstavec = lorem.paragraph()
return Text.objects.create(na_web = odstavec, do_cisla = odstavec)
def gen_dlouhe_tema(rnd, organizatori, rocnik, nazev, obor, kod):
tema = Tema.objects.create(
nazev=nazev,
stav=Problem.STAV_ZADANY,
zamereni="M",
autor=rnd.choice(organizatori),
garant=rnd.choice(organizatori),
kod=str(kod),
tema_typ=rnd.choice(Tema.TEMA_CHOICES)[0],
rocnik=rocnik,
abstrakt = lorem.paragraph()
)
# Generování struktury k tématu
cisla = sorted(rocnik.cisla.all(), key=lambda cislo: cislo.poradi)
for cislo in cisla:
# Přidáme TemaVCisleNode do daného čísla
cislo_node = cislo.cislonode
tema_cislo_node = TemaVCisleNode.objects.create(tema = tema, root = cislo_node.root)
insert_last_child(cislo_node, tema_cislo_node)
# Přidávání obsahu do čísla
cast_node = m.CastNode.objects.create(nadpis = "Příspěvek k číslu {}".format(cislo.kod), root=cislo_node.root)
add_first_child(tema_cislo_node, cast_node)
text_node = TextNode.objects.create(text = get_text(), root=cislo_node.root)
add_first_child(cast_node, text_node)
cast_node2 = m.CastNode.objects.create(nadpis = "První podproblém", root=cislo_node.root)
add_first_child(text_node, cast_node2)
text_node2 = TextNode.objects.create(text = get_text(), root=cislo_node.root)
add_first_child(cast_node2, text_node2)
cast_node3 = m.CastNode.objects.create(nadpis = "Druhý podproblém", root=cislo_node.root)
add_first_child(text_node2, cast_node3)
text_node3 = TextNode.objects.create(text = get_text(), root=cislo_node.root)
add_first_child(cast_node3, text_node3)
cast_node4 = m.CastNode.objects.create(nadpis = "Třetí podproblém", root=cislo_node.root)
add_first_child(text_node3, cast_node4)
text_node4 = TextNode.objects.create(text = get_text(), root=cislo_node.root)
add_first_child(cast_node3, text_node4)
cast_node3a = m.CastNode.objects.create(nadpis = "Podproblém paralelní s "
"druhým podproblémem", root=cislo_node.root)
cast_node3.succ = cast_node3a
cast_node3.save()
text_node3a = TextNode.objects.create(text = get_text(), root=cislo_node.root)
add_first_child(cast_node3a, text_node3a)
# Občas přidáme mezičíslo
if rnd.randint(1, 3) == 1:
create_node_after(cislo_node, m.MezicisloNode, root=cislo_node.root)
mezicislo_node = cislo_node.succ
cast_node_mezicislo = m.CastNode.objects.create(
nadpis = "Příspěvek k mezičíslu".format(cislo.kod), root=cislo_node.root)
add_first_child(mezicislo_node, cast_node_mezicislo)
odstavec = lorem.paragraph()
text_mezicislo = Text.objects.create(na_web = odstavec, do_cisla = odstavec)
text_node_mezicislo = TextNode.objects.create(text = text_mezicislo, root=cislo_node.root)
add_first_child(cast_node_mezicislo, text_node_mezicislo)
return tema
def gen_temata(rnd, rocniky, rocnik_cisla, organizatori):
logger.info('Generuji témata...')
jake = ["Hravé", "Fyzikální", "Nejlepší", "Totálně masakrální",
"Šokující", "Magnetické", "Modré", "Překvapivé",
"Plasmatické", "Novoroční"]
co = ["téma", "záření", "stavení", "jiskření", "jelito",
"drama", "kuře", "moře", "klání", "proudění", "čekání"]
poc_oboru = rnd.randint(1, 2)
rocnik_temata = []
# Věříme, že rocnik_cisla je pole polí čísel podle ročníků, tak si necháme dát
# vždycky jeden ročník a k němu příslušná čísla.
for rocnik, cisla in zip(rocniky, rocnik_cisla):
kod = 1
letosni_temata = []
# Do každého ročníku vymyslíme tři (zatím) témata, v každém z prvních čísel jedno
for zacatek_tematu in range(1, 3):
# Vygenerujeme téma
t = Tema.objects.create(
# atributy třídy Problem
nazev=" ".join([rnd.choice(jake), rnd.choice(co)]),
stav=Problem.STAV_ZADANY,
zamereni=rnd.sample(["M", "F", "I", "O", "B"], poc_oboru),
autor=rnd.choice(organizatori),
garant=rnd.choice(organizatori),
kod=str(kod),
# atributy třídy Téma
tema_typ=rnd.choice(Tema.TEMA_CHOICES)[0],
rocnik=rocnik,
abstrakt = "Abstrakt tematka {}".format(kod)
)
kod += 1
# Vymyslíme, kdy skončí
konec_tematu = min(rnd.randint(zacatek_tematu, 7), len(cisla))
# Vyrobíme TemaVCisleNody pro obsah
for i in range(zacatek_tematu, konec_tematu+1):
node = TemaVCisleNode.objects.create(tema = t,root=rocnik.rocniknode)
# FIXME: Není to off-by-one?
otec = cisla[i-1].cislonode
otec_syn(otec, node)
# Vymyslíme, kdo to bude opravovat
poc_opravovatelu = rnd.randint(1, 3)
t.opravovatele.set(rnd.sample(organizatori, poc_opravovatelu))
# Uložíme všechno
t.save()
letosni_temata.append((zacatek_tematu, konec_tematu, t))
rocnik_temata.append(letosni_temata)
return rocnik_temata
def gen_ulohy_tematu(rnd, organizatori, resitele, tema, kod, cislo, cislo_se_vzorakem):
""" Generování úlohy k danému tématu. """
# Proměnné pro náhodné generování názvů a zadání.
jaka = ["Šachová", "Černá", "Větrná", "Dlouhá", "Křehká", "Rychlá",
"Zákeřná", "Fyzikální"]
co = ["kostka", "smršť", "díra", "zrada", "toulka", "tyč",
"úloha", "blecha"]
sloveso = ["Najděte", "Spočítejte", "Zapište", "Změřte", "Odhadněte"]
koho = ["délku", "počet", "množství", "dílky"]
ceho = ["všech", "správných", "konstatních", "zelených"]
jmeno = ["řešení", "tahů", "čísel", "kalhot", "koulí", "hadů"]
kde = ["na zemi", "ve vesmíru", "ve vzduchu", "na šňůře", "v letadle"]
obory = ["M", "F", "I", "O", "B"]
uloha = Uloha.objects.create(
nazev=": ".join([tema.nazev,
"úloha {}.".format(kod)]),
nadproblem=tema,
stav=Problem.STAV_ZADANY,
zamereni=tema.zamereni,
autor=tema.autor,
garant=tema.garant,
kod=str(kod),
cislo_zadani=cislo,
cislo_reseni=cislo_se_vzorakem,
cislo_deadline=cislo_se_vzorakem,
max_body = rnd.randint(1, 8)
)
# Samotný obsah následně vzniklého Textu zadání
obsah = " ".join(
[rnd.choice(sloveso),
rnd.choice(koho),
rnd.choice(ceho),
rnd.choice(jmeno),
rnd.choice(kde)]
)
text_zadani = Text.objects.create(
na_web = obsah,
do_cisla = obsah,
)
zad = TextNode.objects.create(text = text_zadani, root=tema.temavcislenode_set.first().root)
uloha_zadani = UlohaZadaniNode.objects.create(uloha=uloha, first_child = zad, root=tema.temavcislenode_set.first().root)
uloha.ulohazadaninode = uloha_zadani
# Generování řešení a hodnocení k úloze
gen_reseni_ulohy(rnd, [cislo], uloha, len(resitele)//4, 1,
resitele, resitele)
return uloha, uloha_zadani
def gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori, resitele):
logger.info('Generuji úlohy k tématům...')
# Ke každému ročníku si vezmeme příslušná čísla a témata
for rocnik, cisla, temata in zip(rocniky, rocnik_cisla, rocnik_temata):
# Do každého čísla nagenerujeme ke každému témátku pár úložek
for cislo in cisla:
print("Generuji úložky do {}-tého čísla".format(cislo.poradi))
# Vzorák bude o dvě čísla dál
cislo_se_vzorakem = Cislo.objects.filter(
rocnik=rocnik,
poradi=str(int(cislo.poradi) + 2),
)
# Pokud není číslo pro vzorák, tak se dá do posledního čísla
# (i kdyby tam mělo být zadání i řešení...)
# Tohle sice umožňuje vygenerovat vzorák do čísla dávno po konci témátka,
# ale to nám pro jednoduchost nevadí.
if len(cislo_se_vzorakem) == 0:
cislo_se_vzorakem = cisla[-1]
else:
cislo_se_vzorakem = cislo_se_vzorakem.first()
for tema_node in all_children_of_type(cislo.cislonode, TemaVCisleNode):
tema = tema_node.tema
# Pokud už témátko skončilo, žádné úložky negenerujeme
# FIXME: Bylo by hezčí, kdyby se čísla předávala jako Cislo a ne
# jako int v té trojici (start, konec, tema)
if not temata[int(tema.kod)-1][1] >= int(cislo_se_vzorakem.poradi):
continue
# Generujeme 1 až 4 úložky k tomuto témátku do tohoto čísla.
for kod in range(1, rnd.randint(1, 4)):
u, uz = gen_ulohy_tematu(rnd, organizatori, resitele, tema, kod,
cislo, cislo_se_vzorakem)
insert_last_child(tema_node, uz)
poc_op = rnd.randint(1, 4)
uvz = gen_vzoroveho_reseni_ulohy(rnd, organizatori,
u, poc_op)
# Najdeme správný TemaVCisleNode pro vložení vzoráku
res_tema_node = None;
for node in all_children(cislo_se_vzorakem.cislonode):
if isinstance(node, TemaVCisleNode):
if node.tema == tema:
res_tema_node = node
if res_tema_node is None:
raise LookupError("Nenalezen Node pro vložení vzoráku")
insert_last_child(res_tema_node, uvz)
u.save()
return
def gen_novinky(rnd, organizatori):
logger.info('Generuji novinky...')
jake = ["zábavné", "veselé", "dobrodružné", "skvělé"]
co = ["soustředění", "Fyziklání", "víkendové setkání"]
kde = ["na Šumavě", "v Praze", "u Plzně", "na Marsu"]
kdy = ["Zítra bude", "10. 10. 2020 bude", "V prosinci bude", "V létě bude"]
for i in range(5):
text_novinky = " ".join([rnd.choice(kdy), rnd.choice(kde), rnd.choice(jake),
rnd.choice(co)])
novinka = Novinky.objects.create(id=i,autor=rnd.choice(organizatori),
text=(text_novinky+", těšíme se na vás!"),zverejneno=rnd.choice([True,False]))
novinka.save()
return
def otec_syn(otec, syn):
bratr = otec.first_child
syn.succ = bratr
otec.first_child = syn
syn.save()
otec.save()
def gen_clanek(rnd, organizatori, resitele):
logger.info("Generuji článek do čísla 22.2")
clanek = m.Clanek.objects.create(
nazev="Článek o Lorem ipsum",
nadproblem=None,
stav='vyreseny',
zamereni=['I'],
garant=rnd.choice(organizatori),
kod='cl',
)
clanek.save()
reseni = m.Reseni.objects.create(
zverejneno=True,
)
reseni.resitele.add(rnd.choice(resitele))
reseni.save()
cislo = m.Cislo.objects.get(rocnik__rocnik=22, poradi=2)
cislonode = cislo.cislonode
hodnoceni = m.Hodnoceni.objects.create(
body=15.0,
cislo_body=cislo,
reseni=reseni,
problem=clanek,
)
hodnoceni.save()
reseninode = m.ReseniNode.objects.create(
reseni=reseni
)
reseninode.save()
# Bude to celý text
reseni.text_cely = reseninode
reseni.save()
from treenode.treelib import insert_last_child, create_child
insert_last_child(cislonode, reseninode)
# Vyrobíme nějaký obsah
# FIXME: Ten, kdo vymyslel TreeLib (mj. týž, kdo psal tenhle kód),
# nevyrobil vhodnou funkci, takže to postavíme pozpátku pomocí create_child
# (které vyrábí _prvního_ syna)
create_child(reseninode, m.CastNode, nadpis="Lorem ipsum")
# Taky ten člověk nevyrobil vracení nových věcí...
castnode = reseninode.first_child
# Úvodní odstaveček
obsah = "Tohle je zamyšlení o textu lorem ipsum. Začneme a skončíme ukázkou."
text = m.Text.objects.create(
na_web=obsah,
do_cisla=obsah,
)
text.save()
create_child(reseninode, m.TextNode, text=text)
# Několik odstavců lorem ipsum
for _ in range(rnd.randint(3, 7)):
lipsum = lorem.paragraph()
text = m.Text.objects.create(
na_web=lipsum,
do_cisla=lipsum,
)
text.save()
create_child(castnode, m.TextNode, text=text)
logger.info(f"Článek vygenerován (reseni={reseni.id}, treenode={reseninode.id})")
@transaction.atomic
def create_test_data(size = 6, rnd = None):
logger.info('Vyrábím testovací data (size={})...'.format(size))
assert size >= 1
# pevna pseudo-nahodnost
rnd = rnd or random.Random(x=42)
# static URL stranky
# FIXME: nakopirovat sem vsechny z produkcni databaze
s = Site.objects.filter(name="example.com")
f = FlatPage.objects.create(url="/", title="Seminář M&M",
content = "<p>V&iacute;tejte na str&aacute;nce semin&aacute;ře MaM!</p>")
print(s)
f.sites.add(s[0])
f.save()
# users
admin = User.objects.create_superuser(username='admin', email='', password='admin')
os_admin = Osoba.objects.create(
user=admin, jmeno='admin', prijmeni='admin',
prezdivka='admin', osloveni='', email='admin@admin.admin',
telefon='123 456 789', datum_narozeni=datetime.date(2000, 1, 1),
ulice='admin', mesto='admin', psc='100 00',
datum_registrace=datetime.date(2020, 9, 6)
)
or_admin = Organizator.objects.create(
osoba=os_admin, organizuje_od=None, organizuje_do=None,
strucny_popis_organizatora="Organizátor k uživateli Admin"
)
usernames = ['anet', 'bara', 'cyril', 'david', 'eva', 'filip']
users = []
for usr in usernames[:size]:
u = User.objects.create_user(username=usr, password=usr)
u.first_name = usr.capitalize()
u.save()
users.append(u)
print(users)
# skoly
skoly = gen_skoly()
# osoby
osoby = gen_osoby(rnd, size)
# resitele a organizatori
last_rocnik = 25
organizatori = gen_organizatori(rnd, osoby, last_rocnik)
resitele = gen_resitele(rnd, osoby, skoly)
#generování novinek
novinky = gen_novinky(rnd, organizatori)
# prijemci
prijemci = gen_prijemci(rnd, osoby)
global zlinska
zlinska.kontaktni_osoba=rnd.choice(osoby)
zlinska.save()
# rocniky
rocniky = gen_rocniky(last_rocnik, size)
# cisla
# rocnik_cisla je pole polí čísel (typ Cislo), vnitřní pole odpovídají jednotlivým ročníkům.
rocnik_cisla = gen_cisla(rnd, rocniky)
# generování obyčejných úloh do čísel
gen_ulohy_do_cisla(rnd, organizatori, resitele, rocnik_cisla, rocniky, size)
# generování témat, zatím v prvních třech číslech po jednom
# FIXME: více témat
# rocnik_temata je pole polí trojic (první číslo :int, poslední číslo :int, téma:Tema), přičemž každé vnitřní pole odpovídá ročníku a FIXME: je to takhle fuj a když to někdo vidí poprvé, tak je z toho smutný, protože vůbec neví, co se děje a co má čekat.
rocnik_temata = gen_temata(rnd, rocniky, rocnik_cisla, organizatori)
rocnik = Rocnik.objects.filter(rocnik = 23).first()
dlouhe_tema = gen_dlouhe_tema(rnd, organizatori, rocnik, "Strašně dlouhé téma",
"MFI", 8)
# generování úloh k tématům ve všech číslech
gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori, resitele)
#generování soustředění
soustredeni = gen_soustredeni(rnd, resitele, organizatori)
#generování konfer
konfery = gen_konfery(size, rnd, organizatori, resitele, soustredeni)
# vytvoreni pdf ke korekturam
create_test_pdf(rnd, organizatori)
# TODO: nastavi správně, kolik se čeho generuje, aby rozsahy přibližně odpovídaly
# FIXME: misto typu ruzne typy objektu a vnoreni do sebe (Tom nechápe, co je tímto fixme míněno)
# TODO: vytvorit temata s ruznymi vlakny
# TODO: nagenerovat starsim rocnikum pohadku
# TODO: nagenerovat články
# TODO: vecpat obrázky všude, kde to jde
# TODO: mezičíslo node
# TODO: přidat ke konferám řešení a dát je do čísel
# Dohackované vytvoření jednoho článku
gen_clanek(rnd, organizatori, resitele)
# TODO: přidat články včetně zařazení do struktury treenodů,
# a následně otestovat konsistency check databáze z utils.py
# pomocí stránky /stav
# obecné nastavení semináře, musí být už přidané ročníky a čísla, jinak se nastaví divně
nastaveni = Nastaveni.objects.create(
aktualni_cislo = Cislo.objects.all()[1])

387
seminar/utils.py

@ -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 = '<b>%s:</b> %s' % (cls.__name__, msg)
if objs:
s += ' ['
for o in objs:
try:
url = o.admin_url()
except:
url = None
if url:
s += '<a href="%s">%s</a>, ' % (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í view jakožto funkce, takže u class-based views se 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 <base_url> 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 """
# 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í) ")

4
seminar/views/__init__.py

@ -1,4 +0,0 @@
from .views_all import *
# Dočsasné views
from .docasne import *

2
sifrovacka/urls.py

@ -1,6 +1,6 @@
from django.urls import path 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 from .views import SifrovackaView, SifrovackaListView, NapovedaView, NapovedaListView, PreskoceniView
urlpatterns = [ urlpatterns = [

2
sifrovacka/views.py

@ -1,7 +1,7 @@
from django.urls import reverse from django.urls import reverse
from django.views.generic import FormView, ListView from django.views.generic import FormView, ListView
from seminar.views import formularOKView from various.views.pomocne import formularOKView
from .forms import SifrovackaForm, NapovedaForm from .forms import SifrovackaForm, NapovedaForm
from .models import OdpovedUcastnika, SpravnaOdpoved, Napoveda, NapovezenoUcastnikovi from .models import OdpovedUcastnika, SpravnaOdpoved, Napoveda, NapovezenoUcastnikovi
from personalni.models import Resitel from personalni.models import Resitel

0
seminar/static/images/logomm.pdf → soustredeni/static/soustredeni/logomm.pdf

69
soustredeni/testutils.py

@ -0,0 +1,69 @@
import logging
import datetime
import random
from typing import Sequence
import lorem
from .models import Soustredeni, Konfera
import seminar.models.tvorba as am
import personalni.models as pm
logger = logging.getLogger(__name__)
def gen_soustredeni(
size: int,
resitele: Sequence[pm.Resitel],
organizatori: Sequence[pm.Organizator],
rnd: random.Random = None,
) -> Sequence[Soustredeni]:
logger.info('Generuji soustředění (size={})...')
rnd = rnd or random.Random(x=42)
soustredeni = []
for _ in range(1, 10): # FIXME Tu range si změňte jak chcete, nevím, co přesně znamená size (asi Anet?)
datum_zacatku = datetime.date(rnd.randint(2000, 2020), rnd.randint(1, 12), rnd.randint(1, 28))
working_sous = Soustredeni.objects.create(
rocnik=am.Rocnik.objects.order_by('?').first(),
verejne_db=rnd.choice([True, False]),
misto=rnd.choice(['Kremrolovice', 'Indiánov', 'U zmzliny', 'Vafláreň', 'Větrník', 'Horní Rakvička', 'Dolní cheesecake']),
typ=rnd.choice(['jarni', 'podzimni', 'vikend']),
datum_zacatku=datum_zacatku,
datum_konce=datum_zacatku + datetime.timedelta(days=7))
ucastnici = rnd.sample(resitele, min(len(resitele), 20))
working_sous.ucastnici.set(ucastnici)
orgove_vyber = rnd.sample(organizatori, min(len(organizatori), 20))
working_sous.organizatori.set(orgove_vyber)
working_sous.save()
soustredeni.append(working_sous)
return soustredeni
def gen_konfery(
size: int,
organizatori: Sequence[pm.Organizator],
soustredeni: Sequence[Soustredeni],
resitele: Sequence[pm.Resitel] = None,
rnd: random.Random = None,
) -> Sequence[Konfera]:
logger.info('Generuji konfery (size={})...'.format(size))
rnd = rnd or random.Random(x=42)
konfery = []
for _ in range(1, size): # FIXME Tu range si změňte jak chcete, nevím, co přesně znamená size (asi Anet?)
# Anet: size je parametr udávající velikost testovacích dat a dá se pomocí ní škálovat,
# kolik dat se nageneruje
konfera = Konfera.objects.create(
nazev=rnd.choice(['Pozorování', 'Zkoumání', 'Modelování', 'Počítání', 'Zkoušení']) + rnd.choice([' vlastností', ' jevů', ' charakteristik']) + rnd.choice([' vektorových prostorů', ' kinetické terorie látek', ' molekulární biologie', ' syntentických stromů']),
anotace=lorem.paragraph(),
abstrakt=lorem.paragraph(),
garant=rnd.choice(organizatori),
soustredeni=rnd.choice(soustredeni),
typ_prezentace=rnd.choice(['veletrh', 'prezentace']))
ucastnici_sous = resitele if resitele else list(konfera.soustredeni.ucastnici.all())
ucastnici = rnd.sample(ucastnici_sous, min(len(ucastnici_sous), rnd.randint(3, 6)))
konfera.ucastnici.set(ucastnici)
konfera.save()
konfery.append(konfera)
return konfery

2
soustredeni/urls.py

@ -1,6 +1,6 @@
from django.urls import path, include from django.urls import path, include
from . import views from . import views
from seminar.utils import org_required from personalni.utils import org_required
urlpatterns = [ urlpatterns = [
path( path(

6
soustredeni/views.py

@ -11,7 +11,7 @@ import subprocess
from pathlib import Path from pathlib import Path
import http import http
from seminar.views import obalkyView import personalni.views
class SoustredeniListView(generic.ListView): class SoustredeniListView(generic.ListView):
@ -34,7 +34,7 @@ class SoustredeniListView(generic.ListView):
def soustredeniObalkyView(request, soustredeni): def soustredeniObalkyView(request, soustredeni):
soustredeni = get_object_or_404(Soustredeni, id=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): class SoustredeniUcastniciBaseView(generic.ListView):
@ -93,7 +93,7 @@ def soustredeniStvrzenkyView(request, soustredeni):
with open(tempdir / "stvrzenky.tex", "w") as texfile: with open(tempdir / "stvrzenky.tex", "w") as texfile:
texfile.write(tex.decode()) 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) subprocess.call(["pdflatex", "stvrzenky.tex"], cwd = tempdir, stdout=subprocess.DEVNULL)
with open(tempdir / "stvrzenky.pdf", "rb") as pdffile: with open(tempdir / "stvrzenky.pdf", "rb") as pdffile:

11
treenode/admin.py

@ -1,4 +1,6 @@
from django.contrib import admin from django.contrib import admin
from django.db import models
from django.forms import widgets
from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter
@ -86,3 +88,12 @@ class TextNodeAdmin(PolymorphicChildModelAdmin):
show_in_index = True 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)

2
treenode/templates/treenode/orphanage.html

@ -1,4 +1,4 @@
{% extends "seminar/archiv/base.html" %} {% extends "tvorba/archiv/base.html" %}
{% load static %} {% load static %}
{% block custom_css %} {% block custom_css %}

0
seminar/management/__init__.py → tvorba/__init__.py

116
seminar/admin.py → tvorba/admin.py

@ -1,20 +1,20 @@
from django.contrib import admin from django.contrib import admin
from django.db import models from django.forms import ModelForm
from django.forms import widgets, ModelForm
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter
from solo.admin import SingletonModelAdmin
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
# Todo: reversion # Todo: reversion
import seminar.models as m import soustredeni.models
admin.site.register(m.Rocnik) from seminar.models.tvorba import Rocnik, ZmrazenaVysledkovka, Deadline, Uloha, Problem, Tema, Clanek, Cislo
admin.site.register(m.ZmrazenaVysledkovka)
@admin.register(m.Deadline) admin.site.register(Rocnik)
admin.site.register(ZmrazenaVysledkovka)
@admin.register(Deadline)
class DeadlineAdmin(admin.ModelAdmin): class DeadlineAdmin(admin.ModelAdmin):
actions = ['pregeneruj_vysledkovku'] actions = ['pregeneruj_vysledkovku']
@ -28,42 +28,44 @@ class DeadlineAdmin(admin.ModelAdmin):
# Boilerplate: potřebujeme nějakou permission, protože nějaká haluz v Djangu… # Boilerplate: potřebujeme nějakou permission, protože nějaká haluz v Djangu…
return request.user.is_superuser return request.user.is_superuser
class DeadlineAdminInline(admin.TabularInline): class DeadlineAdminInline(admin.TabularInline):
model = m.Deadline model = Deadline
extra = 0 extra = 0
class CisloForm(ModelForm): class CisloForm(ModelForm):
class Meta: class Meta:
model = m.Cislo model = Cislo
fields = '__all__' fields = '__all__'
def clean(self): def clean(self):
if self.cleaned_data.get('verejne_db') == False: if self.cleaned_data.get('verejne_db') == False:
return self.cleaned_data return self.cleaned_data
# cn = m.CisloNode.objects.get(cislo=self.instance) # cn = CisloNode.objects.get(cislo=self.instance)
# errors = [] # errors = []
# for ch in tl.all_children(cn): # for ch in tl.all_children(cn):
# if isinstance(ch, m.TemaVCisleNode): # if isinstance(ch, TemaVCisleNode):
# if ch.tema.stav not in \ # if ch.tema.stav not in \
# (m.Problem.STAV_ZADANY, m.Problem.STAV_VYRESENY): # (Problem.STAV_ZADANY, Problem.STAV_VYRESENY):
# errors.append(ValidationError('Téma %(tema)s není zadané ani vyřešené', params={'tema':ch.tema})) # errors.append(ValidationError('Téma %(tema)s není zadané ani vyřešené', params={'tema':ch.tema}))
# #
# if isinstance(ch, m.UlohaZadaniNode) or isinstance(ch, m.UlohaVzorakNode): # if isinstance(ch, UlohaZadaniNode) or isinstance(ch, UlohaVzorakNode):
# if ch.uloha.stav not in \ # if ch.uloha.stav not in \
# (m.Problem.STAV_ZADANY, m.Problem.STAV_VYRESENY): # (Problem.STAV_ZADANY, Problem.STAV_VYRESENY):
# errors.append(ValidationError('Úloha %(uloha)s není zadaná ani vyřešená', params={'uloha':ch.uloha})) # errors.append(ValidationError('Úloha %(uloha)s není zadaná ani vyřešená', params={'uloha':ch.uloha}))
# if isinstance(ch, m.ReseniNode): # if isinstance(ch, ReseniNode):
# for problem in ch.reseni.problem_set: # for problem in ch.reseni.problem_set:
# if problem not in \ # if problem not in \
# (m.Problem.STAV_ZADANY, m.Problem.STAV_VYRESENY): # (Problem.STAV_ZADANY, Problem.STAV_VYRESENY):
# errors.append(ValidationError('Problém %s není zadaný ani vyřešený', code=problem)) # errors.append(ValidationError('Problém %s není zadaný ani vyřešený', code=problem))
# if errors: # if errors:
# errors.append(ValidationError(mark_safe('<b>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 <a href="/admin/seminar/cislo">seznamu čísel</a></b>'))) # errors.append(ValidationError(mark_safe('<b>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 <a href="/admin/seminar/cislo">seznamu čísel</a></b>')))
# raise ValidationError(errors) # raise ValidationError(errors)
errors = [] errors = []
for ch in m.Uloha.objects.filter(cislo_zadani=self.instance): for ch in Uloha.objects.filter(cislo_zadani=self.instance):
if ch.stav not in (m.Problem.STAV_ZADANY, m.Problem.STAV_VYRESENY): if ch.stav not in (Problem.STAV_ZADANY, Problem.STAV_VYRESENY):
errors.append( errors.append(
ValidationError('Úloha %(uloha)s není zadaná ani vyřešená', params={'uloha': ch})) ValidationError('Úloha %(uloha)s není zadaná ani vyřešená', params={'uloha': ch}))
if errors: if errors:
@ -78,7 +80,7 @@ class CisloForm(ModelForm):
return self.cleaned_data return self.cleaned_data
@admin.register(m.Cislo) @admin.register(Cislo)
class CisloAdmin(admin.ModelAdmin): class CisloAdmin(admin.ModelAdmin):
form = CisloForm form = CisloForm
actions = ['force_publish', 'pregeneruj_vysledkovky'] actions = ['force_publish', 'pregeneruj_vysledkovky']
@ -86,31 +88,31 @@ class CisloAdmin(admin.ModelAdmin):
def force_publish(self,request,queryset): def force_publish(self,request,queryset):
for cislo in queryset: for cislo in queryset:
# cn = m.CisloNode.objects.get(cislo=cislo) # cn = CisloNode.objects.get(cislo=cislo)
# for ch in tl.all_children(cn): # for ch in tl.all_children(cn):
# if isinstance(ch, m.TemaVCisleNode): # if isinstance(ch, TemaVCisleNode):
# if ch.tema.stav not in (m.Problem.STAV_ZADANY, m.Problem.STAV_VYRESENY): # if ch.tema.stav not in (Problem.STAV_ZADANY, Problem.STAV_VYRESENY):
# ch.tema.stav = m.Problem.STAV_ZADANY # ch.tema.stav = Problem.STAV_ZADANY
# ch.tema.save() # ch.tema.save()
# #
# if isinstance(ch, m.UlohaZadaniNode) or isinstance(ch, m.UlohaVzorakNode): # if isinstance(ch, UlohaZadaniNode) or isinstance(ch, UlohaVzorakNode):
# if ch.uloha.stav not in (m.Problem.STAV_ZADANY, m.Problem.STAV_VYRESENY): # if ch.uloha.stav not in (Problem.STAV_ZADANY, Problem.STAV_VYRESENY):
# ch.uloha.stav = m.Problem.STAV_ZADANY # ch.uloha.stav = Problem.STAV_ZADANY
# ch.uloha.save() # ch.uloha.save()
# if isinstance(ch, m.ReseniNode): # if isinstance(ch, ReseniNode):
# for problem in ch.reseni.problem_set: # for problem in ch.reseni.problem_set:
# if problem not in (m.Problem.STAV_ZADANY, m.Problem.STAV_VYRESENY): # if problem not in (Problem.STAV_ZADANY, Problem.STAV_VYRESENY):
# problem.stav = m.Problem.STAV_ZADANY # problem.stav = Problem.STAV_ZADANY
# problem.save() # problem.save()
for ch in m.Uloha.objects.filter(cislo_zadani=cislo): for ch in Uloha.objects.filter(cislo_zadani=cislo):
if ch.stav not in (m.Problem.STAV_ZADANY, m.Problem.STAV_VYRESENY): if ch.stav not in (Problem.STAV_ZADANY, Problem.STAV_VYRESENY):
ch.stav = m.Problem.STAV_ZADANY ch.stav = Problem.STAV_ZADANY
ch.save() ch.save()
hp = ch.hlavni_problem hp = ch.hlavni_problem
if hp.stav not in (m.Problem.STAV_ZADANY, m.Problem.STAV_VYRESENY): if hp.stav not in (Problem.STAV_ZADANY, Problem.STAV_VYRESENY):
hp.stav = m.Problem.STAV_ZADANY hp.stav = Problem.STAV_ZADANY
hp.save() hp.save()
# TODO Řešení, vzoráky? # TODO Řešení, vzoráky?
@ -133,18 +135,19 @@ class CisloAdmin(admin.ModelAdmin):
return request.user.is_superuser return request.user.is_superuser
@admin.register(m.Problem) @admin.register(Problem)
class ProblemAdmin(PolymorphicParentModelAdmin): class ProblemAdmin(PolymorphicParentModelAdmin):
base_model = m.Problem base_model = Problem
child_models = [ child_models = [
m.Tema, Tema,
m.Clanek, Clanek,
m.Uloha, Uloha,
m.Konfera, soustredeni.models.Konfera,
] ]
# Pokud chceme orezavat na aktualni rocnik, musime do modelu pridat odkaz na rocnik. Zatim bere vse. # Pokud chceme orezavat na aktualni rocnik, musime do modelu pridat odkaz na rocnik. Zatim bere vse.
search_fields = ['nazev'] search_fields = ['nazev']
# V ProblemAdmin to nejde, protoze se to nepropise do deti # V ProblemAdmin to nejde, protoze se to nepropise do deti
class ProblemAdminMixin(object): class ProblemAdminMixin(object):
show_in_index = True show_in_index = True
@ -152,32 +155,23 @@ class ProblemAdminMixin(object):
filter_horizontal = ['opravovatele'] filter_horizontal = ['opravovatele']
@admin.register(m.Tema) @admin.register(Tema)
class TemaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin): class TemaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin):
base_model = m.Tema base_model = Tema
@admin.register(m.Clanek)
class ClanekAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin):
base_model = m.Clanek
@admin.register(m.Uloha) @admin.register(Clanek)
class UlohaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin): class ClanekAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin):
base_model = m.Uloha base_model = Clanek
@admin.register(m.Konfera)
class KonferaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin):
base_model = m.Konfera
@admin.register(Uloha)
class UlohaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin):
base_model = Uloha
class TextAdminInline(admin.TabularInline):
model = m.Text
formfield_overrides = {
models.TextField: {'widget': widgets.TextInput}
}
exclude = ['text_zkraceny_set','text_zkraceny']
admin.site.register(m.Text) @admin.register(soustredeni.models.Konfera)
class KonferaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin):
base_model = soustredeni.models.Konfera
# admin.site.register(m.Pohadka) # admin.site.register(m.Pohadka)
admin.site.register(m.Obrazek)
admin.site.register(m.Nastaveni, SingletonModelAdmin)

6
tvorba/apps.py

@ -0,0 +1,6 @@
from django.apps import AppConfig
class TvorbaConfig(AppConfig):
name = 'tvorba'
verbose_name = 'Tvorba'

0
seminar/management/commands/__init__.py → tvorba/migrations/__init__.py

0
seminar/static/images/no-picture.png → tvorba/static/tvorba/no-picture.png

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

0
seminar/static/images/tema-bez-obrazku.png → tvorba/static/tvorba/tema-bez-obrazku.png

Before

Width:  |  Height:  |  Size: 300 KiB

After

Width:  |  Height:  |  Size: 300 KiB

0
seminar/templates/seminar/archiv/cisla.html → tvorba/templates/tvorba/archiv/cisla.html

0
seminar/templates/seminar/archiv/cislo.html → tvorba/templates/tvorba/archiv/cislo.html

0
seminar/templates/seminar/archiv/cislo_vysledkovka.tex → tvorba/templates/tvorba/archiv/cislo_vysledkovka.tex

0
seminar/templates/seminar/archiv/odmeny.html → tvorba/templates/tvorba/archiv/odmeny.html

0
seminar/templates/seminar/archiv/prispevek.html → tvorba/templates/tvorba/archiv/prispevek.html

0
seminar/templates/seminar/archiv/problem.html → tvorba/templates/tvorba/archiv/problem.html

0
seminar/templates/seminar/archiv/problem_clanek.html → tvorba/templates/tvorba/archiv/problem_clanek.html

0
seminar/templates/seminar/archiv/problem_tema.html → tvorba/templates/tvorba/archiv/problem_tema.html

0
seminar/templates/seminar/archiv/problem_uloha.html → tvorba/templates/tvorba/archiv/problem_uloha.html

0
seminar/templates/seminar/archiv/problem_uloha_tema.html → tvorba/templates/tvorba/archiv/problem_uloha_tema.html

4
seminar/templates/seminar/archiv/rocnik.html → tvorba/templates/tvorba/archiv/rocnik.html

@ -33,7 +33,7 @@
{% if c.titulka_nahled %} {% if c.titulka_nahled %}
<img src="{{ c.titulka_nahled.url }}" alt="{{ c.kod }}" height=180px> <img src="{{ c.titulka_nahled.url }}" alt="{{ c.kod }}" height=180px>
{% else %} {% else %}
{% load static %} <img src="{% static 'images/no-picture.png' %}" height=180px alt="no-picture"> {% load static %} <img src="{% static 'tvorba/no-picture.png' %}" height=180px alt="no-picture">
{% endif %} {% endif %}
</div> </div>
@ -79,7 +79,7 @@
{% if c.titulka_nahled %} {% if c.titulka_nahled %}
<img src="{{ c.titulka_nahled.url }}" alt="{{ c.kod }}" height=180px> <img src="{{ c.titulka_nahled.url }}" alt="{{ c.kod }}" height=180px>
{% else %} {% else %}
{% load static %} <img src="{% static 'images/no-picture.png' %}" height=180px alt="no-picture"> {% load static %} <img src="{% static 'tvorba/no-picture.png' %}" height=180px alt="no-picture">
{% endif %} {% endif %}
</div> </div>

0
seminar/templates/seminar/archiv/rocnik_vysledkovka.tex → tvorba/templates/tvorba/archiv/rocnik_vysledkovka.tex

0
seminar/templates/seminar/archiv/temata.html → tvorba/templates/tvorba/archiv/temata.html

0
seminar/templates/seminar/archiv/tituly.tex → tvorba/templates/tvorba/archiv/tituly.tex

0
seminar/templates/seminar/clanky/organizatorske_clanky.html → tvorba/templates/tvorba/clanky/organizatorske_clanky.html

0
seminar/templates/seminar/clanky/resitelske_clanky.html → tvorba/templates/tvorba/clanky/resitelske_clanky.html

2
seminar/templates/seminar/tematka/rozcestnik.html → tvorba/templates/tvorba/tematka/rozcestnik.html

@ -34,7 +34,7 @@
{% if tematko.obrazek %} {% if tematko.obrazek %}
<img src="{{ tematko.obrazek.url }}" alt="{{ tematko.nazev }}"> <img src="{{ tematko.obrazek.url }}" alt="{{ tematko.nazev }}">
{% else %} {# pokud témátko nemá fotku, zobrazuje se defaultní obrázek #} {% else %} {# pokud témátko nemá fotku, zobrazuje se defaultní obrázek #}
{% load static %} <img src="{% static 'images/tema-bez-obrazku.png' %}" alt="{{ tematko.nazev }}"> {% load static %} <img src="{% static 'tvorba/tema-bez-obrazku.png' %}" alt="{{ tematko.nazev }}">
{% endif %} {% endif %}
</div> </div>
</div> </div>

0
seminar/templates/seminar/tematka/toaletak.html → tvorba/templates/tvorba/tematka/toaletak.html

0
seminar/templates/seminar/zadani/AktualniVysledkovka.html → tvorba/templates/tvorba/zadani/AktualniVysledkovka.html

0
seminar/templates/seminar/zadani/AktualniZadani.html → tvorba/templates/tvorba/zadani/AktualniZadani.html

0
seminar/templates/seminar/zadani/Temata.html → tvorba/templates/tvorba/zadani/Temata.html

0
seminar/templatetags/__init__.py → tvorba/templatetags/__init__.py

0
seminar/templatetags/deadliny.py → tvorba/templatetags/deadliny.py

506
tvorba/testutils.py

@ -0,0 +1,506 @@
# FIXME vypreparovat treenode
import datetime
import lorem
import django.contrib.auth
import logging
from seminar.models import Rocnik, Cislo, Deadline, Problem, Tema, Uloha, TextNode, UlohaVzorakNode, RocnikNode, CisloNode, TemaVCisleNode, Text, UlohaZadaniNode
import seminar.models as m
from treenode.treelib import all_children, insert_last_child, all_children_of_type, create_node_after
from odevzdavatko.testutils import gen_reseni_ulohy
logger = logging.getLogger(__name__)
User = django.contrib.auth.get_user_model()
def gen_zadani_ulohy(rnd, cisla, organizatori, pocet_oboru, poradi_cisla, poradi_problemu):
# Proměnné pro náhodné generování názvů a zadání.
jaka = ["Šachová", "Černá", "Větrná", "Dlouhá", "Křehká", "Rychlá",
"Zákeřná", "Fyzikální"]
co = ["kostka", "smršť", "díra", "zrada", "toulka", "tyč",
"úloha", "blecha"]
sloveso = ["Najděte", "Spočítejte", "Zapište", "Změřte", "Odhadněte"]
koho = ["délku", "počet", "množství", "dílky"]
ceho = ["všech", "správných", "konstatních", "zelených"]
jmeno = ["řešení", "tahů", "čísel", "kalhot", "koulí", "hadů"]
kde = ["na zemi", "ve vesmíru", "ve vzduchu", "na šňůře", "v letadle"]
obory = ["M", "F", "I", "O", "B"]
p = Uloha.objects.create(
# atributy třídy Problem
nazev=" ".join([rnd.choice(jaka), rnd.choice(co)]),
stav=Problem.STAV_ZADANY,
zamereni=rnd.sample(obory, pocet_oboru),
autor=rnd.choice(organizatori),
garant=rnd.choice(organizatori),
kod=str(poradi_problemu),
# atributy třídy Uloha
cislo_zadani=cisla[poradi_cisla-2-1],
cislo_reseni=cisla[poradi_cisla-1],
cislo_deadline=cisla[poradi_cisla-1],
max_body = rnd.randint(1, 8)
)
text = " ".join(
[rnd.choice(sloveso),
rnd.choice(koho),
rnd.choice(ceho),
rnd.choice(jmeno),
rnd.choice(kde)]
)
text_zadani = Text.objects.create(
na_web = text,
do_cisla = text,
)
zad = TextNode.objects.create(text = text_zadani, root = p.cislo_zadani.rocnik.rocniknode)
uloha_zadani = UlohaZadaniNode.objects.create(uloha = p, first_child = zad, root = p.cislo_zadani.rocnik.rocniknode)
p.ulohazadaninode = uloha_zadani
otec_syn(cisla[poradi_cisla-2-1].cislonode, uloha_zadani)
return p
def gen_vzoroveho_reseni_ulohy(rnd, organizatori, uloha, pocet_opravovatelu):
reseni = ["to je přece jasné", "triviální", "omlouváme se,"
"otevřený problém", "neřešitelné", "triviálně triviální",
"použitím věty z prvního semestru na matfyzu",
"jednoduše pomocí látky z druhého semestru na matfyzu",
"netriviální aplikace diferenciálních rovnic", "zadání je vnitřně"
"sporné", "nepopsatelně jednoduché", "pokud jste na to nepřišli,"
"tak jste fakt hloupí"]
# Generování vzorového řešení.
obsah = rnd.choice(reseni)
text_vzoraku = Text.objects.create(
na_web = obsah,
do_cisla = obsah
)
vzorak = TextNode.objects.create(text = text_vzoraku, root = uloha.cislo_zadani.rocnik.rocniknode)
uloha_vzorak = UlohaVzorakNode.objects.create(uloha = uloha, first_child = vzorak, root = uloha.cislo_zadani.rocnik.rocniknode)
uloha.ulohavzoraknode = uloha_vzorak
uloha.opravovatele.set(rnd.sample(organizatori, pocet_opravovatelu))
uloha.save()
return uloha_vzorak
def gen_ulohy_do_cisla(rnd, organizatori, resitele, rocnik_cisla, rocniky, size):
logger.info('Generuji úlohy do čísla (size={})...'.format(size))
k = 0
for rocnik in rocniky:
k += 1
print("Generuji {}. číslo.".format(k))
cisla = rocnik_cisla[k - 1]
for ci in range(3, len(cisla) + 1): # pro všechna čísla
resitele_size = round(7/8 * 30 * size) # očekáváný celkový počet řešitelů
poc_res = rnd.randint(resitele_size//8, resitele_size//4)
# dané číslo řeší něco mezi osminou a čtvrtinou všech řešitelů
# (náhodná hausnumera, možno změnit)
# účelem je, aby se řešení generovala z menší množiny řešitelů a tedy
# bylo více řešení od jednoho řešitele daného čísla
resitele_cisla = rnd.sample(resitele, poc_res)
for pi in range(1, ((size + 1) // 2) + 1): # počet problémů
poc_op = rnd.randint(1, 4) # počet opravovatelů
poc_oboru = rnd.randint(1, 2)
# Generování zadání úlohy a UlohaZadaniNode,
# přivěšení pod dané číslo
p = gen_zadani_ulohy(rnd, cisla, organizatori, poc_oboru, ci, pi)
# Generování vzorového řešení
uloha_vzorak = gen_vzoroveho_reseni_ulohy(rnd, organizatori,
p, poc_op)
insert_last_child(cisla[ci-1].cislonode, uloha_vzorak)
# Generování řešení a hodnocení k úloze
gen_reseni_ulohy(rnd, cisla, p, poc_res, ci,
resitele_cisla, resitele)
return
def gen_rocniky(last_rocnik, size):
logger.info('Generuji ročníky (size={})...'.format(size))
rocniky = []
node = None
for ri in range(min(last_rocnik - size, 1), last_rocnik + 1):
rocnik = Rocnik.objects.create(prvni_rok = 1993 + ri, rocnik = ri)
node2 = RocnikNode.objects.create(rocnik = rocnik, succ = node)
rocnik.save()
node = node2
rocniky.append(rocnik)
return rocniky
def gen_cisla(rnd, rocniky):
logger.info('Generuji čísla...')
rocnik_cisla = []
for rocnik in rocniky:
otec = True
cisla = []
cisel = rnd.randint(4, 8)
node = None
for ci in range(1, cisel + 1):
# první číslo vydáváme typicky okolo prázdnin
# (ci - 1)*2 zaručuje první číslo v červnu a všechna
# další po dvou měsících (což je rozumná aproximace)
mesic_vydani = (ci - 1)*2 + 6
# celociselné dělení mi řekne, jestli to je první nebo druhý rok ročníku
vydano = datetime.date(rocnik.prvni_rok + mesic_vydani // 12,
(mesic_vydani - 1) % 12 + 1,
rnd.randint(1, 28))
deadline = datetime.date(rocnik.prvni_rok + (mesic_vydani + 2) // 12,
(mesic_vydani + 1) % 12 + 1,
rnd.randint(1, 28))
cislo = Cislo.objects.create(
rocnik = rocnik,
poradi = str(ci),
datum_vydani=vydano,
verejne_db=True,
)
node2 = CisloNode.objects.get(cislo = cislo)
node2.succ = node
node2.root = rocnik.rocniknode
cislo.save()
deadline = Deadline.objects.create(
cislo=cislo,
deadline=deadline,
typ=Deadline.TYP_CISLA,
verejna_vysledkovka=True,
)
deadline.save()
node = node2
if otec:
otec = False
rocnik.rocniknode.first_child = node
rocnik.save()
cisla.append(cislo)
rocnik_cisla.append(cisla)
return rocnik_cisla
def add_first_child(node, child):
node.first_child = child
node.save()
return
def get_text():
odstavec = lorem.paragraph()
return Text.objects.create(na_web = odstavec, do_cisla = odstavec)
def gen_dlouhe_tema(rnd, organizatori, rocnik, nazev, obor, kod):
tema = Tema.objects.create(
nazev=nazev,
stav=Problem.STAV_ZADANY,
zamereni="M",
autor=rnd.choice(organizatori),
garant=rnd.choice(organizatori),
kod=str(kod),
tema_typ=rnd.choice(Tema.TEMA_CHOICES)[0],
rocnik=rocnik,
abstrakt = lorem.paragraph()
)
# Generování struktury k tématu
cisla = sorted(rocnik.cisla.all(), key=lambda cislo: cislo.poradi)
for cislo in cisla:
# Přidáme TemaVCisleNode do daného čísla
cislo_node = cislo.cislonode
tema_cislo_node = TemaVCisleNode.objects.create(tema = tema, root = cislo_node.root)
insert_last_child(cislo_node, tema_cislo_node)
# Přidávání obsahu do čísla
cast_node = m.CastNode.objects.create(nadpis = "Příspěvek k číslu {}".format(cislo.kod), root=cislo_node.root)
add_first_child(tema_cislo_node, cast_node)
text_node = TextNode.objects.create(text = get_text(), root=cislo_node.root)
add_first_child(cast_node, text_node)
cast_node2 = m.CastNode.objects.create(nadpis = "První podproblém", root=cislo_node.root)
add_first_child(text_node, cast_node2)
text_node2 = TextNode.objects.create(text = get_text(), root=cislo_node.root)
add_first_child(cast_node2, text_node2)
cast_node3 = m.CastNode.objects.create(nadpis = "Druhý podproblém", root=cislo_node.root)
add_first_child(text_node2, cast_node3)
text_node3 = TextNode.objects.create(text = get_text(), root=cislo_node.root)
add_first_child(cast_node3, text_node3)
cast_node4 = m.CastNode.objects.create(nadpis = "Třetí podproblém", root=cislo_node.root)
add_first_child(text_node3, cast_node4)
text_node4 = TextNode.objects.create(text = get_text(), root=cislo_node.root)
add_first_child(cast_node3, text_node4)
cast_node3a = m.CastNode.objects.create(nadpis = "Podproblém paralelní s "
"druhým podproblémem", root=cislo_node.root)
cast_node3.succ = cast_node3a
cast_node3.save()
text_node3a = TextNode.objects.create(text = get_text(), root=cislo_node.root)
add_first_child(cast_node3a, text_node3a)
# Občas přidáme mezičíslo
if rnd.randint(1, 3) == 1:
create_node_after(cislo_node, m.MezicisloNode, root=cislo_node.root)
mezicislo_node = cislo_node.succ
cast_node_mezicislo = m.CastNode.objects.create(
nadpis = "Příspěvek k mezičíslu".format(cislo.kod), root=cislo_node.root)
add_first_child(mezicislo_node, cast_node_mezicislo)
odstavec = lorem.paragraph()
text_mezicislo = Text.objects.create(na_web = odstavec, do_cisla = odstavec)
text_node_mezicislo = TextNode.objects.create(text = text_mezicislo, root=cislo_node.root)
add_first_child(cast_node_mezicislo, text_node_mezicislo)
return tema
def gen_temata(rnd, rocniky, rocnik_cisla, organizatori):
logger.info('Generuji témata...')
jake = ["Hravé", "Fyzikální", "Nejlepší", "Totálně masakrální",
"Šokující", "Magnetické", "Modré", "Překvapivé",
"Plasmatické", "Novoroční"]
co = ["téma", "záření", "stavení", "jiskření", "jelito",
"drama", "kuře", "moře", "klání", "proudění", "čekání"]
poc_oboru = rnd.randint(1, 2)
rocnik_temata = []
# Věříme, že rocnik_cisla je pole polí čísel podle ročníků, tak si necháme dát
# vždycky jeden ročník a k němu příslušná čísla.
for rocnik, cisla in zip(rocniky, rocnik_cisla):
kod = 1
letosni_temata = []
# Do každého ročníku vymyslíme tři (zatím) témata, v každém z prvních čísel jedno
for zacatek_tematu in range(1, 3):
# Vygenerujeme téma
t = Tema.objects.create(
# atributy třídy Problem
nazev=" ".join([rnd.choice(jake), rnd.choice(co)]),
stav=Problem.STAV_ZADANY,
zamereni=rnd.sample(["M", "F", "I", "O", "B"], poc_oboru),
autor=rnd.choice(organizatori),
garant=rnd.choice(organizatori),
kod=str(kod),
# atributy třídy Téma
tema_typ=rnd.choice(Tema.TEMA_CHOICES)[0],
rocnik=rocnik,
abstrakt = "Abstrakt tematka {}".format(kod)
)
kod += 1
# Vymyslíme, kdy skončí
konec_tematu = min(rnd.randint(zacatek_tematu, 7), len(cisla))
# Vyrobíme TemaVCisleNody pro obsah
for i in range(zacatek_tematu, konec_tematu+1):
node = TemaVCisleNode.objects.create(tema = t,root=rocnik.rocniknode)
# FIXME: Není to off-by-one?
otec = cisla[i-1].cislonode
otec_syn(otec, node)
# Vymyslíme, kdo to bude opravovat
poc_opravovatelu = rnd.randint(1, 3)
t.opravovatele.set(rnd.sample(organizatori, poc_opravovatelu))
# Uložíme všechno
t.save()
letosni_temata.append((zacatek_tematu, konec_tematu, t))
rocnik_temata.append(letosni_temata)
return rocnik_temata
def gen_ulohy_tematu(rnd, organizatori, resitele, tema, kod, cislo, cislo_se_vzorakem):
""" Generování úlohy k danému tématu. """
# Proměnné pro náhodné generování názvů a zadání.
jaka = ["Šachová", "Černá", "Větrná", "Dlouhá", "Křehká", "Rychlá",
"Zákeřná", "Fyzikální"]
co = ["kostka", "smršť", "díra", "zrada", "toulka", "tyč",
"úloha", "blecha"]
sloveso = ["Najděte", "Spočítejte", "Zapište", "Změřte", "Odhadněte"]
koho = ["délku", "počet", "množství", "dílky"]
ceho = ["všech", "správných", "konstatních", "zelených"]
jmeno = ["řešení", "tahů", "čísel", "kalhot", "koulí", "hadů"]
kde = ["na zemi", "ve vesmíru", "ve vzduchu", "na šňůře", "v letadle"]
obory = ["M", "F", "I", "O", "B"]
uloha = Uloha.objects.create(
nazev=": ".join([tema.nazev,
"úloha {}.".format(kod)]),
nadproblem=tema,
stav=Problem.STAV_ZADANY,
zamereni=tema.zamereni,
autor=tema.autor,
garant=tema.garant,
kod=str(kod),
cislo_zadani=cislo,
cislo_reseni=cislo_se_vzorakem,
cislo_deadline=cislo_se_vzorakem,
max_body = rnd.randint(1, 8)
)
# Samotný obsah následně vzniklého Textu zadání
obsah = " ".join(
[rnd.choice(sloveso),
rnd.choice(koho),
rnd.choice(ceho),
rnd.choice(jmeno),
rnd.choice(kde)]
)
text_zadani = Text.objects.create(
na_web = obsah,
do_cisla = obsah,
)
zad = TextNode.objects.create(text = text_zadani, root=tema.temavcislenode_set.first().root)
uloha_zadani = UlohaZadaniNode.objects.create(uloha=uloha, first_child = zad, root=tema.temavcislenode_set.first().root)
uloha.ulohazadaninode = uloha_zadani
# Generování řešení a hodnocení k úloze
gen_reseni_ulohy(rnd, [cislo], uloha, len(resitele)//4, 1,
resitele, resitele)
return uloha, uloha_zadani
def gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori, resitele):
logger.info('Generuji úlohy k tématům...')
# Ke každému ročníku si vezmeme příslušná čísla a témata
for rocnik, cisla, temata in zip(rocniky, rocnik_cisla, rocnik_temata):
# Do každého čísla nagenerujeme ke každému témátku pár úložek
for cislo in cisla:
print("Generuji úložky do {}-tého čísla".format(cislo.poradi))
# Vzorák bude o dvě čísla dál
cislo_se_vzorakem = Cislo.objects.filter(
rocnik=rocnik,
poradi=str(int(cislo.poradi) + 2),
)
# Pokud není číslo pro vzorák, tak se dá do posledního čísla
# (i kdyby tam mělo být zadání i řešení...)
# Tohle sice umožňuje vygenerovat vzorák do čísla dávno po konci témátka,
# ale to nám pro jednoduchost nevadí.
if len(cislo_se_vzorakem) == 0:
cislo_se_vzorakem = cisla[-1]
else:
cislo_se_vzorakem = cislo_se_vzorakem.first()
for tema_node in all_children_of_type(cislo.cislonode, TemaVCisleNode):
tema = tema_node.tema
# Pokud už témátko skončilo, žádné úložky negenerujeme
# FIXME: Bylo by hezčí, kdyby se čísla předávala jako Cislo a ne
# jako int v té trojici (start, konec, tema)
if not temata[int(tema.kod)-1][1] >= int(cislo_se_vzorakem.poradi):
continue
# Generujeme 1 až 4 úložky k tomuto témátku do tohoto čísla.
for kod in range(1, rnd.randint(1, 4)):
u, uz = gen_ulohy_tematu(rnd, organizatori, resitele, tema, kod,
cislo, cislo_se_vzorakem)
insert_last_child(tema_node, uz)
poc_op = rnd.randint(1, 4)
uvz = gen_vzoroveho_reseni_ulohy(rnd, organizatori,
u, poc_op)
# Najdeme správný TemaVCisleNode pro vložení vzoráku
res_tema_node = None;
for node in all_children(cislo_se_vzorakem.cislonode):
if isinstance(node, TemaVCisleNode):
if node.tema == tema:
res_tema_node = node
if res_tema_node is None:
raise LookupError("Nenalezen Node pro vložení vzoráku")
insert_last_child(res_tema_node, uvz)
u.save()
return
def otec_syn(otec, syn):
bratr = otec.first_child
syn.succ = bratr
otec.first_child = syn
syn.save()
otec.save()
def gen_clanek(rnd, organizatori, resitele):
logger.info("Generuji článek do čísla 22.2")
clanek = m.Clanek.objects.create(
nazev="Článek o Lorem ipsum",
nadproblem=None,
stav='vyreseny',
zamereni=['I'],
garant=rnd.choice(organizatori),
kod='cl',
)
clanek.save()
reseni = m.Reseni.objects.create(
zverejneno=True,
)
reseni.resitele.add(rnd.choice(resitele))
reseni.save()
cislo = m.Cislo.objects.get(rocnik__rocnik=22, poradi=2)
cislonode = cislo.cislonode
hodnoceni = m.Hodnoceni.objects.create(
body=15.0,
cislo_body=cislo,
reseni=reseni,
problem=clanek,
)
hodnoceni.save()
reseninode = m.ReseniNode.objects.create(
reseni=reseni
)
reseninode.save()
# Bude to celý text
reseni.text_cely = reseninode
reseni.save()
from treenode.treelib import insert_last_child, create_child
insert_last_child(cislonode, reseninode)
# Vyrobíme nějaký obsah
# FIXME: Ten, kdo vymyslel TreeLib (mj. týž, kdo psal tenhle kód),
# nevyrobil vhodnou funkci, takže to postavíme pozpátku pomocí create_child
# (které vyrábí _prvního_ syna)
create_child(reseninode, m.CastNode, nadpis="Lorem ipsum")
# Taky ten člověk nevyrobil vracení nových věcí...
castnode = reseninode.first_child
# Úvodní odstaveček
obsah = "Tohle je zamyšlení o textu lorem ipsum. Začneme a skončíme ukázkou."
text = m.Text.objects.create(
na_web=obsah,
do_cisla=obsah,
)
text.save()
create_child(reseninode, m.TextNode, text=text)
# Několik odstavců lorem ipsum
for _ in range(rnd.randint(3, 7)):
lipsum = lorem.paragraph()
text = m.Text.objects.create(
na_web=lipsum,
do_cisla=lipsum,
)
text.save()
create_child(castnode, m.TextNode, text=text)
logger.info(f"Článek vygenerován (reseni={reseni.id}, treenode={reseninode.id})")

15
seminar/urls.py → tvorba/urls.py

@ -1,15 +1,11 @@
from django.urls import path, include, re_path from django.urls import path, include, re_path
from . import views from . import views
from .utils import org_required from personalni.utils import org_required
urlpatterns = [ urlpatterns = [
# path('aktualni/temata/', views.TemataRozcestnikView), # path('aktualni/temata/', views.TemataRozcestnikView),
# path('<int:rocnik>/t<int:tematko>/', views.TematkoView), # path('<int:rocnik>/t<int:tematko>/', 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 # Archiv
path('archiv/rocniky/', views.ArchivView.as_view(), name="seminar_archiv_rocniky"), path('archiv/rocniky/', views.ArchivView.as_view(), name="seminar_archiv_rocniky"),
path('archiv/temata/', views.ArchivTemataView.as_view(), name="seminar_archiv_temata"), path('archiv/temata/', views.ArchivTemataView.as_view(), name="seminar_archiv_temata"),
@ -65,20 +61,11 @@ urlpatterns = [
org_required(views.TitulyView), org_required(views.TitulyView),
name='seminar_cislo_titul' name='seminar_cislo_titul'
), ),
path(
'stav',
org_required(views.StavDatabazeView),
name='stav_databaze'
),
path( path(
'cislo/<int:trocnik>.<str:tcislo>/odmeny/<int:frocnik>.<str:fcislo>/', 'cislo/<int:trocnik>.<str:tcislo>/odmeny/<int:frocnik>.<str:fcislo>/',
org_required(views.OdmenyView.as_view()), org_required(views.OdmenyView.as_view()),
name="seminar_archiv_odmeny"), name="seminar_archiv_odmeny"),
path('', views.TitulniStranaView.as_view(), name='titulni_strana'),
path('jak-resit/', views.JakResitView.as_view(), name='jak_resit'),
path('stare-novinky/', views.StareNovinkyView.as_view(), name='stare_novinky'),
# Dočasné & neodladěné: # Dočasné & neodladěné:
path( path(
'hidden/hromadne_pridani', 'hidden/hromadne_pridani',

89
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)

584
tvorba/views/__init__.py

@ -0,0 +1,584 @@
# Dočsasné views
from .docasne import *
# Zbytek
from django.shortcuts import get_object_or_404, render
from django.http import HttpResponse
from django.urls import reverse
from django.core.exceptions import ObjectDoesNotExist
from django.views import generic
from django.utils.translation import gettext as _
from django.http import Http404
from django.db.models import Q, Sum, Count
from django.views.generic.base import RedirectView
from django.core.exceptions import PermissionDenied
import seminar.models as s
import seminar.models as m
from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, \
Resitel, Novinky, Tema, Clanek, \
Deadline # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci
#from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva
from treenode import treelib
import treenode.templatetags as tnltt
import treenode.serializers as vr
from vysledkovky.utils import body_resitelu, VysledkovkaCisla, \
VysledkovkaRocniku, VysledkovkaDoTeXu
from datetime import date, datetime
from itertools import groupby
from collections import OrderedDict
import os
import os.path as op
from django.conf import settings
import unicodedata
import logging
import time
import personalni.views
from .. import utils
# ze starého modelu
#def verejna_temata(rocnik):
# """
# Vrací queryset zveřejněných témat v daném ročníku.
# """
# return Problem.objects.filter(typ=Problem.TYP_TEMA, cislo_zadani__rocnik=rocnik, cislo_zadani__verejne_db=True).order_by('kod')
#
#def temata_v_rocniku(rocnik):
# return Problem.objects.filter(typ=Problem.TYP_TEMA, rocnik=rocnik)
logger = logging.getLogger(__name__)
def get_problemy_k_tematu(tema):
return Problem.objects.filter(nadproblem = tema)
# FIXME: Pozor, níž je ještě jeden ProblemView!
#class ProblemView(generic.DetailView):
# model = s.Problem
# # Zkopírujeme template_name od TreeNodeView, protože jsme prakticky jen trošku upravený TreeNodeView
# template_name = TreeNodeView.template_name
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# user = self.request.user
# # Teď potřebujeme doplnit tnldata do kontextu.
# # Ošklivý type switch, hezčí by bylo udělat to polymorfni. FIXME.
# if False:
# # Hezčí formátování zbytku :-P
# pass
# elif isinstance(self.object, s.Clanek) or isinstance(self.object, s.Konfera):
# # Tyhle Problémy mají ŘešeníNode
# context['tnldata'] = TNLData.from_treenode(self.object.reseninode,user)
# elif isinstance(self.object, s.Uloha):
# # FIXME: Teď vždycky zobrazujeme i vzorák! Možná by bylo hezčí/lepší mít to stejně jako pro Téma: procházet jen dosažitelné z Ročníku / čísla / whatever
# tnl_zadani = TNLData.from_treenode(self.object.ulohazadaninode,user)
# tnl_vzorak = TNLData.from_treenode(self.object.ulohavzoraknode,user)
# context['tnldata'] = TNLData.from_tnldata_list([tnl_zadani, tnl_vzorak])
# elif isinstance(self.object, s.Tema):
# rocniknode = self.object.rocnik.rocniknode
# context['tnldata'] = TNLData.filter_treenode(rocniknode, lambda x: isinstance(x, s.TemaVCisleNode))
# else:
# raise ValueError("Obecný problém nejde zobrazit.")
# return context
#class AktualniZadaniView(generic.TemplateView):
# template_name = 'treenode/treenode.html'
# TODO Co chceme vlastně zobrazovat na této stránce? Zatím je zde aktuální číslo, ale může tu být cokoli jiného...
#class AktualniZadaniView(TreeNodeView):
# def get_object(self):
# nastaveni = get_object_or_404(Nastaveni)
# return nastaveni.aktualni_cislo.cislonode
#
# def get_context_data(self,**kwargs):
# nastaveni = get_object_or_404(Nastaveni)
# context = super().get_context_data(**kwargs)
# verejne = nastaveni.aktualni_cislo.verejne()
# context['verejne'] = verejne
# return context
def AktualniZadaniView(request):
nastaveni = get_object_or_404(Nastaveni)
verejne = nastaveni.aktualni_cislo.verejne()
return render(request, 'tvorba/zadani/AktualniZadani.html',
{'nastaveni': nastaveni,
'verejne': verejne,
},
)
def ZadaniTemataView(request):
nastaveni = get_object_or_404(Nastaveni)
verejne = nastaveni.aktualni_cislo.verejne()
akt_rocnik = nastaveni.aktualni_cislo.rocnik
temata = s.Tema.objects.filter(rocnik=akt_rocnik, stav='zadany')
return render(request, 'tvorba/tematka/rozcestnik.html',
{
'tematka': temata,
'verejne': verejne,
},
)
# nastaveni = get_object_or_404(Nastaveni)
# temata = verejna_temata(nastaveni.aktualni_rocnik)
# for t in temata:
# if request.user.is_staff:
# t.prispevky = t.prispevek_set.filter(problem=t)
# else:
# t.prispevky = t.prispevek_set.filter(problem=t, zverejnit=True)
# return render(request, 'tvorba/zadani/Temata.html',
# {
# 'temata': temata,
# }
# )
#
#
#
#def TematkoView(request, rocnik, tematko):
# nastaveni = s.Nastaveni.objects.first()
# rocnik_object = s.Rocnik.objects.filter(rocnik=rocnik)
# tematko_object = s.Tema.objects.filter(rocnik=rocnik_object[0], kod=tematko)
# seznam = vytahniZLesaSeznam(tematko_object[0], nastaveni.aktualni_rocnik().rocniknode)
# for node, depth in seznam:
# if node.isinstance(node, s.KonferaNode):
# raise Exception("Not implemented yet")
# if node.isinstance(node, s.PohadkaNode): # Mohu ignorovat, má pod sebou
# pass
#
# return render(request, 'tvorba/tematka/toaletak.html', {})
#
#
#def TemataRozcestnikView(request):
# print("=============================================")
# nastaveni = s.Nastaveni.objects.first()
# tematka_objects = s.Tema.objects.filter(rocnik=nastaveni.aktualni_rocnik())
# tematka = [] #List tematka obsahuje pro kazde tematko object a list vsech TemaVCisleNodu - implementované pomocí slovníku
# for tematko_object in tematka_objects:
# print("AKTUALNI TEMATKO")
# print(tematko_object.id)
# odkazy = vytahniZLesaSeznam(tematko_object, nastaveni.aktualni_rocnik().rocniknode, pouze_zajimave = True) #Odkazy jsou tuply (node, depth) v listu
# print(odkazy)
# cisla = [] # List tuplů (nazev cisla, list odkazů)
# vcisle = []
# cislo = None
# for odkaz in odkazy:
# if odkaz[1] == 0:
# if cislo != None:
# cisla.append((cislo, vcisle))
# cislo = (odkaz[0].getOdkazStr(), odkaz[0].getOdkaz())
# vcisle = []
# else:
# print(odkaz[0].getOdkaz())
# vcisle.append((odkaz[0].getOdkazStr(), odkaz[0].getOdkaz()))
# if cislo != None:
# cisla.append((cislo, vcisle))
#
# print(cisla)
# tematka.append({
# "kod" : tematko_object.kod,
# "nazev" : tematko_object.nazev,
# "abstrakt" : tematko_object.abstrakt,
# "obrazek": tematko_object.obrazek,
# "cisla" : cisla
# })
# return render(request, 'tvorba/tematka/rozcestnik.html', {"tematka": tematka, "rocnik" : nastaveni.aktualni_rocnik().rocnik})
#
def ZadaniAktualniVysledkovkaView(request):
nastaveni = get_object_or_404(Nastaveni)
# Aktualni verejna vysledkovka
rocnik = nastaveni.aktualni_rocnik
context = {'vysledkovka': VysledkovkaRocniku(rocnik, True)}
# kdyz neni verejna vysledkovka, tak zobraz starou
if len(context['vysledkovka'].cisla_rocniku) == 0:
try:
minuly_rocnik = Rocnik.objects.get(
rocnik=(rocnik.rocnik-1))
rocnik = minuly_rocnik
# Přepíšeme prázdnou výsledkovku výsledkovkou z minulého ročníku
context['vysledkovka'] = VysledkovkaRocniku(rocnik, True)
except ObjectDoesNotExist:
pass
context['rocnik'] = rocnik
return render(
request,
'tvorba/zadani/AktualniVysledkovka.html',
context
)
### Titulni strana
def aktualni_temata(rocnik):
"""
Vrací PolymorphicQuerySet témat v daném ročníku, ke kterým se aktuálně 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)<F12>:
# 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)

0
seminar/views/docasne.py → tvorba/views/docasne.py

193
seminar/views/views_all.py → tvorba/views/views_all.py

@ -8,15 +8,13 @@ from django.http import Http404
from django.db.models import Q, Sum, Count from django.db.models import Q, Sum, Count
from django.views.generic.base import RedirectView from django.views.generic.base import RedirectView
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.contrib.staticfiles.finders import find
import seminar.models as s import seminar.models as s
import seminar.models as m import seminar.models as m
from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, \ 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 Deadline # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci
#from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva #from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva
from seminar import utils
from treenode import treelib from treenode import treelib
import treenode.templatetags as tnltt import treenode.templatetags as tnltt
import treenode.serializers as vr import treenode.serializers as vr
@ -24,22 +22,18 @@ from vysledkovky.utils import body_resitelu, VysledkovkaCisla, \
VysledkovkaRocniku, VysledkovkaDoTeXu VysledkovkaRocniku, VysledkovkaDoTeXu
from datetime import date, datetime from datetime import date, datetime
from django.utils import timezone
from itertools import groupby from itertools import groupby
from collections import OrderedDict from collections import OrderedDict
import tempfile
import subprocess
import shutil
import os import os
import os.path as op import os.path as op
from django.conf import settings from django.conf import settings
import unicodedata import unicodedata
import logging import logging
import time import time
from collections.abc import Sequence
import http
from seminar.utils import aktivniResitele import personalni.views
from .. import utils
# ze starého modelu # ze starého modelu
#def verejna_temata(rocnik): #def verejna_temata(rocnik):
@ -88,7 +82,7 @@ def get_problemy_k_tematu(tema):
#class AktualniZadaniView(generic.TemplateView): #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... # 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): #class AktualniZadaniView(TreeNodeView):
@ -106,7 +100,7 @@ def get_problemy_k_tematu(tema):
def AktualniZadaniView(request): def AktualniZadaniView(request):
nastaveni = get_object_or_404(Nastaveni) nastaveni = get_object_or_404(Nastaveni)
verejne = nastaveni.aktualni_cislo.verejne() verejne = nastaveni.aktualni_cislo.verejne()
return render(request, 'seminar/zadani/AktualniZadani.html', return render(request, 'tvorba/zadani/AktualniZadani.html',
{'nastaveni': nastaveni, {'nastaveni': nastaveni,
'verejne': verejne, 'verejne': verejne,
}, },
@ -117,7 +111,7 @@ def ZadaniTemataView(request):
verejne = nastaveni.aktualni_cislo.verejne() verejne = nastaveni.aktualni_cislo.verejne()
akt_rocnik = nastaveni.aktualni_cislo.rocnik akt_rocnik = nastaveni.aktualni_cislo.rocnik
temata = s.Tema.objects.filter(rocnik=akt_rocnik, stav='zadany') 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, 'tematka': temata,
'verejne': verejne, 'verejne': verejne,
@ -132,7 +126,7 @@ def ZadaniTemataView(request):
# t.prispevky = t.prispevek_set.filter(problem=t) # t.prispevky = t.prispevek_set.filter(problem=t)
# else: # else:
# t.prispevky = t.prispevek_set.filter(problem=t, zverejnit=True) # 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, # 'temata': temata,
# } # }
@ -151,7 +145,7 @@ def ZadaniTemataView(request):
# if node.isinstance(node, s.PohadkaNode): # Mohu ignorovat, má pod sebou # if node.isinstance(node, s.PohadkaNode): # Mohu ignorovat, má pod sebou
# pass # pass
# #
# return render(request, 'seminar/tematka/toaletak.html', {}) # return render(request, 'tvorba/tematka/toaletak.html', {})
# #
# #
#def TemataRozcestnikView(request): #def TemataRozcestnikView(request):
@ -187,7 +181,7 @@ def ZadaniTemataView(request):
# "obrazek": tematko_object.obrazek, # "obrazek": tematko_object.obrazek,
# "cisla" : cisla # "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): def ZadaniAktualniVysledkovkaView(request):
@ -211,25 +205,13 @@ def ZadaniAktualniVysledkovkaView(request):
context['rocnik'] = rocnik context['rocnik'] = rocnik
return render( return render(
request, request,
'seminar/zadani/AktualniVysledkovka.html', 'tvorba/zadani/AktualniVysledkovka.html',
context context
) )
### Titulni strana ### 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): def aktualni_temata(rocnik):
""" """
Vrací PolymorphicQuerySet témat v daném ročníku, ke kterým se aktuálně něco odevzdat. Vrací PolymorphicQuerySet témat v daném ročníku, ke kterým se aktuálně něco odevzdat.
@ -237,73 +219,12 @@ def aktualni_temata(rocnik):
return Tema.objects.filter(rocnik=rocnik, stav='zadany').order_by('kod') return Tema.objects.filter(rocnik=rocnik, stav='zadany').order_by('kod')
class TitulniStranaView(generic.ListView):
template_name= 'seminar/titulnistrana/titulnistrana.html'
def get_queryset(self):
return spravne_novinky(self.request)[:3]
def get_context_data(self, **kwargs):
context = super(TitulniStranaView, self).get_context_data(**kwargs)
nastaveni = get_object_or_404(Nastaveni)
deadline = m.Deadline.objects.filter(deadline__gte=timezone.now()).order_by("deadline").first()
context['nejblizsi_deadline'] = deadline
# Aktuální témata
nazvy_a_odkazy_na_aktualni_temata = []
akt_temata = aktualni_temata(nastaveni.aktualni_rocnik)
for tema in akt_temata:
# FIXME: netuším, jestli funguje tema.verejne_url(), nemáme testdata na témátka - je to asi url vzhledem k ročníku
nazvy_a_odkazy_na_aktualni_temata.append({'nazev':tema.nazev,'url':tema.verejne_url()})
context['aktualni_temata'] = nazvy_a_odkazy_na_aktualni_temata
print(context)
return context
class StareNovinkyView(generic.ListView):
template_name = 'seminar/stare_novinky.html'
def get_queryset(self):
return spravne_novinky(self.request)
### Co je M&M
# Organizatori
def aktivniOrganizatori(datum=timezone.now()):
return Organizator.objects.exclude(
organizuje_do__isnull=False,
organizuje_do__lt=datum
).order_by('osoba__jmeno')
class CojemamOrganizatoriView(generic.ListView):
model = Organizator
template_name = 'seminar/cojemam/organizatori.html'
queryset = aktivniOrganizatori()
def get_context_data(self, **kwargs):
context = super(CojemamOrganizatoriView, self).get_context_data(**kwargs)
context['aktivni'] = True
return context
class CojemamOrganizatoriStariView(generic.ListView):
model = Organizator
template_name = 'seminar/cojemam/organizatori.html'
queryset = Organizator.objects.exclude(
id__in=aktivniOrganizatori()).order_by('-organizuje_do')
### Archiv ### Archiv
class ArchivView(generic.ListView): class ArchivView(generic.ListView):
model = Rocnik model = Rocnik
template_name='seminar/archiv/cisla.html' template_name = 'tvorba/archiv/cisla.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(ArchivView, self).get_context_data(**kwargs) context = super(ArchivView, self).get_context_data(**kwargs)
@ -316,7 +237,7 @@ class ArchivView(generic.ListView):
for i, c in enumerate(cisla): for i, c in enumerate(cisla):
# Výchozí nastavení # Výchozí nastavení
if c.rocnik not in urls: 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 # NOTE: tohle možná nastavuje poslední titulku
if c.titulka_nahled: if c.titulka_nahled:
urls[c.rocnik] = c.titulka_nahled.url urls[c.rocnik] = c.titulka_nahled.url
@ -331,7 +252,7 @@ class ArchivView(generic.ListView):
class RocnikView(generic.DetailView): class RocnikView(generic.DetailView):
model = Rocnik model = Rocnik
template_name = 'seminar/archiv/rocnik.html' template_name = 'tvorba/archiv/rocnik.html'
# Vlastni ziskavani objektu z databaze podle (Rocnik.rocnik) # Vlastni ziskavani objektu z databaze podle (Rocnik.rocnik)
def get_object(self, queryset=None): def get_object(self, queryset=None):
@ -371,7 +292,7 @@ def resiteleRocnikuCsvExportView(request, rocnik):
# s.Clanek: "clanek", # s.Clanek: "clanek",
# } # }
# context = super().get_context_data(**kwargs) # 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): # def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs) # context = super().get_context_data(**kwargs)
@ -387,7 +308,7 @@ def resiteleRocnikuCsvExportView(request, rocnik):
class CisloView(generic.DetailView): class CisloView(generic.DetailView):
# FIXME zobrazování témátek a vůbec, teď je tam jen odkaz na číslo v pdf # FIXME zobrazování témátek a vůbec, teď je tam jen odkaz na číslo v pdf
model = Cislo model = Cislo
template_name = 'seminar/archiv/cislo.html' template_name = 'tvorba/archiv/cislo.html'
# Vlastni ziskavani objektu z databaze podle (Rocnik.rocnik) # Vlastni ziskavani objektu z databaze podle (Rocnik.rocnik)
def get_object(self, queryset=None): def get_object(self, queryset=None):
@ -430,7 +351,7 @@ class CisloView(generic.DetailView):
class ArchivTemataView(generic.ListView): class ArchivTemataView(generic.ListView):
model = Problem 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') queryset = Tema.objects.filter(stav=Problem.STAV_ZADANY).select_related('rocnik').order_by('rocnik', 'kod')
def get_context_data(self, *args, **kwargs): def get_context_data(self, *args, **kwargs):
@ -441,13 +362,13 @@ class ArchivTemataView(generic.ListView):
return ctx return ctx
class OdmenyView(generic.TemplateView): class OdmenyView(generic.TemplateView):
template_name = 'seminar/archiv/odmeny.html' template_name = 'tvorba/archiv/odmeny.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
fromcislo = get_object_or_404(Cislo, rocnik=self.kwargs.get('frocnik'), poradi=self.kwargs.get('fcislo')) 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')) 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): def get_diff(from_deadline: Deadline, to_deadline: Deadline):
frombody = body_resitelu(resitele=resitele, jen_verejne=False, do=from_deadline) frombody = body_resitelu(resitele=resitele, jen_verejne=False, do=from_deadline)
@ -487,7 +408,7 @@ class CisloVysledkovkaView(CisloView):
"""View vytvořené pro stránku zobrazující výsledkovku čísla v TeXu.""" """View vytvořené pro stránku zobrazující výsledkovku čísla v TeXu."""
model = Cislo model = Cislo
template_name = 'seminar/archiv/cislo_vysledkovka.tex' template_name = 'tvorba/archiv/cislo_vysledkovka.tex'
#content_type = 'application/x-tex; charset=UTF8' #content_type = 'application/x-tex; charset=UTF8'
#umozni rovnou stahnout TeXovsky dokument #umozni rovnou stahnout TeXovsky dokument
content_type = 'text/plain; charset=UTF8' content_type = 'text/plain; charset=UTF8'
@ -518,7 +439,7 @@ class PosledniCisloVysledkovkaView(generic.DetailView):
"""View vytvořené pro zobrazení výsledkovky posledního čísla v TeXu.""" """View vytvořené pro zobrazení výsledkovky posledního čísla v TeXu."""
model = Rocnik model = Rocnik
template_name = 'seminar/archiv/cislo_vysledkovka.tex' template_name = 'tvorba/archiv/cislo_vysledkovka.tex'
content_type = 'text/plain; charset=UTF8' content_type = 'text/plain; charset=UTF8'
def get_object(self, queryset=None): def get_object(self, queryset=None):
@ -552,7 +473,7 @@ class PosledniCisloVysledkovkaView(generic.DetailView):
class RocnikVysledkovkaView(RocnikView): class RocnikVysledkovkaView(RocnikView):
""" View vytvořené pro stránku zobrazující výsledkovku ročníku v TeXu.""" """ View vytvořené pro stránku zobrazující výsledkovku ročníku v TeXu."""
model = Rocnik model = Rocnik
template_name = 'seminar/archiv/rocnik_vysledkovka.tex' template_name = 'tvorba/archiv/rocnik_vysledkovka.tex'
#content_type = 'application/x-tex; charset=UTF8' #content_type = 'application/x-tex; charset=UTF8'
#umozni rovnou stahnout TeXovsky dokument #umozni rovnou stahnout TeXovsky dokument
content_type = 'text/plain; charset=UTF8' content_type = 'text/plain; charset=UTF8'
@ -560,30 +481,8 @@ class RocnikVysledkovkaView(RocnikView):
def cisloObalkyView(request, rocnik, cislo): def cisloObalkyView(request, rocnik, cislo):
realne_cislo = get_object_or_404(Cislo, poradi=cislo, rocnik__rocnik=rocnik) realne_cislo = get_object_or_404(Cislo, poradi=cislo, rocnik__rocnik=rocnik)
return obalkyView(request, aktivniResitele(realne_cislo)) return personalni.views.obalkyView(request, utils.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 ### Tituly
@ -618,7 +517,7 @@ def TitulyView(request, rocnik, cislo):
else: else:
jmenovci = True jmenovci = True
return render(request, 'seminar/archiv/tituly.tex', return render(request, 'tvorba/archiv/tituly.tex',
{'resitele': resitele,'jmenovci':jmenovci},content_type="text/plain") {'resitele': resitele,'jmenovci':jmenovci},content_type="text/plain")
@ -649,7 +548,7 @@ def group_by_rocnik(clanky):
# FIXME: Původně tu byl kód přímo v těle třídy, což rozbíjelo migrace. Opravil jsem, ale vůbec nevím, jestli to funguje. # 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): class ClankyResitelView(generic.ListView):
model = Problem model = Problem
template_name = 'seminar/clanky/resitelske_clanky.html' template_name = 'tvorba/clanky/resitelske_clanky.html'
# FIXME: QuerySet není pole! # FIXME: QuerySet není pole!
def get_queryset(self): def get_queryset(self):
@ -665,51 +564,11 @@ class ClankyResitelView(generic.ListView):
# FIXME: pokud chceme orgoclanky, tak nejak zavest do modelu a podle toho odkomentovat a upravit # FIXME: pokud chceme orgoclanky, tak nejak zavest do modelu a podle toho odkomentovat a upravit
#class ClankyOrganizatorView(generic.ListView)<F12>: #class ClankyOrganizatorView(generic.ListView)<F12>:
# model = Problem # 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') # 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): class AktualniRocnikRedirectView(RedirectView):
permanent=False permanent=False

5
various/admin.py

@ -1 +1,6 @@
from solo.admin import SingletonModelAdmin
from django.contrib import admin from django.contrib import admin
from .models import Nastaveni
admin.site.register(Nastaveni, SingletonModelAdmin)

0
various/management/__init__.py

0
various/management/commands/__init__.py

0
seminar/management/commands/generate_thumbnails.py → various/management/commands/generate_thumbnails.py

0
seminar/management/commands/load_org_permissions.py → various/management/commands/load_org_permissions.py

0
seminar/management/commands/nukedb.py → various/management/commands/nukedb.py

0
seminar/management/commands/pregeneruj_zmrazene_vysledkovky.py → various/management/commands/pregeneruj_zmrazene_vysledkovky.py

0
seminar/management/commands/save_org_permissions.py → various/management/commands/save_org_permissions.py

4
seminar/management/commands/testdata.py → various/management/commands/testdata.py

@ -1,13 +1,11 @@
import datetime
import os import os
import random
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.core.management import call_command from django.core.management import call_command
from django.conf import settings from django.conf import settings
from seminar.models import Skola, Resitel, Rocnik, Cislo, Problem, Reseni, PrilohaReseni, Nastaveni 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 import django.contrib.auth
User = django.contrib.auth.get_user_model() User = django.contrib.auth.get_user_model()

0
seminar/templates/seminar/formular_ok.html → various/templates/various/formular_ok.html

16
various/templates/various/jakresit/jak-resit.html

@ -0,0 +1,16 @@
{% extends 'base.html' %}
{% load humanize %}
{% load static %}
{% block content %}
<div class=jakresit>
{% include 'various/jakresit/jakresit_1.svg' %}
{% include 'various/jakresit/jakresit_2.svg' %}
{% include 'various/jakresit/jakresit_3.svg' %}
</div>
{% endblock %}

0
seminar/templates/seminar/jakresit/jakresit_1.svg → various/templates/various/jakresit/jakresit_1.svg

Before

Width:  |  Height:  |  Size: 664 KiB

After

Width:  |  Height:  |  Size: 664 KiB

0
seminar/templates/seminar/jakresit/jakresit_2.svg → various/templates/various/jakresit/jakresit_2.svg

Before

Width:  |  Height:  |  Size: 689 KiB

After

Width:  |  Height:  |  Size: 689 KiB

0
seminar/templates/seminar/jakresit/jakresit_3.svg → various/templates/various/jakresit/jakresit_3.svg

Before

Width:  |  Height:  |  Size: 767 KiB

After

Width:  |  Height:  |  Size: 767 KiB

0
seminar/templates/seminar/pracuje_se.html → various/templates/various/pracuje_se.html

0
seminar/templates/seminar/stav_databaze.html → various/templates/various/stav_databaze.html

0
seminar/templates/seminar/titulnistrana/graph.svg → various/templates/various/titulnistrana/graph.svg

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

4
seminar/templates/seminar/titulnistrana/titulnistrana.html → various/templates/various/titulnistrana/titulnistrana.html

@ -79,7 +79,7 @@ function sousdeadline() {
<div class="TITULNI_STRANA_graf"> <div class="TITULNI_STRANA_graf">
<div class="TITULNI_STRANA_graf-svg"> <div class="TITULNI_STRANA_graf-svg">
{% include 'seminar/titulnistrana/graph.svg' %} <!-- TODO: aby to nemuselo být v templates --> {% include 'various/titulnistrana/graph.svg' %} <!-- TODO: aby to nemuselo být v templates -->
</div> </div>
<span class="TITULNI_STRANA_zjistit_vic"> <span class="TITULNI_STRANA_zjistit_vic">
@ -95,7 +95,7 @@ function sousdeadline() {
{# Novinky #} {# Novinky #}
<h1>Co je nového?</h1> <h1>Co je nového?</h1>
{% include 'seminar/novinky.html' %} {% include 'novinky/novinky.html' %}
<a href='/stare-novinky/'>Archiv novinek</a> <a href='/stare-novinky/'>Archiv novinek</a>

0
seminar/templatetags/tex.py → various/templatetags/tex.py

135
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="<p>V&iacute;tejte na str&aacute;nce semin&aacute;ře MaM!</p>",
)
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])

9
various/urls.py

@ -0,0 +1,9 @@
from django.urls import path
from .views.final import TitulniStranaView, JakResitView, StavDatabazeView
from personalni.utils import org_required
urlpatterns = [
path('', TitulniStranaView.as_view(), name='titulni_strana'),
path('jak-resit/', JakResitView.as_view(), name='jak_resit'),
path('stav', org_required(StavDatabazeView), name='stav_databaze'),
]

0
various/views/__init__.py

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save