Compare commits

...

91 commits

Author SHA1 Message Date
d67d2f372b Oprava zobrazování deadlinů v aktuálním zadání 2025-01-03 18:00:47 +01:00
65ec9bfaed Fix přednášek 2024-12-10 16:33:25 +01:00
7038de2e25 Merge pull request 'Dokumentace zkratek do zdrojáků' (!77) from doc_zkratky into master
Reviewed-on: #77
2024-12-03 22:33:16 +01:00
c43575d8d2 Merge pull request 'Vnořené rámečky mají být vidět' (!82) from nested_ramecky into master
Reviewed-on: #82
2024-12-03 22:32:35 +01:00
8d6b352545 Merge pull request 'Práva v data/* a načítané pomocí ./manage.py loaddata' (!80) from prava into master
Reviewed-on: #80
2024-12-03 22:32:08 +01:00
51eeffd0c5 Ještě právo řešitele a trochu jiné řazení 2024-12-03 22:23:53 +01:00
42d57e7b42 Přebývající dump práv 2024-12-03 22:19:40 +01:00
edde41e1ab Chybějící data/groups.json 2024-12-03 22:19:10 +01:00
Pavel "LEdoian" Turinsky
497bb054ee Vnitřní rámečky by měly jít vidět
Vnořený rámeček totiž značí, co dalšího ostatní neuvidí po zveřejnění
vnějšího rámečku.
2024-12-03 21:27:40 +01:00
e9451ed62e Merge pull request 'Řešitelský rámeček (aneb resitel-only; pro neveřejné věci řešitele)' (!79) from ucastnicky-ramecek into master
Reviewed-on: #79
2024-12-03 21:22:30 +01:00
MaM Web user
e87b84b028 Fix rámečku u soustředění, kde jsem nebyl účastník (Jidáš) 2024-12-03 20:59:03 +01:00
9020f5551d Oprava testdat KorekturovanePDF 2024-11-26 23:12:43 +01:00
ca462289a9 Zúžení except klauzule 2024-11-26 23:10:08 +01:00
80b20f5290 Práva v data/* a načítané pomocí ./manage.py loaddata 2024-11-26 20:05:49 +01:00
dbe8c39b37 Merge pull request 'Korektury Link Z Admina' (!72) from korektury-link-z-admina into master
Reviewed-on: #72
2024-11-26 19:37:18 +01:00
0aee5b9bdb Řešitelský rámeček (aneb resitel-only; pro neveřejné věci řešitele) 2024-11-26 19:33:44 +01:00
aee7637fcf Oprava křížku pro mazání hodnocení 2024-11-26 19:03:06 +01:00
5bf8df0218 Merge pull request 'Upgrade CKEditoru na verzi 5' (!76) from ckeditor5 into master
Reviewed-on: #76
2024-11-26 18:36:07 +01:00
5853b243dd Komentář 2024-11-26 18:34:54 +01:00
Pavel "LEdoian" Turinsky
5f87045b31 Ještě jedna aktualizace :-) 2024-11-19 22:39:18 +01:00
Pavel "LEdoian" Turinsky
36b261580c Aktualizace toho, jak se používá Sphinx u nás 2024-11-19 22:38:46 +01:00
Pavel "LEdoian" Turinsky
3920ac72aa Sepsané zkratky pro zdrojáky 2024-11-19 22:38:34 +01:00
dc91ef571f Upgrade CKEditoru na verzi 5 2024-11-19 22:05:12 +01:00
f0fbd8021f Merge pull request 'Přidání CKEditoru do Soustředění a Novinek (#1294)' (!75) from ckeditor-dalsi-veci into master
Reviewed-on: #75
2024-11-19 20:31:18 +01:00
3d035b994a Přidání CKEditoru do Soustředění a Novinek 2024-11-19 20:22:12 +01:00
db3dd39696 Merge pull request 'Možnost mít deadline celého čísla a sousu zároveň' (!74) from deadline-cisla-a-sousu into master
Reviewed-on: #74
2024-11-13 12:16:21 +01:00
e0e1dfda41 Merge remote-tracking branch 'origin/master' into deadline-cisla-a-sousu
# Conflicts:
#	tvorba/models.py
2024-11-13 12:14:33 +01:00
MaM Web user
44766efe2a Ještě migrace
— LEdo
2024-11-12 21:47:10 +01:00
d129f8a764 Merge remote-tracking branch 'refs/remotes/origin/master' 2024-11-12 21:45:59 +01:00
5b330567f8 generating file names with three functions 2024-11-12 21:44:17 +01:00
e6a21a5f1b Komentář, abychom kdyžtak uměli duplikovat přidání deadlinu 2024-11-12 21:41:32 +01:00
042246e948 dalsí do masteru zobrazovani kontaktnicku 2024-11-12 21:39:17 +01:00
e224ee66a7 push to master quick fix zobratzeni pro orrrgy 2024-11-12 21:23:13 +01:00
5ccb94d155 Merge pull request 'verejny kontaktnicek' (!71) from kontaktnicek_pro_vsecny into master
Reviewed-on: #71
2024-11-12 21:15:24 +01:00
fa00652a69 Merge branch 'master' into kontaktnicek_pro_vsecny 2024-11-12 21:14:10 +01:00
036c68bc2a another lambad function 2024-11-12 21:07:13 +01:00
fcc2c7c374 lol komentar 2024-11-12 20:37:50 +01:00
1b755ad1f7 deduplikace listu 2024-11-12 20:37:14 +01:00
ad5a242f8d upravy generate filename 2024-11-12 20:31:08 +01:00
6186914a7c aaaaa another bug 2024-11-12 20:27:51 +01:00
7a781e463f lol docker tu nemel byt 2024-11-12 20:20:43 +01:00
2dbbb588d0 opravy pro pull 2024-11-12 20:15:09 +01:00
eada7920f0 Možnost mít deadline celého čísla a sousu zároveň 2024-11-12 20:10:19 +01:00
6a7e4b1a39 Možnost mít deadline celého čísla a sousu zároveň 2024-11-12 19:58:14 +01:00
5883a5cd28 Merge pull request 'Předělání sousových views' (!57) from predelani_sousovych_view into master
Reviewed-on: #57
2024-11-12 19:33:10 +01:00
7e8092c30c tex -> tex_prikaz 2024-11-12 19:32:19 +01:00
f01a808ac2 Komentář 2024-11-12 19:29:17 +01:00
c8dd54e8ba opravy bad designu 2024-11-12 18:37:21 +01:00
Pavel 'LEdoian' Turinsky
f2825a97cf Přidání odkazu z Admina do korekturovátka 2024-11-11 07:46:34 +01:00
0f6f6a85b6 verejny kontaktnicek 2024-11-05 22:47:18 +01:00
502588fd3a Merge pull request 'soucet_bodu' (!70) from soucet_bodu into master
Reviewed-on: #70
2024-11-05 22:27:54 +01:00
b4b6c7c0ce seminar.models se načítá automaticky, budiž to mamweb.vsechno 2024-11-05 22:25:40 +01:00
854c902322 Merge branch 'master' into predelani_sousovych_view 2024-11-05 20:36:43 +01:00
024f8e0a80 Merge pull request 'Podezřelé semináře (#1465)' (!65) from podezrele-seminare into master
Reviewed-on: #65
2024-11-05 20:31:54 +01:00
bf726df117 Přepsání loggeru, protože to vypadá lépe (i kdyby to nefungovalo) 2024-11-05 20:30:55 +01:00
227d438ebd Poslední obrana proti importování seminar.models 2024-11-05 20:22:04 +01:00
71948383d2 Upraven popis aplikace seminar 2024-11-05 20:19:34 +01:00
6df9665af3 Migrace utišující Django (stejně se default nikdy nepoužívá). 2024-11-05 20:15:41 +01:00
c1938c8ff7 Odstřel funkcí pro migrace 2024-11-05 19:56:07 +01:00
4536c56a83 Merge pull request '+5 let limit maturity' (!69) from vek_maturity into master
Reviewed-on: #69
2024-11-05 19:53:43 +01:00
f5c2e22121 Zapomenutý seminar.models 2024-11-05 18:54:01 +01:00
62a42675f8 mv SeminarModelBase a OverwriteStorage 2024-11-05 18:53:38 +01:00
a372aa1bc2 Seminar v dokumentaci 2024-11-05 17:46:27 +01:00
d0cb46b658 Název url seznamu všech soustředění 2024-11-05 17:40:36 +01:00
ac6c41cc88 Název url seznamu všech soustředění 2024-11-05 15:10:36 +01:00
1c146ac5c3 Admin ReseniNode 2024-11-05 15:07:51 +01:00
527a7b3ea6 Pojmenované URL místo čistého URL 2024-11-05 14:58:25 +01:00
e4a72940e8 Merge remote-tracking branch 'origin/master' into podezrele-seminare 2024-11-05 14:46:50 +01:00
c0825691a6 Merge branch 'master' into podezrele-seminare 2024-11-05 14:29:02 +01:00
Pavel "LEdoian" Turinsky
9b14e4a333 Treenode: žádné pomocné treenody nebudou. 2024-11-03 01:46:18 +01:00
Pavel "LEdoian" Turinsky
8283b530e9 Merge remote-tracking branch 'gitea/podezrele-seminare' into bez_treenodu_i_bez_podezrelych_seminaru
Zatím tak, jak se namergeovalo, změny k funkčnosti v dalších commitech
(ať neděláme zbytečně magické merge)
2024-11-03 01:42:56 +01:00
007332804e Fix: Ať to alespoň nehází chybu. 2024-11-01 16:40:21 +01:00
7f21d10c26 Ha, tak jsem se někde zamotal do import cyklu a musel jsem ReseniNode dát tam, kam patří 2024-11-01 14:02:07 +01:00
dc0ff80632 Přehlídnutý import seminar.models 2024-11-01 13:51:43 +01:00
07d1505e2a Odstřel (importů) treenodů 2024-11-01 13:50:48 +01:00
9ca5967261 Přesun vue (zatím) do treenodů 2024-11-01 13:20:28 +01:00
eb6eb2d6fb Přejmenování loggerů 2024-11-01 13:17:39 +01:00
28fef9a393 Pojmenované URL místo relativních URL 2024-11-01 12:58:02 +01:00
f8b1f0978c Přejmenování URLs 2024-11-01 12:38:47 +01:00
5f931c49a5 Merge branch 'master' into podezrele-seminare 2024-11-01 12:10:07 +01:00
8fd582d194 Další částečné řešení #1465 (Podezřelé seminare). Záměrně se vyhýbá treenode. 2024-11-01 11:44:17 +01:00
27a16719be Merge remote-tracking branch 'origin/master' into podezrele-seminare 2024-10-31 10:56:48 +01:00
5db14ea242 Částečné řešení #1465 (Podezřelé seminare) 2024-10-30 15:03:17 +01:00
11eb3c3665 Pohrobek splitu semináře (předchozího merge) 2024-10-24 11:51:28 +02:00
446515a52e Merge branch 'master' into predelani_sousovych_view
# Conflicts:
#	soustredeni/views.py
#	various/views/generic.py
2024-10-24 11:44:04 +02:00
d8d37adc1f Merge branch 'refs/heads/split_sous' into predelani_sousovych_view 2024-08-06 02:41:19 +02:00
1b01fe54d2 Merge branch 'refs/heads/master' into predelani_sousovych_view 2024-08-06 02:40:04 +02:00
be8c9810e4 Rozdělení varous.views, aby odpovídali 5f7ec853 2024-08-04 17:51:26 +02:00
47894ce335 Přesun csrf_error 2024-08-04 17:43:29 +02:00
d55199d6ae Achich ouvej, on je to Soustredeni_Ucastnici model 2024-08-02 23:44:37 +02:00
05a710185c Předělání sousových views do hodně inheritance stavu
Vím, že je toho tady trochu moc najednou, ale napadalo mě to tak propleteně…
2024-08-02 23:36:12 +02:00
130 changed files with 1925 additions and 1433 deletions

View file

@ -5,16 +5,16 @@ urlpatterns = [
path(
'aesop-export/mam-rocnik-<int:prvni_rok>.csv',
views.ExportRocnikView.as_view(),
name='seminar_export_rocnik'
name='aesop_export_rocnik'
),
path(
'aesop-export/mam-sous-<str:datum_zacatku>.csv',
views.ExportSousView.as_view(),
name='seminar_export_sous'
name='aesop_export_sous'
),
path(
'aesop-export/index.csv',
views.ExportIndexView.as_view(),
name='seminar_export_index'
name='aesop_export_index'
),
]

View file

@ -6,7 +6,8 @@ from django.views import generic
from django.utils.encoding import force_str
from .utils import default_ovvpfile
from seminar.models import Rocnik, Soustredeni
from soustredeni.models import Soustredeni
from tvorba.models import Rocnik
from vysledkovky import utils
from tvorba.utils import aktivniResitele
@ -14,10 +15,10 @@ class ExportIndexView(generic.View):
def get(self, request):
ls = []
for r in Rocnik.objects.filter(exportovat = True):
url = reverse('seminar_export_rocnik', kwargs={'prvni_rok': r.prvni_rok})
url = reverse('aesop_export_rocnik', kwargs={'prvni_rok': r.prvni_rok})
ls.append(url.split('/')[-1])
for s in Soustredeni.objects.filter(exportovat = True):
url = reverse('seminar_export_sous', kwargs={'datum_zacatku': s.datum_zacatku.isoformat()})
url = reverse('aesop_export_sous', kwargs={'datum_zacatku': s.datum_zacatku.isoformat()})
ls.append(url.split('/')[-1])
return HttpResponse('\n'.join(ls) + '\n', content_type='text/plain; charset=utf-8')

View file

@ -1,6 +1,6 @@
from django.test import TestCase, tag
from django.urls import reverse
import seminar.models as m
from personalni.models import Skola
from personalni.utils import sync_skoly
@tag('stejny-model-na-produkci')
@ -48,7 +48,7 @@ class OrgSkolyAutocompleteTestCase(TestCase):
"""Testuje, že pro každého orga je jeho škola ve výsledném QuerySetu"""
for pfx, id in self.spravna_data:
with self.subTest(prefix=pfx, spravne_id=id):
spravna_skola = m.Skola.objects.get(id=id)
spravna_skola = Skola.objects.get(id=id)
# Zeptáme se view, co si myslí
resp = self.client.get(reverse('autocomplete_skola')+'?q='+pfx).json()
ids = [int(x['id']) for x in resp['results']]

View file

@ -17,5 +17,5 @@ urlpatterns = [
# Ceka na autocomplete v3
# path('autocomplete/organizatori/',
# org_member_required(views.OrganizatorAutocomplete.as_view()),
# name='seminar_autocomplete_organizator')
# name='autocomplete_organizator')
]

View file

@ -5,7 +5,9 @@ from dal import autocomplete
from django.shortcuts import get_object_or_404
from django.db.models import Q
import seminar.models as m
from personalni.models import Skola, Resitel
from tvorba.models import Problem
from various.models import Nastaveni
from .helpers import LoginRequiredAjaxMixin
# TODO filosofie - zkratky, jak v databázi, tak ve vyhledávání (SPŠE, GASOŠ, Kpt., soukr)
@ -13,7 +15,7 @@ class SkolaAutocomplete(autocomplete.Select2QuerySetView):
""" View k :mod:`dal.autocomplete` pro vyhledávání škol hlavně při registraci. """
def get_queryset(self):
# Don't forget to filter out results depending on the visitor !
qs = m.Skola.objects.all()
qs = Skola.objects.all()
if self.q:
words = self.q.split(' ') #TODO re split podle bileho znaku
partq = Q()
@ -31,7 +33,7 @@ class SkolaAutocomplete(autocomplete.Select2QuerySetView):
class ResitelAutocomplete(LoginRequiredAjaxMixin,autocomplete.Select2QuerySetView):
""" View k :mod:`dal.autocomplete` pro vyhledávání řešitelů především v odevzdávátku. """
def get_queryset(self):
qs = m.Resitel.objects.all()
qs = Resitel.objects.all()
if self.q:
parts = self.q.split()
query = Q()
@ -51,8 +53,8 @@ class PublicResitelAutocomplete(LoginRequiredAjaxMixin, autocomplete.Select2Quer
především v odevzdávátku.
"""
def get_queryset(self):
letos = m.Nastaveni.get_solo().aktualni_rocnik
qs = m.Resitel.objects.filter(
letos = Nastaveni.get_solo().aktualni_rocnik
qs = Resitel.objects.filter(
rok_maturity__gte=letos.druhy_rok()
).filter(
prezdivka_resitele__isnull=False
@ -70,7 +72,7 @@ class PublicResitelAutocomplete(LoginRequiredAjaxMixin, autocomplete.Select2Quer
class OdevzdatelnyProblemAutocomplete(autocomplete.Select2QuerySetView):
""" View k :mod:`dal.autocomplete` pro vyhledávání problémů především v odevzdávátku. """
def get_queryset(self):
qs = m.Problem.objects.filter(stav=m.Problem.STAV_ZADANY)
qs = Problem.objects.filter(stav=Problem.STAV_ZADANY)
if self.q:
qs = qs.filter(
Q(nazev__icontains=self.q))
@ -87,12 +89,12 @@ class ProblemAutocomplete(autocomplete.Select2QuerySetView):
""" View k :mod:`dal.autocomplete` pro vyhledávání problémů především v odevzdávátku. """
def get_queryset(self):
# FIXME i starší úlohy
nastaveni = get_object_or_404(m.Nastaveni)
nastaveni = get_object_or_404(Nastaveni)
rocnik = nastaveni.aktualni_rocnik
temaQ = Q(Tema___rocnik = rocnik)
ulohaQ = Q(Uloha___cislo_zadani__rocnik=rocnik)
clanekQ = Q(Clanek___cislo__rocnik=rocnik)
qs = m.Problem.objects.filter(temaQ | ulohaQ | clanekQ).order_by("-stav", "nazev")
qs = Problem.objects.filter(temaQ | ulohaQ | clanekQ).order_by("-stav", "nazev")
if self.q:
qs = qs.filter(
Q(nazev__icontains=self.q))

View file

@ -1,4 +1,4 @@
import seminar.models as m
import personalni.models as m
from django.core import serializers as ser
from django.http import HttpResponse
def exportSkolView(request):

645
data/groups.json Normal file
View file

@ -0,0 +1,645 @@
[
{
"fields": {
"name": "org",
"permissions": [
[
"org",
"auth",
"user"
],
[
"add_flatpage",
"flatpages",
"flatpage"
],
[
"delete_flatpage",
"flatpages",
"flatpage"
],
[
"change_flatpage",
"flatpages",
"flatpage"
],
[
"view_flatpage",
"flatpages",
"flatpage"
],
[
"add_galerie",
"galerie",
"galerie"
],
[
"delete_galerie",
"galerie",
"galerie"
],
[
"change_galerie",
"galerie",
"galerie"
],
[
"view_galerie",
"galerie",
"galerie"
],
[
"add_obrazek",
"galerie",
"obrazek"
],
[
"delete_obrazek",
"galerie",
"obrazek"
],
[
"change_obrazek",
"galerie",
"obrazek"
],
[
"view_obrazek",
"galerie",
"obrazek"
],
[
"add_fotkaheader",
"header_fotky",
"fotkaheader"
],
[
"change_fotkaheader",
"header_fotky",
"fotkaheader"
],
[
"view_fotkaheader",
"header_fotky",
"fotkaheader"
],
[
"add_fotkaurlvazba",
"header_fotky",
"fotkaurlvazba"
],
[
"change_fotkaurlvazba",
"header_fotky",
"fotkaurlvazba"
],
[
"view_fotkaurlvazba",
"header_fotky",
"fotkaurlvazba"
],
[
"add_komentar",
"korektury",
"komentar"
],
[
"delete_komentar",
"korektury",
"komentar"
],
[
"change_komentar",
"korektury",
"komentar"
],
[
"view_komentar",
"korektury",
"komentar"
],
[
"add_korekturovanepdf",
"korektury",
"korekturovanepdf"
],
[
"delete_korekturovanepdf",
"korektury",
"korekturovanepdf"
],
[
"change_korekturovanepdf",
"korektury",
"korekturovanepdf"
],
[
"view_korekturovanepdf",
"korektury",
"korekturovanepdf"
],
[
"add_oprava",
"korektury",
"oprava"
],
[
"delete_oprava",
"korektury",
"oprava"
],
[
"change_oprava",
"korektury",
"oprava"
],
[
"view_oprava",
"korektury",
"oprava"
],
[
"add_novinky",
"novinky",
"novinky"
],
[
"delete_novinky",
"novinky",
"novinky"
],
[
"change_novinky",
"novinky",
"novinky"
],
[
"view_novinky",
"novinky",
"novinky"
],
[
"change_organizator",
"personalni",
"organizator"
],
[
"view_organizator",
"personalni",
"organizator"
],
[
"change_osoba",
"personalni",
"osoba"
],
[
"view_osoba",
"personalni",
"osoba"
],
[
"add_prijemce",
"personalni",
"prijemce"
],
[
"delete_prijemce",
"personalni",
"prijemce"
],
[
"change_prijemce",
"personalni",
"prijemce"
],
[
"view_prijemce",
"personalni",
"prijemce"
],
[
"change_resitel",
"personalni",
"resitel"
],
[
"view_resitel",
"personalni",
"resitel"
],
[
"add_skola",
"personalni",
"skola"
],
[
"delete_skola",
"personalni",
"skola"
],
[
"change_skola",
"personalni",
"skola"
],
[
"view_skola",
"personalni",
"skola"
],
[
"add_hlasovani",
"prednasky",
"hlasovani"
],
[
"delete_hlasovani",
"prednasky",
"hlasovani"
],
[
"change_hlasovani",
"prednasky",
"hlasovani"
],
[
"view_hlasovani",
"prednasky",
"hlasovani"
],
[
"add_prednaska",
"prednasky",
"prednaska"
],
[
"delete_prednaska",
"prednasky",
"prednaska"
],
[
"change_prednaska",
"prednasky",
"prednaska"
],
[
"view_prednaska",
"prednasky",
"prednaska"
],
[
"add_seznam",
"prednasky",
"seznam"
],
[
"delete_seznam",
"prednasky",
"seznam"
],
[
"change_seznam",
"prednasky",
"seznam"
],
[
"view_seznam",
"prednasky",
"seznam"
],
[
"add_konfera",
"soustredeni",
"konfera"
],
[
"delete_konfera",
"soustredeni",
"konfera"
],
[
"change_konfera",
"soustredeni",
"konfera"
],
[
"view_konfera",
"soustredeni",
"konfera"
],
[
"add_konfery_ucastnici",
"soustredeni",
"konfery_ucastnici"
],
[
"delete_konfery_ucastnici",
"soustredeni",
"konfery_ucastnici"
],
[
"change_konfery_ucastnici",
"soustredeni",
"konfery_ucastnici"
],
[
"view_konfery_ucastnici",
"soustredeni",
"konfery_ucastnici"
],
[
"add_soustredeni",
"soustredeni",
"soustredeni"
],
[
"delete_soustredeni",
"soustredeni",
"soustredeni"
],
[
"change_soustredeni",
"soustredeni",
"soustredeni"
],
[
"view_soustredeni",
"soustredeni",
"soustredeni"
],
[
"add_soustredeni_organizatori",
"soustredeni",
"soustredeni_organizatori"
],
[
"delete_soustredeni_organizatori",
"soustredeni",
"soustredeni_organizatori"
],
[
"change_soustredeni_organizatori",
"soustredeni",
"soustredeni_organizatori"
],
[
"view_soustredeni_organizatori",
"soustredeni",
"soustredeni_organizatori"
],
[
"add_soustredeni_ucastnici",
"soustredeni",
"soustredeni_ucastnici"
],
[
"delete_soustredeni_ucastnici",
"soustredeni",
"soustredeni_ucastnici"
],
[
"change_soustredeni_ucastnici",
"soustredeni",
"soustredeni_ucastnici"
],
[
"view_soustredeni_ucastnici",
"soustredeni",
"soustredeni_ucastnici"
],
[
"add_tag",
"taggit",
"tag"
],
[
"delete_tag",
"taggit",
"tag"
],
[
"change_tag",
"taggit",
"tag"
],
[
"view_tag",
"taggit",
"tag"
],
[
"add_taggeditem",
"taggit",
"taggeditem"
],
[
"delete_taggeditem",
"taggit",
"taggeditem"
],
[
"change_taggeditem",
"taggit",
"taggeditem"
],
[
"view_taggeditem",
"taggit",
"taggeditem"
],
[
"add_cislo",
"tvorba",
"cislo"
],
[
"delete_cislo",
"tvorba",
"cislo"
],
[
"change_cislo",
"tvorba",
"cislo"
],
[
"view_cislo",
"tvorba",
"cislo"
],
[
"add_clanek",
"tvorba",
"clanek"
],
[
"delete_clanek",
"tvorba",
"clanek"
],
[
"change_clanek",
"tvorba",
"clanek"
],
[
"view_clanek",
"tvorba",
"clanek"
],
[
"add_deadline",
"tvorba",
"deadline"
],
[
"change_deadline",
"tvorba",
"deadline"
],
[
"view_deadline",
"tvorba",
"deadline"
],
[
"add_pohadka",
"tvorba",
"pohadka"
],
[
"delete_pohadka",
"tvorba",
"pohadka"
],
[
"change_pohadka",
"tvorba",
"pohadka"
],
[
"view_pohadka",
"tvorba",
"pohadka"
],
[
"add_problem",
"tvorba",
"problem"
],
[
"delete_problem",
"tvorba",
"problem"
],
[
"change_problem",
"tvorba",
"problem"
],
[
"view_problem",
"tvorba",
"problem"
],
[
"add_rocnik",
"tvorba",
"rocnik"
],
[
"delete_rocnik",
"tvorba",
"rocnik"
],
[
"change_rocnik",
"tvorba",
"rocnik"
],
[
"view_rocnik",
"tvorba",
"rocnik"
],
[
"add_tema",
"tvorba",
"tema"
],
[
"delete_tema",
"tvorba",
"tema"
],
[
"change_tema",
"tvorba",
"tema"
],
[
"view_tema",
"tvorba",
"tema"
],
[
"add_uloha",
"tvorba",
"uloha"
],
[
"delete_uloha",
"tvorba",
"uloha"
],
[
"change_uloha",
"tvorba",
"uloha"
],
[
"view_uloha",
"tvorba",
"uloha"
],
[
"add_nastaveni",
"various",
"nastaveni"
],
[
"delete_nastaveni",
"various",
"nastaveni"
],
[
"change_nastaveni",
"various",
"nastaveni"
],
[
"view_nastaveni",
"various",
"nastaveni"
]
]
},
"model": "auth.group",
"pk": 1
},
{
"fields": {
"name": "resitel",
"permissions": [
[
"resitel",
"auth",
"user"
]
]
},
"model": "auth.group",
"pk": 2
}
]

View file

@ -73,7 +73,7 @@
"sort_order": 3,
"title": "Aktuální<br/> ročník",
"tree": 1,
"url": "seminar_aktualni_zadani",
"url": "tvorba_aktualni_zadani",
"urlaspattern": true
},
"model": "sitetree.treeitem",
@ -121,7 +121,7 @@
"sort_order": 5,
"title": "Archiv",
"tree": 1,
"url": "seminar_archiv_rocniky",
"url": "tvorba_archiv_rocniky",
"urlaspattern": true
},
"model": "sitetree.treeitem",
@ -289,7 +289,7 @@
"sort_order": 43,
"title": "Výsledková listina",
"tree": 1,
"url": "seminar_aktualni_vysledky",
"url": "tvorba_aktualni_vysledky",
"urlaspattern": true
},
"model": "sitetree.treeitem",
@ -361,7 +361,7 @@
"sort_order": 20,
"title": "Proběhlo",
"tree": 1,
"url": "seminar_seznam_soustredeni",
"url": "soustredeni_seznam",
"urlaspattern": true
},
"model": "sitetree.treeitem",
@ -409,7 +409,7 @@
"sort_order": 23,
"title": "Osobní údaje",
"tree": 1,
"url": "seminar_resitel_edit",
"url": "personalni_resitel_edit",
"urlaspattern": true
},
"model": "sitetree.treeitem",
@ -439,7 +439,7 @@
"sort_order": 36,
"title": "Nahrát řešení",
"tree": 1,
"url": "seminar_nahraj_reseni",
"url": "odevzdavatko_nahraj_reseni",
"urlaspattern": true
},
"model": "sitetree.treeitem",
@ -463,7 +463,7 @@
"sort_order": 35,
"title": "Témata",
"tree": 1,
"url": "seminar_archiv_temata",
"url": "tvorba_archiv_temata",
"urlaspattern": true
},
"model": "sitetree.treeitem",
@ -589,7 +589,7 @@
"sort_order": 15,
"title": "Aktuální číslo",
"tree": 1,
"url": "seminar_aktualni_zadani",
"url": "tvorba_aktualni_zadani",
"urlaspattern": true
},
"model": "sitetree.treeitem",
@ -613,7 +613,7 @@
"sort_order": 24,
"title": "Čísla",
"tree": 1,
"url": "seminar_archiv_rocniky",
"url": "tvorba_archiv_rocniky",
"urlaspattern": true
},
"model": "sitetree.treeitem",
@ -721,7 +721,7 @@
"sort_order": 36,
"title": "Vložit řešení",
"tree": 1,
"url": "seminar_vloz_reseni",
"url": "odevzdavatko_vloz_reseni",
"urlaspattern": true
},
"model": "sitetree.treeitem",
@ -804,7 +804,7 @@
"sort_order": 37,
"title": "Moje řešení",
"tree": 1,
"url": "seminar_resitel_odevzdana_reseni",
"url": "odevzdavatko_resitel_odevzdana_reseni",
"urlaspattern": true
},
"model": "sitetree.treeitem",
@ -828,7 +828,7 @@
"sort_order": 33,
"title": "Aktuální ročník",
"tree": 1,
"url": "seminar_aktualni_rocnik",
"url": "tvorba_aktualni_rocnik",
"urlaspattern": true
},
"model": "sitetree.treeitem",
@ -900,7 +900,7 @@
"sort_order": 46,
"title": "Ročník {{rocnik.rocnik}}",
"tree": 1,
"url": "seminar_rocnik rocnik.rocnik",
"url": "tvorba_rocnik rocnik.rocnik",
"urlaspattern": true
},
"model": "sitetree.treeitem",
@ -924,7 +924,7 @@
"sort_order": 47,
"title": "Číslo {{ cislo.rocnik.rocnik }}.{{ cislo.poradi }}",
"tree": 1,
"url": "seminar_cislo cislo.rocnik.rocnik cislo.poradi",
"url": "tvorba_cislo cislo.rocnik.rocnik cislo.poradi",
"urlaspattern": true
},
"model": "sitetree.treeitem",
@ -1061,7 +1061,7 @@
"sort_order": 52,
"title": "Nahrát řešení k nadproblému {{nadproblem_id}}",
"tree": 1,
"url": "seminar_nahraj_reseni nadproblem_id",
"url": "odevzdavatko_nahraj_reseni nadproblem_id",
"urlaspattern": true
},
"model": "sitetree.treeitem",
@ -1109,10 +1109,10 @@
"sort_order": 54,
"title": "Export do abstraktů sousu {{ soustredeni.id }}",
"tree": 1,
"url": "seminar_soustredeni_abstrakty soustredeni.id",
"url": "soustredeni_abstrakty soustredeni.id",
"urlaspattern": true
},
"model": "sitetree.treeitem",
"pk": 54
}
]
]

View file

@ -1,622 +0,0 @@
[
{
"codename": "org",
"ct_app_label": "auth",
"ct_model": "user"
},
{
"codename": "add_flatpage",
"ct_app_label": "flatpages",
"ct_model": "flatpage"
},
{
"codename": "change_flatpage",
"ct_app_label": "flatpages",
"ct_model": "flatpage"
},
{
"codename": "delete_flatpage",
"ct_app_label": "flatpages",
"ct_model": "flatpage"
},
{
"codename": "view_flatpage",
"ct_app_label": "flatpages",
"ct_model": "flatpage"
},
{
"codename": "add_galerie",
"ct_app_label": "galerie",
"ct_model": "galerie"
},
{
"codename": "change_galerie",
"ct_app_label": "galerie",
"ct_model": "galerie"
},
{
"codename": "delete_galerie",
"ct_app_label": "galerie",
"ct_model": "galerie"
},
{
"codename": "view_galerie",
"ct_app_label": "galerie",
"ct_model": "galerie"
},
{
"codename": "add_obrazek",
"ct_app_label": "galerie",
"ct_model": "obrazek"
},
{
"codename": "change_obrazek",
"ct_app_label": "galerie",
"ct_model": "obrazek"
},
{
"codename": "delete_obrazek",
"ct_app_label": "galerie",
"ct_model": "obrazek"
},
{
"codename": "view_obrazek",
"ct_app_label": "galerie",
"ct_model": "obrazek"
},
{
"codename": "add_fotkaheader",
"ct_app_label": "header_fotky",
"ct_model": "fotkaheader"
},
{
"codename": "change_fotkaheader",
"ct_app_label": "header_fotky",
"ct_model": "fotkaheader"
},
{
"codename": "view_fotkaheader",
"ct_app_label": "header_fotky",
"ct_model": "fotkaheader"
},
{
"codename": "add_fotkaurlvazba",
"ct_app_label": "header_fotky",
"ct_model": "fotkaurlvazba"
},
{
"codename": "change_fotkaurlvazba",
"ct_app_label": "header_fotky",
"ct_model": "fotkaurlvazba"
},
{
"codename": "view_fotkaurlvazba",
"ct_app_label": "header_fotky",
"ct_model": "fotkaurlvazba"
},
{
"codename": "add_komentar",
"ct_app_label": "korektury",
"ct_model": "komentar"
},
{
"codename": "change_komentar",
"ct_app_label": "korektury",
"ct_model": "komentar"
},
{
"codename": "delete_komentar",
"ct_app_label": "korektury",
"ct_model": "komentar"
},
{
"codename": "view_komentar",
"ct_app_label": "korektury",
"ct_model": "komentar"
},
{
"codename": "add_korekturovanepdf",
"ct_app_label": "korektury",
"ct_model": "korekturovanepdf"
},
{
"codename": "change_korekturovanepdf",
"ct_app_label": "korektury",
"ct_model": "korekturovanepdf"
},
{
"codename": "delete_korekturovanepdf",
"ct_app_label": "korektury",
"ct_model": "korekturovanepdf"
},
{
"codename": "view_korekturovanepdf",
"ct_app_label": "korektury",
"ct_model": "korekturovanepdf"
},
{
"codename": "add_oprava",
"ct_app_label": "korektury",
"ct_model": "oprava"
},
{
"codename": "change_oprava",
"ct_app_label": "korektury",
"ct_model": "oprava"
},
{
"codename": "delete_oprava",
"ct_app_label": "korektury",
"ct_model": "oprava"
},
{
"codename": "view_oprava",
"ct_app_label": "korektury",
"ct_model": "oprava"
},
{
"codename": "add_hlasovani",
"ct_app_label": "prednasky",
"ct_model": "hlasovani"
},
{
"codename": "change_hlasovani",
"ct_app_label": "prednasky",
"ct_model": "hlasovani"
},
{
"codename": "delete_hlasovani",
"ct_app_label": "prednasky",
"ct_model": "hlasovani"
},
{
"codename": "view_hlasovani",
"ct_app_label": "prednasky",
"ct_model": "hlasovani"
},
{
"codename": "add_prednaska",
"ct_app_label": "prednasky",
"ct_model": "prednaska"
},
{
"codename": "change_prednaska",
"ct_app_label": "prednasky",
"ct_model": "prednaska"
},
{
"codename": "delete_prednaska",
"ct_app_label": "prednasky",
"ct_model": "prednaska"
},
{
"codename": "view_prednaska",
"ct_app_label": "prednasky",
"ct_model": "prednaska"
},
{
"codename": "add_seznam",
"ct_app_label": "prednasky",
"ct_model": "seznam"
},
{
"codename": "change_seznam",
"ct_app_label": "prednasky",
"ct_model": "seznam"
},
{
"codename": "delete_seznam",
"ct_app_label": "prednasky",
"ct_model": "seznam"
},
{
"codename": "view_seznam",
"ct_app_label": "prednasky",
"ct_model": "seznam"
},
{
"codename": "add_cislo",
"ct_app_label": "tvorba",
"ct_model": "cislo"
},
{
"codename": "change_cislo",
"ct_app_label": "tvorba",
"ct_model": "cislo"
},
{
"codename": "delete_cislo",
"ct_app_label": "tvorba",
"ct_model": "cislo"
},
{
"codename": "view_cislo",
"ct_app_label": "tvorba",
"ct_model": "cislo"
},
{
"codename": "add_clanek",
"ct_app_label": "tvorba",
"ct_model": "clanek"
},
{
"codename": "change_clanek",
"ct_app_label": "tvorba",
"ct_model": "clanek"
},
{
"codename": "delete_clanek",
"ct_app_label": "tvorba",
"ct_model": "clanek"
},
{
"codename": "view_clanek",
"ct_app_label": "tvorba",
"ct_model": "clanek"
},
{
"codename": "add_deadline",
"ct_app_label": "tvorba",
"ct_model": "deadline"
},
{
"codename": "change_deadline",
"ct_app_label": "tvorba",
"ct_model": "deadline"
},
{
"codename": "view_deadline",
"ct_app_label": "tvorba",
"ct_model": "deadline"
},
{
"codename": "add_konfera",
"ct_app_label": "soustredeni",
"ct_model": "konfera"
},
{
"codename": "change_konfera",
"ct_app_label": "soustredeni",
"ct_model": "konfera"
},
{
"codename": "delete_konfera",
"ct_app_label": "soustredeni",
"ct_model": "konfera"
},
{
"codename": "view_konfera",
"ct_app_label": "soustredeni",
"ct_model": "konfera"
},
{
"codename": "add_konfery_ucastnici",
"ct_app_label": "soustredeni",
"ct_model": "konfery_ucastnici"
},
{
"codename": "change_konfery_ucastnici",
"ct_app_label": "soustredeni",
"ct_model": "konfery_ucastnici"
},
{
"codename": "delete_konfery_ucastnici",
"ct_app_label": "soustredeni",
"ct_model": "konfery_ucastnici"
},
{
"codename": "view_konfery_ucastnici",
"ct_app_label": "soustredeni",
"ct_model": "konfery_ucastnici"
},
{
"codename": "add_nastaveni",
"ct_app_label": "various",
"ct_model": "nastaveni"
},
{
"codename": "change_nastaveni",
"ct_app_label": "various",
"ct_model": "nastaveni"
},
{
"codename": "delete_nastaveni",
"ct_app_label": "various",
"ct_model": "nastaveni"
},
{
"codename": "view_nastaveni",
"ct_app_label": "various",
"ct_model": "nastaveni"
},
{
"codename": "add_novinky",
"ct_app_label": "novinky",
"ct_model": "novinky"
},
{
"codename": "change_novinky",
"ct_app_label": "novinky",
"ct_model": "novinky"
},
{
"codename": "delete_novinky",
"ct_app_label": "novinky",
"ct_model": "novinky"
},
{
"codename": "view_novinky",
"ct_app_label": "novinky",
"ct_model": "novinky"
},
{
"codename": "change_organizator",
"ct_app_label": "personalni",
"ct_model": "organizator"
},
{
"codename": "view_organizator",
"ct_app_label": "personalni",
"ct_model": "organizator"
},
{
"codename": "change_osoba",
"ct_app_label": "personalni",
"ct_model": "osoba"
},
{
"codename": "view_osoba",
"ct_app_label": "personalni",
"ct_model": "osoba"
},
{
"codename": "add_pohadka",
"ct_app_label": "tvorba",
"ct_model": "pohadka"
},
{
"codename": "change_pohadka",
"ct_app_label": "tvorba",
"ct_model": "pohadka"
},
{
"codename": "delete_pohadka",
"ct_app_label": "tvorba",
"ct_model": "pohadka"
},
{
"codename": "view_pohadka",
"ct_app_label": "tvorba",
"ct_model": "pohadka"
},
{
"codename": "add_prijemce",
"ct_app_label": "personalni",
"ct_model": "prijemce"
},
{
"codename": "change_prijemce",
"ct_app_label": "personalni",
"ct_model": "prijemce"
},
{
"codename": "delete_prijemce",
"ct_app_label": "personalni",
"ct_model": "prijemce"
},
{
"codename": "view_prijemce",
"ct_app_label": "personalni",
"ct_model": "prijemce"
},
{
"codename": "add_problem",
"ct_app_label": "tvorba",
"ct_model": "problem"
},
{
"codename": "change_problem",
"ct_app_label": "tvorba",
"ct_model": "problem"
},
{
"codename": "delete_problem",
"ct_app_label": "tvorba",
"ct_model": "problem"
},
{
"codename": "view_problem",
"ct_app_label": "tvorba",
"ct_model": "problem"
},
{
"codename": "change_resitel",
"ct_app_label": "personalni",
"ct_model": "resitel"
},
{
"codename": "view_resitel",
"ct_app_label": "personalni",
"ct_model": "resitel"
},
{
"codename": "add_rocnik",
"ct_app_label": "tvorba",
"ct_model": "rocnik"
},
{
"codename": "change_rocnik",
"ct_app_label": "tvorba",
"ct_model": "rocnik"
},
{
"codename": "delete_rocnik",
"ct_app_label": "tvorba",
"ct_model": "rocnik"
},
{
"codename": "view_rocnik",
"ct_app_label": "tvorba",
"ct_model": "rocnik"
},
{
"codename": "add_skola",
"ct_app_label": "personalni",
"ct_model": "skola"
},
{
"codename": "change_skola",
"ct_app_label": "personalni",
"ct_model": "skola"
},
{
"codename": "delete_skola",
"ct_app_label": "personalni",
"ct_model": "skola"
},
{
"codename": "view_skola",
"ct_app_label": "personalni",
"ct_model": "skola"
},
{
"codename": "add_soustredeni",
"ct_app_label": "soustredeni",
"ct_model": "soustredeni"
},
{
"codename": "change_soustredeni",
"ct_app_label": "soustredeni",
"ct_model": "soustredeni"
},
{
"codename": "delete_soustredeni",
"ct_app_label": "soustredeni",
"ct_model": "soustredeni"
},
{
"codename": "view_soustredeni",
"ct_app_label": "soustredeni",
"ct_model": "soustredeni"
},
{
"codename": "add_soustredeni_organizatori",
"ct_app_label": "soustredeni",
"ct_model": "soustredeni_organizatori"
},
{
"codename": "change_soustredeni_organizatori",
"ct_app_label": "soustredeni",
"ct_model": "soustredeni_organizatori"
},
{
"codename": "delete_soustredeni_organizatori",
"ct_app_label": "soustredeni",
"ct_model": "soustredeni_organizatori"
},
{
"codename": "view_soustredeni_organizatori",
"ct_app_label": "soustredeni",
"ct_model": "soustredeni_organizatori"
},
{
"codename": "add_soustredeni_ucastnici",
"ct_app_label": "soustredeni",
"ct_model": "soustredeni_ucastnici"
},
{
"codename": "change_soustredeni_ucastnici",
"ct_app_label": "soustredeni",
"ct_model": "soustredeni_ucastnici"
},
{
"codename": "delete_soustredeni_ucastnici",
"ct_app_label": "soustredeni",
"ct_model": "soustredeni_ucastnici"
},
{
"codename": "view_soustredeni_ucastnici",
"ct_app_label": "soustredeni",
"ct_model": "soustredeni_ucastnici"
},
{
"codename": "add_tema",
"ct_app_label": "tvorba",
"ct_model": "tema"
},
{
"codename": "change_tema",
"ct_app_label": "tvorba",
"ct_model": "tema"
},
{
"codename": "delete_tema",
"ct_app_label": "tvorba",
"ct_model": "tema"
},
{
"codename": "view_tema",
"ct_app_label": "tvorba",
"ct_model": "tema"
},
{
"codename": "add_uloha",
"ct_app_label": "tvorba",
"ct_model": "uloha"
},
{
"codename": "change_uloha",
"ct_app_label": "tvorba",
"ct_model": "uloha"
},
{
"codename": "delete_uloha",
"ct_app_label": "tvorba",
"ct_model": "uloha"
},
{
"codename": "view_uloha",
"ct_app_label": "tvorba",
"ct_model": "uloha"
},
{
"codename": "add_tag",
"ct_app_label": "taggit",
"ct_model": "tag"
},
{
"codename": "change_tag",
"ct_app_label": "taggit",
"ct_model": "tag"
},
{
"codename": "delete_tag",
"ct_app_label": "taggit",
"ct_model": "tag"
},
{
"codename": "view_tag",
"ct_app_label": "taggit",
"ct_model": "tag"
},
{
"codename": "add_taggeditem",
"ct_app_label": "taggit",
"ct_model": "taggeditem"
},
{
"codename": "change_taggeditem",
"ct_app_label": "taggit",
"ct_model": "taggeditem"
},
{
"codename": "delete_taggeditem",
"ct_app_label": "taggit",
"ct_model": "taggeditem"
},
{
"codename": "view_taggeditem",
"ct_app_label": "taggit",
"ct_model": "taggeditem"
}
]

View file

@ -13,7 +13,6 @@ make install_venv
make install
deploy_v2/pre_migration.py
make deploy_test
./manage.py load_org_permissions admin_org_prava.json
./manage.py loaddata data/*
systemctl --user start mamweb-test.service
./manage.py generate_thumbnails

View file

@ -30,6 +30,7 @@ Dokumentace (jak v ``docs/``, tak přímo v kódu) je psaná ve
zavislosti
sphinx
skripty
zkratky
modules/modules
dalsi_soubory
zapisy/zapisy

View file

@ -1,13 +1,15 @@
Sphinx na našem webu
====================
Dokumentace se zkompiluje příkazem ``make html`` ve složce ``doc``.
Dokumentace se zkompiluje příkazem ``make html`` ve složce ``docs``. (Musíte mít zapnutý virtualenv)
Složka ``modules`` je automaticiky generována a přegenerovávána. (**Nic v ní neupravovat!**)
Jinak všechny rst, co jsou ve složce ``doc`` a jejích podsložkách nezačínajících podtržítkem, budou v dokumentaci a to je přesně to, co editovat pro změnu dokumentace (kromě dokumentace přímo v Pythonu).
Jinak všechny rst, co jsou ve složce ``docs`` a jejích podsložkách nezačínajících podtržítkem, budou v dokumentaci a to je přesně to, co editovat pro změnu dokumentace (kromě dokumentace přímo v Pythonu).
Sphinx se píše v rst: `Návod na syntaxi rst`_ `Cheat sheet`_
Pokud něco chcete protlačit do bočního meníčka, je potřeba to připsat do souboru ``docs/index.rst`` (Zatím není úplně konsensus nad tím, co tam má a nemá být, takže pokud si nejste jistí, cpěte tam *všechno* ☺)
To je snad vše, co je potřeba vědět k dokumentaci mamwebu. Následující sekce jsou o tom, co jsem provedl Sphinxu, aby to fungovalo:
.. _Návod na syntaxi rst: https://sphinx-tutorial.readthedocs.io/step-1/#sections

View file

@ -10,11 +10,9 @@ věci jako chybové hlášky a vzhled M&M stránek (menu, patička, atd.). Aktu
i veškeré csv.
Další jsou pak jednotlivé aplikace (pokud něco hledáte, tak zřejmě chcete najít
tu aplikaci, která tomu odpovídá, respektive se k ní dostat přes url), za
zmínku stojí seminar, kde jsou takové ty věci, co zbyly. Plus jsou tam aktuálně
téměř všechny modely, protože je těžké je přesunout jinam.
tu aplikaci, která tomu odpovídá, respektive se k ní dostat přes url).
**TLDR: Nevšímejte si složky data/ a souborů přímo v kořenové složce.**
**TLDR: Nevšímejte si složek data/ seminar/ a souborů přímo v kořenové složce.**
Kromě věcí potřebných ke gitu, :doc:`ke spuštění <vyvoj>` a fukci djanga,
dalších drobností, lokální databáze a již zmíněných aplikací jsou tu ``data``,
kde je takový ten obsah webu, co by se měl dát snadno měnit (tudíž musí být v
@ -22,6 +20,9 @@ databázi), tj. statické stránky, menu a obrázky v pozadí menu. Ten je třeb
měnit hlavně na produkci a sekundárně tady (může to dělat i newebař a nechcete
přepsat jeho práci). Vše, co nejsou aplikace je popsáno :doc:`tady <dalsi_soubory>`.
Ještě je tu aplikace ``seminar/``, kde bylo původně skoro všechno, a tak nám
tam zbývá spoustu historických migrací (čehož se jen tak nezbavíme).
Základy djanga
--------------

View file

@ -116,7 +116,7 @@ Aktuálně: Jakýsi coding style zhruba existuje, není popsaný, šíří se li
- Nesmí být striktně vynucovaný
- Musel by být hodně nastavitelný
- Nechceme mít kód plný `#NOQA: WTF42`
- Nejspíš vždycky bude mít false positives (`tvorba.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 (`tvorba.models.Cislo.posli_cislo_mailem`)
- Možná dobrý sluha, ale určitě špatný pán (also: špatná zkušenost ☺)
- __Důsledek:__ Hrozí, že těch falešných varování bude moc, čímž to ztratí smysl úplně
- Potenciálně by šlo aplikovat jen lokálně na změny?

86
docs/zkratky.rst Normal file
View file

@ -0,0 +1,86 @@
Zkratky aplikací ve zdrojácích
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Ve zdrojácích (zejména různé ``models.py``, ``views.py`` ap.) používáme spoustu
modelů. Někdy je praktičtější / někdo preferuje importovat celou aplikaci jako
jedno jméno a používat modely bez explicitních importů, tj::
# „hromadné“ importy:
import personalni.models as p
...
p.Organizator.objects.all()
# „explicitní“ importy:
from personalni.models import Organizator
...
Organizator.objects.all()
Na webschůzce 2024-11-05 jsme na toto téma otevřeli diskusi, tady je její závěr.
.. admonition:: Historické okénko
:class: note
Kdysi jsme měli (prakticky) všechny modely v jedné aplikaci, ``seminar``. Na
různých místech se pak ``seminar.models`` importovalo typicky jako ``s``
nebo jako ``m``.
Přirozeně, toto už nejde tak snadno, protože už neexistuje jedno místo, ze
kterého chceme tahat modely v kódu.
Konvence
========
Shodli jsme se, že nám rozhodně nevadí explicitní importy a z pohledu
čitelnosti je preferujeme. Nicméně při psaní kódu to některým webařům přijde
nepohodlné, takže očekáváme, že bude existovat spousta kódu, která bude chtít
importovat hromadně. Usnesli jsme se proto na následujících kanonických
zkratkách, aby se aplikace alespoň zkracovaly konzistentně.
V závorkách je uvedené případné jméno, ale nepředpokládáme, že někdo bude danou
aplikaci chtít importovat hromadně. Některé aplikace zkratku nemají, ty se
importují vždy pod plným jménem nebo explicitně.
.. list-table::
:header-rows: 1
* - Model
- Zkratka
* - ``aesop``
- ---
* - ``api``
- --- (``api``)
* - ``galerie``
- ``g``
* - ``header_fotky``
- --- (``hdr``)
* - ``korektury``
- ``kor``
* - ``novinky``
- ``nov``
* - ``odevzdavatko``
- ``odev``
* - ``personální``
- ``pers``/``p``
* - ``sifrovacka``
- (``sifr``)
* - ``soustredeni``
- ``sou``
* - ``treenode``
- ``tn``
* - ``tvorba``
- ``tv``
* - ``various``
- ``v``/``var``
* - ``vyroci``
- ---
* - ``vysledkovky``
- ``vysl``
.. admonition:: O všech modelech pod jedním jménem
:class: warning
Historické okénko výš zatajuje jeden detail: Při práci v shellu se hodí mít
modely k dispozici a nemuset přemýšlet nad dělením do aplikací, takže ve
skutečnosti existuje ``mamweb.vsechno``, jenž všechny modely obsahuje.
Z čitelnostních důvodů je ale *zakázáno* tento modul používat v kódu.

View file

@ -6,7 +6,7 @@ from django.template import RequestContext
from datetime import datetime
from galerie.models import Obrazek, Galerie
from seminar.models import Soustredeni
from soustredeni.models import Soustredeni
from galerie.forms import KomentarForm, NewGalerieForm
def zobrazit(galerie, request):

View file

@ -1,5 +1,6 @@
import os
from django.db import models
from django.urls import reverse
from django.utils import timezone
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
@ -128,6 +129,9 @@ class KorekturovanePDF(models.Model):
return nazev_split[0] # + " " + nazev_split[2]
except IndexError:
return self.nazev
def get_absolute_url(self):
return reverse('korektury', kwargs={'pdf': self.id})
@reversion.register(ignore_duplicates=True)

View file

@ -35,24 +35,27 @@ def create_test_pdf(rnd, organizatori):
# TODO silent ghostscript (vypisuje odstavec za každou stránku…)
KorekturovanePDF.objects.create(
nazev='B', komentar='Neuronové sítě', org=rnd.choice(organizatori), pdf=gen_filename(filename='B.pdf')
)
KorekturovanePDF.objects.create(
nazev='A', komentar='M&M: Jak řešit?', org=rnd.choice(organizatori), pdf=gen_filename(filename='A.pdf')
korekturovane_pdf = KorekturovanePDF.objects.create(
nazev='B', komentar='Neuronové sítě', pdf=gen_filename(filename='B.pdf')
)
korekturovane_pdf.orgove.set((rnd.choice(organizatori),))
korekturovane_pdf = KorekturovanePDF.objects.create(
nazev='A', komentar='M&M: Jak řešit?', org=rnd.choice(organizatori), pdf=gen_filename(filename='A.pdf'),
nazev='A', komentar='M&M: Jak řešit?', pdf=gen_filename(filename='A.pdf')
)
korekturovane_pdf.orgove.set(rnd.sample(organizatori, 2))
KorekturovanePDF.objects.create(
nazev='A', komentar='M&M: Jak řešit?', pdf=gen_filename(filename='A.pdf'),
status='zanaseni'
)
KorekturovanePDF.objects.create(
nazev='A', komentar='M&M: Jak řešit?', org=rnd.choice(organizatori), pdf=gen_filename(filename='A.pdf'),
korekturovane_pdf = KorekturovanePDF.objects.create(
nazev='A', komentar='M&M: Jak řešit?', pdf=gen_filename(filename='A.pdf'),
status='zastarale'
)
except (FileNotFoundError, Exception) as e:
korekturovane_pdf.orgove.set((rnd.choice(organizatori),))
except OSError as e:
# TODO najít správné chyby, které vyhazují různé systémy při neexistenci ImageMagick, nebo knihoven
logger.error(str(e))
logger.error(

View file

@ -8,4 +8,3 @@ ensure_venv
./manage.py testdata
./manage.py loaddata data/*
#make/sync_prod_flatpages
./manage.py load_org_permissions deploy_v2/admin_org_prava.json

View file

@ -4,7 +4,7 @@ set -exuo pipefail
. make/lib.sh
scp vue_frontend/webpack-stats.json "$GIMLI_LOGIN:$TESTWEB/vue_frontend/"
rsync -ave ssh seminar/static/seminar/vue "$GIMLI_LOGIN:$TESTWEB/seminar/static/seminar/"
rsync -ave ssh treenode/static/treenode/vue "$GIMLI_LOGIN:$TESTWEB/treenode/static/treenode/"
ssh "$GIMLI_LOGIN" "
set -euxo pipefail
cd $TESTWEB

View file

@ -13,11 +13,11 @@ from django.contrib.flatpages.admin import FlatPageAdmin as FlatPageAdminOld
from django.contrib.flatpages.admin import FlatpageForm as FlatpageFormOld
from django import forms
from ckeditor_uploader.widgets import CKEditorUploadingWidget
from django_ckeditor_5.widgets import CKEditor5Widget
class FlatpageForm(FlatpageFormOld):
content = forms.CharField(widget=CKEditorUploadingWidget())
content = forms.CharField(widget=CKEditor5Widget())
class Meta:
model = FlatPage # this is not automatically inherited from FlatpageFormOld
exclude = []
@ -43,7 +43,7 @@ def get_app_list(self, request, app_label=None):
app_dict = self._build_app_dict(request, label=app_label)
aplikace_nahore = [
'seminar',
'tvorba',
'personalni',
'novinky',
'korektury',
@ -57,7 +57,7 @@ def get_app_list(self, request, app_label=None):
# Sort the models alphabetically within each app.
for app in app_list:
app['models'].sort(key=lambda x: locale.strxfrm('žž' + x['name'].lower()) if (x['name'].endswith("(Node)")) else locale.strxfrm(x['name'].lower()))
app['models'].sort(key=lambda x: locale.strxfrm(x['name'].lower()))
return app_list

View file

@ -110,8 +110,7 @@ INSTALLED_APPS = (
'reversion',
'django_countries',
'solo',
'ckeditor',
'ckeditor_uploader',
'django_ckeditor_5',
'taggit',
'dal',
'dal_select2',
@ -186,26 +185,98 @@ SUMMERNOTE_CONFIG = {
]
}
CKEDITOR_UPLOAD_PATH = "uploads/"
CKEDITOR_IMAGE_BACKEND = 'pillow'
#CKEDITOR_JQUERY_URL = '//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js'
CKEDITOR_CONFIGS = {
CKEDITOR_5_CUSTOM_CSS = "css/ckeditor5_fix.css"
# customColorPalette = [
# {
# 'color': 'hsl(4, 90%, 58%)',
# 'label': 'Red',
# },
# {
# 'color': 'hsl(340, 82%, 52%)',
# 'label': 'Pink',
# },
# {
# 'color': 'hsl(291, 64%, 42%)',
# 'label': 'Purple',
# },
# {
# 'color': 'hsl(262, 52%, 47%)',
# 'label': 'Deep Purple',
# },
# {
# 'color': 'hsl(231, 48%, 48%)',
# 'label': 'Indigo',
# },
# {
# 'color': 'hsl(207, 90%, 54%)',
# 'label': 'Blue',
# },
# ]
CKEDITOR_5_FILE_STORAGE = "various.storage.UploadStorage"
CKEDITOR_5_CONFIGS = {
'default': {
'entities': False,
'toolbar': [
['Source', 'ShowBlocks', '-', 'Maximize'],
['Bold', 'Italic', 'Subscript', 'Superscript', '-', 'RemoveFormat'],
['NumberedList','BulletedList','-','Blockquote','-','JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock'],
['Link', 'Unlink', 'Anchor', '-', 'Image', 'Table', 'HorizontalRule'],
['Format'],
'language': 'cs',
'blockToolbar': [
'paragraph', 'heading1', 'heading2', 'heading3',
'|',
'bulletedList', 'numberedList',
'|',
'blockQuote',
],
# 'toolbar': 'full',
'height': '40em',
'width': '100%',
'toolbarStartupExpanded': False,
'allowedContent' : True,
'toolbar': ['sourceEditing', '|', 'heading', '|',
# 'outdent', 'indent', '|',
'bold', 'italic', 'link', 'underline', 'strikethrough',
'code',
# 'subscript', 'superscript',
# 'highlight',
'|', 'codeBlock', 'insertImage',
'bulletedList', 'numberedList', 'todoList', '|',
# 'blockQuote', '|',
# 'fontSize', 'fontFamily', 'fontColor', 'fontBackgroundColor',
# 'mediaEmbed',
'removeFormat',
# 'insertTable',
],
'image': {
'toolbar': ['imageTextAlternative', '|', 'imageStyle:alignLeft',
'imageStyle:alignRight', 'imageStyle:alignCenter', 'imageStyle:side', '|'],
'styles': [
'full',
'side',
'alignLeft',
'alignRight',
'alignCenter',
]
},
# 'table': {
# 'contentToolbar': [ 'tableColumn', 'tableRow', 'mergeTableCells',
# 'tableProperties', 'tableCellProperties' ],
# 'tableProperties': {
# 'borderColors': customColorPalette,
# 'backgroundColors': customColorPalette,
# },
# 'tableCellProperties': {
# 'borderColors': customColorPalette,
# 'backgroundColors': customColorPalette,
# }
# },
'heading' : {
'options': [
{ 'model': 'paragraph', 'title': 'Paragraph', 'class': 'ck-heading_paragraph' },
{ 'model': 'heading1', 'view': 'h1', 'title': 'Heading 1', 'class': 'ck-heading_heading1' },
{ 'model': 'heading2', 'view': 'h2', 'title': 'Heading 2', 'class': 'ck-heading_heading2' },
{ 'model': 'heading3', 'view': 'h3', 'title': 'Heading 3', 'class': 'ck-heading_heading3' },
]
},
},
'list': {
'properties': {
'styles': 'true',
'startIndex': 'true',
'reversed': 'true',
},
}
}
# Webpack loader
@ -279,11 +350,11 @@ LOGGING = {
'filters': ['Http404AsInfo'],
},
'seminar.prihlaska.form':{
'personalni.prihlaska.form':{
'handlers': ['console','registration_logfile'],
'level': 'INFO'
},
'seminar.prihlaska.problem':{
'personalni.prihlaska.problem':{
'handlers': ['console','mail_registration','registration_error_log'],
'level': 'INFO'
},
@ -342,6 +413,7 @@ SEMINAR_KONFERY_DIR = os.path.join('konfery')
KOREKTURY_PDF_DIR = os.path.join('korektury', 'pdf')
KOREKTURY_IMG_DIR = os.path.join('korektury', 'img')
CISLO_IMG_DIR = os.path.join('cislo', 'img')
SOUSTREDENI_KONTAKTNICKY_DIR = os.path.join('soustredeni', 'kontaktnicky')

View file

@ -0,0 +1,3 @@
.ck.ck-editor {
color: black !important; /* V tmavém módu zapomene CKEditor přepnout barvu textu. (Bílý text na bílém pozadí je best.) */
}

View file

@ -10,6 +10,8 @@
--orgovska-fialova: #6a0043;
--orgovska-svetla-fialova: #eee4ec;
--resitelska-fialova: #f296b3;
--resitelska-svetla-fialova: #f2E5EF;
--barva-pozadi: #fffbf6;
}

View file

@ -17,7 +17,27 @@
border: var(--orgovska-fialova) 2px dashed;
& .mam-org-only {
border: 0;
/* Vnitřní rámečky mají být taky vidět */
border-width: 1px;
background-color: rgba(0, 0, 0, 0.06);
}
& li {
padding: 3px 0;
margin: -2px 0;
}
}
.mam-resitel-only {
background: var(--resitelska-svetla-fialova);
padding: 10px;
margin: 10px -10px;
border: var(--resitelska-fialova) 2px dashed;
& .mam-resitel-only {
/* Vnitřní rámečky mají být taky vidět */
border-width: 1px;
background-color: rgba(0, 0, 0, 0.06);
}
& li {
@ -369,7 +389,7 @@ div.org_email {
}
/* td obsahující křížek v detailu řešení se nesmí smrštit na 0 */
.td:has(.smazat_hodnoceni) {
td:has(.smazat_hodnoceni) {
min-width: 20px;
padding: 3px;
}

View file

@ -15,7 +15,7 @@ urlpatterns = [
# Admin a nastroje
path('admin/', admin.site.urls), # NOQA
path('ckeditor/', include('ckeditor_uploader.urls')),
path("ckeditor5/", include('django_ckeditor_5.urls')),
# Tvorba = ročníky, čísla, problémy atd. (ma vlastni podadresare)
path('', include('tvorba.urls')),

15
mamweb/vsechno.py Normal file
View file

@ -0,0 +1,15 @@
# Tento soubor slouží pouze pro shell a podobné. Nikde neimportovat v kódu!
print("Naimportoval jsi `seminar.models`. Pevně věřím, že to nebylo nikde v kódu. Díky.")
from galerie.models import *
from header_fotky.models import *
from korektury.models import *
from novinky.models import *
from odevzdavatko.models import *
from personalni.models import *
from prednasky.models import *
from soustredeni.models import *
from treenode.models import *
from tvorba.models import *
from various.models import *

View file

@ -1,5 +1,20 @@
import django.forms
from django.contrib import admin
from django_ckeditor_5.widgets import CKEditor5Widget
from .models import Novinky
admin.site.register(Novinky)
class NovinkyAdminForm(django.forms.ModelForm):
class Meta:
model = Novinky
widgets = {
'text': CKEditor5Widget,
}
fields = '__all__'
@admin.register(Novinky)
class NovinkyAdmin(admin.ModelAdmin):
form = NovinkyAdminForm

View file

@ -4,7 +4,7 @@
{% if not novinka.zverejneno and user.je_org %}
<div class="mam-org-only">
<ul>
<li><a href="/admin/seminar/novinky/{{novinka.pk}}">Upravit novinku</a>
<li><a href="{% url 'admin:novinky_novinky_change' novinka.pk %}">Upravit novinku</a>
</ul>
{% endif %}
{% if novinka.zverejneno or user.je_org %}

View file

@ -1,6 +1,6 @@
from django.contrib import admin
from django_reverse_admin import ReverseModelAdmin
import seminar.models as m
import odevzdavatko.models as m
class PrilohaReseniInline(admin.TabularInline):

View file

@ -4,8 +4,11 @@ from django.forms import formset_factory
from django.forms.models import inlineformset_factory
from django.utils import timezone
from seminar.models import Resitel
import seminar.models as m
from personalni.models import Resitel
from tvorba.models import Problem, Deadline
from various.models import Nastaveni
from odevzdavatko.models import Reseni, PrilohaReseni, Hodnoceni
import logging
@ -22,7 +25,7 @@ class DateInput(forms.DateInput):
class PosliReseniForm(forms.Form):
problem = forms.ModelMultipleChoiceField(
queryset=m.Problem.objects.all(),
queryset=Problem.objects.all(),
label="Problémy",
widget=autocomplete.ModelSelect2Multiple(
url='autocomplete_problem',
@ -58,7 +61,7 @@ class PosliReseniForm(forms.Form):
#cas_doruceni = models.DateTimeField('čas_doručení', default=timezone.now, blank=True)
forma = forms.ChoiceField(label="Forma řešení",choices = m.Reseni.FORMA_CHOICES)
forma = forms.ChoiceField(label="Forma řešení",choices = Reseni.FORMA_CHOICES)
#forma = models.CharField('forma řešení', max_length=16, choices=FORMA_CHOICES, blank=False,
# default=FORMA_EMAIL)
@ -69,7 +72,7 @@ class PosliReseniForm(forms.Form):
class NahrajReseniForm(forms.ModelForm):
class Meta:
model = m.Reseni
model = Reseni
fields = ('problem', 'resitele')
help_texts = {'problem':''} # Nezobrazovat help text ve formuláři
@ -109,11 +112,11 @@ class NahrajReseniForm(forms.ModelForm):
def clean_problem(self):
problem = self.cleaned_data.get('problem')
for p in problem:
if p.stav != m.Problem.STAV_ZADANY:
if p.stav != Problem.STAV_ZADANY:
raise forms.ValidationError("Problém " + str(p) + " již nelze řešit!")
return problem
ReseniSPrilohamiFormSet = inlineformset_factory(m.Reseni,m.PrilohaReseni,
ReseniSPrilohamiFormSet = inlineformset_factory(Reseni, PrilohaReseni,
form = NahrajReseniForm,
fields = ('soubor','res_poznamka'),
widgets = {'res_poznamka':forms.TextInput()},
@ -125,7 +128,7 @@ ReseniSPrilohamiFormSet = inlineformset_factory(m.Reseni,m.PrilohaReseni,
class JednoHodnoceniForm(forms.ModelForm):
class Meta:
model = m.Hodnoceni
model = Hodnoceni
fields = ('problem', 'body', 'deadline_body', 'feedback',)
widgets = {
'problem': autocomplete.ModelSelect2(
@ -158,7 +161,7 @@ OhodnoceniReseniFormSet = formset_factory(JednoHodnoceniForm,
class PoznamkaReseniForm(forms.ModelForm):
class Meta:
model = m.Reseni
model = Reseni
fields = ('poznamka',)
# FIXME: Ideálně by mělo být součástí třídy níž, ale neumím to udělat
@ -198,7 +201,7 @@ class OdevzdavatkoTabulkaFiltrForm(forms.Form):
from django.db.utils import OperationalError
try:
aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik
aktualni_rocnik = Nastaveni.get_solo().aktualni_rocnik
except OperationalError:
# django.db.utils.OperationalError: no such table: seminar_nastaveni
# Nemáme databázi, takže to selhalo. Pro jistotu vrátíme aspoň dvě možnosti, ať to nepadá dál
@ -214,7 +217,7 @@ class OdevzdavatkoTabulkaFiltrForm(forms.Form):
result.append(("0001-01-01", f"Odjakživa"))
for deadline in m.Deadline.objects.filter(
for deadline in Deadline.objects.filter(
deadline__lte=timezone.now(),
cislo__rocnik=aktualni_rocnik
).order_by("deadline"):

View file

@ -9,14 +9,14 @@ from django.urls import reverse_lazy
from django.utils import timezone
from django.conf import settings
import tvorba.models as am
from seminar.models import base as bm
from tvorba.models import Problem, Deadline, Cislo, Uloha, aux_generate_filename
from various.models import SeminarModelBase
from odevzdavatko.utils import vzorecek_na_prepocet, inverze_vzorecku_na_prepocet
from personalni.models import Resitel
@reversion.register(ignore_duplicates=True)
class Reseni(bm.SeminarModelBase):
class Reseni(SeminarModelBase):
class Meta:
db_table = 'seminar_reseni'
@ -29,7 +29,7 @@ class Reseni(bm.SeminarModelBase):
id = models.AutoField(primary_key = True)
# Ke každé dvojici řešní a problém existuje nanejvýš jedno hodnocení, doplnění vazby.
problem = models.ManyToManyField(am.Problem, verbose_name='problém', help_text='Problém',
problem = models.ManyToManyField(Problem, verbose_name='problém', help_text='Problém',
through='Hodnoceni')
resitele = models.ManyToManyField(Resitel, verbose_name='autoři řešení',
@ -79,7 +79,7 @@ class Reseni(bm.SeminarModelBase):
# NOTE: Potenciální DB HOG (bez select_related)
def deadline_reseni(self):
return am.Deadline.objects.filter(deadline__gte=self.cas_doruceni).order_by("deadline").first()
return Deadline.objects.filter(deadline__gte=self.cas_doruceni).order_by("deadline").first()
## Pravdepodobne uz nebude potreba:
# def save(self, *args, **kwargs):
@ -88,7 +88,7 @@ class Reseni(bm.SeminarModelBase):
# self.cislo_body = self.problem.cislo_reseni
# super(Reseni, self).save(*args, **kwargs)
class Hodnoceni(bm.SeminarModelBase):
class Hodnoceni(SeminarModelBase):
class Meta:
db_table = 'seminar_hodnoceni'
verbose_name = 'Hodnocení'
@ -101,16 +101,16 @@ class Hodnoceni(bm.SeminarModelBase):
body = models.DecimalField(max_digits=8, decimal_places=1, verbose_name='body',
blank=True, null=True)
cislo_body = models.ForeignKey(am.Cislo, verbose_name='číslo pro body',
cislo_body = models.ForeignKey(Cislo, verbose_name='číslo pro body',
related_name='hodnoceni', blank=True, null=True, on_delete=models.PROTECT)
# V ročníku < 26 nastaveno na deadline vygenerovaný pro původní cislo_body
deadline_body = models.ForeignKey(am.Deadline, verbose_name='deadline pro body',
deadline_body = models.ForeignKey(Deadline, verbose_name='deadline pro body',
related_name='hodnoceni', blank=True, null=True, on_delete=models.PROTECT)
reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE)
problem = models.ForeignKey(am.Problem, verbose_name='problém',
problem = models.ForeignKey(Problem, verbose_name='problém',
related_name='hodnoceni', on_delete=models.PROTECT)
feedback = models.TextField('zpětná vazba', blank=True, default='', help_text='Zpětná vazba řešiteli (plain text)')
@ -166,7 +166,7 @@ class Hodnoceni(bm.SeminarModelBase):
@property
def body_neprepocitane_max(self):
if not isinstance(self.problem.get_real_instance(), am.Uloha):
if not isinstance(self.problem.get_real_instance(), Uloha):
return None
return self.problem.uloha.max_body
@ -176,12 +176,12 @@ class Hodnoceni(bm.SeminarModelBase):
def generate_filename(self, filename):
return os.path.join(
settings.SEMINAR_RESENI_DIR,
am.aux_generate_filename(self, filename)
aux_generate_filename(self, filename)
)
@reversion.register(ignore_duplicates=True)
class PrilohaReseni(bm.SeminarModelBase):
class PrilohaReseni(SeminarModelBase):
class Meta:
db_table = 'seminar_priloha_reseni'

View file

@ -11,7 +11,7 @@
{% endblock %}
</h1>
<form enctype="multipart/form-data" action="{% url 'seminar_nahraj_reseni' nadproblem_id %}" method="post" onsubmit="return zkontroluj_prilohy();">
<form enctype="multipart/form-data" action="{% url 'odevzdavatko_nahraj_reseni' nadproblem_id %}" method="post" onsubmit="return zkontroluj_prilohy();">
{% csrf_token %}
<table class='form'>
<tr>

View file

@ -12,7 +12,7 @@
<ul>
{% for problem in object_list %}
<li><a href="{% url 'seminar_nahraj_reseni' problem.id %}">{{ problem }}</a></li>
<li><a href="{% url 'odevzdavatko_nahraj_reseni' problem.id %}">{{ problem }}</a></li>
{% empty %}
<li>Nelze nic odevzdávat.</li>
{% endfor %}

View file

@ -12,7 +12,7 @@
Vložit řešení
{% endblock %}
</h1>
<form enctype="multipart/form-data" action="{% url 'seminar_vloz_reseni' %}" method="post" onsubmit="return zkontroluj_prilohy();">
<form enctype="multipart/form-data" action="{% url 'odevzdavatko_vloz_reseni' %}" method="post" onsubmit="return zkontroluj_prilohy();">
{% csrf_token %}
{{form.as_p}}

View file

@ -2,11 +2,11 @@ from django import template
register = template.Library()
from functools import cache
import seminar.models as m
from odevzdavatko.models import Reseni
@register.filter
@cache
def barva_reseni(r: m.Reseni):
def barva_reseni(r: Reseni):
"""Vrátí nějakou barvu pro daný problém, ve tvaru '#RRGGBB'
Efektivně hešujeme do barev."""

View file

@ -2,8 +2,8 @@ from django import template
register = template.Library()
from personalni.utils import normalizuj_jmeno
import seminar.models as m # jen kvůli typové anotaci…
from personalni.models import Osoba # jen kvůli typové anotaci…
@register.filter
def jmeno_jako_prefix(o: m.Osoba):
def jmeno_jako_prefix(o: Osoba):
return normalizuj_jmeno(o).replace(' ', '_')

View file

@ -5,10 +5,10 @@ from various.views.generic import viewMethodSwitch
from . import views
urlpatterns = [
path('org/add_solution', org_required(views.VlozReseniView.as_view()), name='seminar_vloz_reseni'),
path('resitel/nahraj_reseni', resitel_required(views.NahrajReseniRozcestnikTematekView.as_view()), name='seminar_nahraj_reseni'),
path('resitel/nahraj_reseni/<int:nadproblem_id>/', resitel_required(views.NahrajReseniView.as_view()), name='seminar_nahraj_reseni'),
path('resitel/odevzdana_reseni/', resitel_or_org_required(views.PrehledOdevzdanychReseni.as_view()), name='seminar_resitel_odevzdana_reseni'),
path('org/add_solution', org_required(views.VlozReseniView.as_view()), name='odevzdavatko_vloz_reseni'),
path('resitel/nahraj_reseni', resitel_required(views.NahrajReseniRozcestnikTematekView.as_view()), name='odevzdavatko_nahraj_reseni'),
path('resitel/nahraj_reseni/<int:nadproblem_id>/', resitel_required(views.NahrajReseniView.as_view()), name='odevzdavatko_nahraj_reseni'),
path('resitel/odevzdana_reseni/', resitel_or_org_required(views.PrehledOdevzdanychReseni.as_view()), name='odevzdavatko_resitel_odevzdana_reseni'),
path('org/reseni/', org_required(views.TabulkaOdevzdanychReseniView.as_view()), name='odevzdavatko_tabulka'),
path('org/reseni/rocnik/<int:rocnik>/', org_required(views.TabulkaOdevzdanychReseniView.as_view()), name='odevzdavatko_tabulka'),

View file

@ -17,10 +17,14 @@ from decimal import Decimal
from itertools import groupby
import logging
import seminar.models as m
from . import forms as f
from .forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm
from .models import Hodnoceni, Reseni
from personalni.models import Resitel, Osoba, Organizator
from tvorba.models import Problem, Deadline, Rocnik
from tvorba.utils import resi_v_rocniku
from various.models import Nastaveni
from various.views.pomocne import formularOKView
logger = logging.getLogger(__name__)
@ -40,20 +44,20 @@ logger = logging.getLogger(__name__)
class TabulkaOdevzdanychReseniView(ListView):
template_name = 'odevzdavatko/tabulka.html'
model = m.Hodnoceni
model = Hodnoceni
def inicializuj_osy_tabulky(self):
"""Vyrobí prvotní querysety pro sloupce a řádky, tj. seznam všech řešitelů a problémů"""
# FIXME: jméno metody není vypovídající...
# NOTE: Tenhle blok nemůže být přímo ve třídě, protože před vyrobením databáze neexistují ty objekty (?). TODO: Otestovat
# TODO: Prefetches, Select related, ...
self.resitele = m.Resitel.objects.all()
self.problemy = m.Problem.objects.all()
self.reseni = m.Reseni.objects.all()
self.resitele = Resitel.objects.all()
self.problemy = Problem.objects.all()
self.reseni = Reseni.objects.all()
self.aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik # .get_solo() vrátí tu jedinou instanci
self.aktualni_rocnik = Nastaveni.get_solo().aktualni_rocnik # .get_solo() vrátí tu jedinou instanci
if 'rocnik' in self.kwargs:
self.aktualni_rocnik = get_object_or_404(m.Rocnik, rocnik=self.kwargs['rocnik'])
self.aktualni_rocnik = get_object_or_404(Rocnik, rocnik=self.kwargs['rocnik'])
form = FiltrForm(self.request.GET, rocnik=self.aktualni_rocnik)
if form.is_valid():
@ -86,14 +90,14 @@ class TabulkaOdevzdanychReseniView(ListView):
self.resitele = self.resitele.filter(rok_maturity__gt=self.aktualni_rocnik.prvni_rok)
if problemy == FiltrForm.PROBLEMY_MOJE:
org = m.Organizator.objects.get(osoba__user=self.request.user)
org = Organizator.objects.get(osoba__user=self.request.user)
self.problemy = self.problemy.filter(
Q(autor=org)|Q(garant=org)|Q(opravovatele=org),
Q(stav=m.Problem.STAV_ZADANY)|Q(stav=m.Problem.STAV_VYRESENY),
Q(stav=Problem.STAV_ZADANY)|Q(stav=Problem.STAV_VYRESENY),
)
elif problemy == FiltrForm.PROBLEMY_LETOSNI:
self.problemy = self.problemy.filter(
Q(stav=m.Problem.STAV_ZADANY)|Q(stav=m.Problem.STAV_VYRESENY),
Q(stav=Problem.STAV_ZADANY)|Q(stav=Problem.STAV_VYRESENY),
)
#self.problemy = list(filter(lambda problem: problem.rocnik() == self.aktualni_rocnik, self.problemy)) # DB HOG? # FIXME: některé problémy nemají ročník....
# NOTE: Protože řešení odkazuje přímo na Problém a QuerySet na Hodnocení je nepolymorfní, musíme porovnávat taky s nepolymorfními Problémy.
@ -121,8 +125,8 @@ class TabulkaOdevzdanychReseniView(ListView):
ctx = super().get_context_data(*args, **kwargs)
ctx['problemy'] = self.problemy
ctx['resitele'] = self.resitele
tabulka: dict[m.Problem, dict[m.Resitel, list[tuple[m.Reseni, m.Hodnoceni]]]] = dict()
soucty: dict[m.Problem, dict[m.Resitel, Decimal]] = dict()
tabulka: dict[Problem, dict[Resitel, list[tuple[Reseni, Hodnoceni]]]] = dict()
soucty: dict[Problem, dict[Resitel, Decimal]] = dict()
def pridej_reseni(resitel, hodnoceni):
problem = hodnoceni.problem
@ -143,11 +147,11 @@ class TabulkaOdevzdanychReseniView(ListView):
for resitel in hodnoceni.reseni.resitele.all():
pridej_reseni(resitel, hodnoceni)
hodnoty: list[list[tuple[Decimal,list[tuple[m.Reseni, m.Hodnoceni]]]]] = [] # Seznam řádků výsledné tabulky podle self.resitele, v každém řádku buňky v pořadí podle self.problemy + jejich součty, v každé buňce seznam řešení k danému řešiteli a problému.
resitele_do_tabulky: list[m.Resitel] = []
hodnoty: list[list[tuple[Decimal,list[tuple[Reseni, Hodnoceni]]]]] = [] # Seznam řádků výsledné tabulky podle self.resitele, v každém řádku buňky v pořadí podle self.problemy + jejich součty, v každé buňce seznam řešení k danému řešiteli a problému.
resitele_do_tabulky: list[Resitel] = []
for resitel in self.resitele:
dostal_body = False
resiteluv_radek: list[tuple[Decimal,list[tuple[m.Reseni, m.Hodnoceni]]]] = [] # podle pořadí v self.problemy
resiteluv_radek: list[tuple[Decimal,list[tuple[Reseni, Hodnoceni]]]] = [] # podle pořadí v self.problemy
for problem in self.problemy:
if problem in tabulka and resitel in tabulka[problem]:
resiteluv_radek.append((soucty[problem][resitel], tabulka[problem][resitel]))
@ -162,7 +166,7 @@ class TabulkaOdevzdanychReseniView(ListView):
# Pro použití hacku na automatické {{form.media}} v template:
ctx['form'] = ctx['filtr']
# Pro maximum v přesměrovátku ročníků
ctx['aktualni_rocnik'] = m.Nastaveni.get_solo().aktualni_rocnik
ctx['aktualni_rocnik'] = Nastaveni.get_solo().aktualni_rocnik
ctx['barvicky'] = self.barvicky
if 'rocnik' in self.kwargs:
ctx['rocnik'] = self.kwargs['rocnik']
@ -178,7 +182,7 @@ class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixi
Asi bude zastaralý v okamžiku, kdy se tenhle komentář nasadí na produkci :-)
V případě, že takové řešení existuje jen jedno, tak na něj přesměruje."""
model = m.Reseni
model = Reseni
template_name = 'odevzdavatko/seznam.html'
def get_queryset(self):
@ -190,8 +194,8 @@ class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixi
if problem_id is None:
raise ValueError("Nemám problém! (To je problém!)")
resitel = m.Resitel.objects.get(id=resitel_id)
problem = m.Problem.objects.get(id=problem_id)
resitel = Resitel.objects.get(id=resitel_id)
problem = Problem.objects.get(id=problem_id)
qs = qs.filter(
problem__in=[problem],
resitele__in=[resitel],
@ -221,13 +225,13 @@ class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixi
## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex
class DetailReseniView(DetailView):
""" Náhled na řešení. Editace je v :py:class:`EditReseniView`. """
model = m.Reseni
model = Reseni
template_name = 'odevzdavatko/detail.html'
def aktualni_hodnoceni(self):
self.reseni = get_object_or_404(m.Reseni, id=self.kwargs['pk'])
self.reseni = get_object_or_404(Reseni, id=self.kwargs['pk'])
result = [] # Slovníky s klíči problem, body, deadline_body -- initial data pro f.OhodnoceniReseniFormSet
for hodn in m.Hodnoceni.objects.filter(reseni=self.reseni):
for hodn in Hodnoceni.objects.filter(reseni=self.reseni):
seznam_atributu = [
"problem",
"body",
@ -284,7 +288,7 @@ class EditReseniView(DetailReseniView):
def hodnoceniReseniView(request, pk, *args, **kwargs):
reseni = get_object_or_404(m.Reseni, pk=pk)
reseni = get_object_or_404(Reseni, pk=pk)
success_url = reverse('odevzdavatko_detail_reseni', kwargs={'pk': pk})
# FIXME: Použit initial i tady a nebastlit hodnocení tak nízkoúrovňově
@ -300,7 +304,7 @@ def hodnoceniReseniView(request, pk, *args, **kwargs):
poznamka_form.save()
# Smažeme všechna dosavadní hodnocení tohoto řešení
qs = m.Hodnoceni.objects.filter(reseni=reseni)
qs = Hodnoceni.objects.filter(reseni=reseni)
logger.info(f"Will delete {qs.count()} objects: {qs}")
qs.delete()
@ -311,7 +315,7 @@ def hodnoceniReseniView(request, pk, *args, **kwargs):
del(data_for_hodnoceni["body_celkem"])
del(data_for_hodnoceni["body_neprepocitane"])
del(data_for_hodnoceni["body_neprepocitane_celkem"])
hodnoceni = m.Hodnoceni(
hodnoceni = Hodnoceni(
reseni=reseni,
**form.cleaned_data,
)
@ -332,14 +336,14 @@ def hodnoceniReseniView(request, pk, *args, **kwargs):
class PrehledOdevzdanychReseni(ListView):
model = m.Hodnoceni
model = Hodnoceni
template_name = 'odevzdavatko/prehled_reseni.html'
def get_queryset(self):
if not self.request.user.is_authenticated:
raise RuntimeError("Uživatel měl být přihlášený!")
# get_or_none, aby neexistence řešitele (např. u orgů) neházela chybu
resitel = m.Resitel.objects.filter(osoba__user=self.request.user).first()
resitel = Resitel.objects.filter(osoba__user=self.request.user).first()
qs = super().get_queryset()
qs = qs.filter(reseni__resitele__in=[resitel])
# Setřídíme podle času doručení řešení, aby se netřídily podle okamžiku vyrobení Hodnocení
@ -365,13 +369,13 @@ class PrehledOdevzdanychReseni(ListView):
# Přehled všech řešení kvůli debugování
class SeznamReseniView(ListView):
model = m.Reseni
model = Reseni
template_name = 'odevzdavatko/seznam.html'
class SeznamAktualnichReseniView(SeznamReseniView):
def get_queryset(self):
qs = super().get_queryset()
akt_rocnik = m.Nastaveni.get_solo().aktualni_rocnik # .get_solo() vrátí tu jedinou instanci, asi...
akt_rocnik = Nastaveni.get_solo().aktualni_rocnik # .get_solo() vrátí tu jedinou instanci, asi...
resitele = resi_v_rocniku(akt_rocnik)
qs = qs.filter(resitele__in=resitele) # FIXME: Najde řešení i ze starých ročníků, která odevzdal alespoň jeden aktuální řešitel
return qs
@ -383,7 +387,7 @@ class VlozReseniView(LoginRequiredMixin, FormView):
def form_valid(self, form):
data = form.cleaned_data
nove_reseni = m.Reseni.objects.create(
nove_reseni = Reseni.objects.create(
cas_doruceni=data['cas_doruceni'],
forma=data['forma'],
poznamka=data['poznamka'],
@ -410,35 +414,35 @@ class VlozReseniView(LoginRequiredMixin, FormView):
class NahrajReseniRozcestnikTematekView(LoginRequiredMixin, ListView):
model = m.Problem
model = Problem
template_name = 'odevzdavatko/nahraj_reseni_nadproblem.html'
def get_queryset(self):
return super().get_queryset().filter(stav=m.Problem.STAV_ZADANY, nadproblem__isnull=True)
return super().get_queryset().filter(stav=Problem.STAV_ZADANY, nadproblem__isnull=True)
class NahrajReseniView(LoginRequiredMixin, CreateView):
model = m.Reseni
model = Reseni
template_name = 'odevzdavatko/nahraj_reseni.html'
form_class = f.NahrajReseniForm
nadproblem: m.Problem
nadproblem: Problem
def setup(self, request, *args, **kwargs):
super().setup(request, *args, **kwargs)
nadproblem_id = self.kwargs["nadproblem_id"]
self.nadproblem = get_object_or_404(m.Problem, id=nadproblem_id)
self.nadproblem = get_object_or_404(Problem, id=nadproblem_id)
def get(self, request, *args, **kwargs):
# Zaříznutí nezadaných problémů
if self.nadproblem.stav != m.Problem.STAV_ZADANY:
if self.nadproblem.stav != Problem.STAV_ZADANY:
raise PermissionDenied()
# Zaříznutí starých řešitelů:
# FIXME: Je to tady dost naprasené, mělo by to asi být jinde…
osoba = m.Osoba.objects.get(user=self.request.user)
osoba = Osoba.objects.get(user=self.request.user)
resitel = osoba.resitel
if resitel.rok_maturity <= m.Nastaveni.get_solo().aktualni_rocnik.prvni_rok:
if resitel.rok_maturity <= Nastaveni.get_solo().aktualni_rocnik.prvni_rok:
return render(request, 'universal.html', {
'title': 'Nelze odevzdat',
'error': 'Zdá se, že jsi již odmaturoval/a, a tedy nemůžeš odevzdat do našeho semináře řešení.',
@ -450,7 +454,7 @@ class NahrajReseniView(LoginRequiredMixin, CreateView):
nadproblem_id = self.nadproblem.id
return {
"nadproblem_id": nadproblem_id,
"problem": [] if self.nadproblem.podproblem.filter(stav=m.Problem.STAV_ZADANY).exists() else nadproblem_id
"problem": [] if self.nadproblem.podproblem.filter(stav=Problem.STAV_ZADANY).exists() else nadproblem_id
}
@ -462,7 +466,7 @@ class NahrajReseniView(LoginRequiredMixin, CreateView):
data['prilohy'] = f.ReseniSPrilohamiFormSet()
data["nadproblem_id"] = self.nadproblem.id
data["nadproblem"] = get_object_or_404(m.Problem, id=self.nadproblem.id)
data["nadproblem"] = get_object_or_404(Problem, id=self.nadproblem.id)
return data
# FIXME prepsat tak, aby form_valid se volalo jen tehdy, kdyz je form i formset validni
@ -474,17 +478,17 @@ class NahrajReseniView(LoginRequiredMixin, CreateView):
return super().form_invalid(form)
with transaction.atomic():
self.object = form.save()
self.object.resitele.add(m.Resitel.objects.get(osoba__user = self.request.user))
self.object.resitele.add(Resitel.objects.get(osoba__user = self.request.user))
self.object.resitele.add(*form.cleaned_data["resitele"])
self.object.cas_doruceni = timezone.now()
self.object.forma = m.Reseni.FORMA_UPLOAD
self.object.forma = Reseni.FORMA_UPLOAD
self.object.save()
prilohy.instance = self.object
prilohy.save()
for hodnoceni in self.object.hodnoceni_set.all():
hodnoceni.deadline_body = m.Deadline.objects.filter(deadline__gte=self.object.cas_doruceni).first()
hodnoceni.deadline_body = Deadline.objects.filter(deadline__gte=self.object.cas_doruceni).first()
hodnoceni.save()
# Pošleme mail opravovatelům a garantovi
@ -502,7 +506,7 @@ class NahrajReseniView(LoginRequiredMixin, CreateView):
# FIXME: Víc informativní obsah mailů, možná vč. příloh?
prijemci = map(lambda it: it.osoba.email, prijemci)
resitel = m.Osoba.objects.get(user = self.request.user)
resitel = Osoba.objects.get(user = self.request.user)
seznam = "problému " + str(problemy[0]) if len(problemy) == 1 else 'následujícím problémům:\n' + ', \n'.join(map(str, problemy))
seznam_do_subjectu = "problému " + str(problemy[0]) + ("" if len(problemy) == 1 else f" (a dalším { len(problemy) - 1 })")
@ -517,5 +521,5 @@ class NahrajReseniView(LoginRequiredMixin, CreateView):
return formularOKView(
self.request,
text='Řešení úspěšně odevzdáno',
dalsi_odkazy=[("Odevzdat další řešení", reverse("seminar_nahraj_reseni"))],
dalsi_odkazy=[("Odevzdat další řešení", reverse("odevzdavatko_nahraj_reseni"))],
)

View file

@ -2,7 +2,7 @@ from django.contrib import admin
from django.contrib.auth.models import Group
from django_reverse_admin import ReverseModelAdmin
from django.contrib.messages import WARNING, ERROR, SUCCESS
import seminar.models as m
import personalni.models as m
from datetime import datetime
@admin.action(description="Sjednoť telefony")

View file

@ -4,7 +4,7 @@ from django.contrib.auth.forms import PasswordResetForm
from django.core.exceptions import ObjectDoesNotExist
from django.contrib.auth.models import User
from seminar.models import Skola, Resitel, Osoba
from personalni.models import Skola, Resitel, Osoba
from datetime import date
import logging
@ -27,7 +27,7 @@ class TelInput(forms.TextInput):
class UdajeForm(forms.Form):
username = None
err_logger = logging.getLogger('seminar.prihlaska.problem')
err_logger = logging.getLogger('personalni.prihlaska.problem')
jmeno = forms.CharField(label='Jméno', max_length=256, required=True)
prezdivka_resitele = forms.CharField(label='Přezdívka (veřejná)', max_length=256, required=False)
@ -147,7 +147,7 @@ class PrihlaskaForm(PasswordResetForm, UdajeForm):
class ProfileEditForm(UdajeForm):
err_logger = logging.getLogger('seminar.edit.problem')
err_logger = logging.getLogger('personalni.prihlaska.problem.edit')
username = forms.CharField(
label='Přihlašovací jméno',
max_length=256,

View file

@ -11,7 +11,7 @@ from django_countries.fields import CountryField
from reversion import revisions as reversion
from seminar.models.base import SeminarModelBase
from various.models import SeminarModelBase
logger = logging.getLogger(__name__)

View file

@ -78,7 +78,7 @@
<li>hlasování o přednáškách</li>
</ul>
</li>
<li><a href="{% url 'seminar_seznam_soustredeni' %}">proběhlá soustředění</a>
<li><a href="{% url 'soustredeni_seznam' %}">proběhlá soustředění</a>
<ul>
<li>vytvoření galerie</li>
<li>stažení seznamu účastníků</li>

View file

@ -10,9 +10,9 @@
</h1>
<a href="{% url 'logout' %}">Odhlásit se</a><br>
<a href="{% url 'seminar_resitel_edit' %}">Upravit údaje</a><br>
<a href="{% url 'seminar_nahraj_reseni' %}">Nahrát řešení</a><br>
<a href="{% url 'seminar_resitel_odevzdana_reseni' %}">Již odevzdaná řešení</a><br>
<a href="{% url 'personalni_resitel_edit' %}">Upravit údaje</a><br>
<a href="{% url 'odevzdavatko_nahraj_reseni' %}">Nahrát řešení</a><br>
<a href="{% url 'odevzdavatko_resitel_odevzdana_reseni' %}">Již odevzdaná řešení</a><br>
{% endblock %}

View file

@ -11,7 +11,7 @@
<hr>
<p><a href="{% url 'reset_password' %}">Změnit heslo</a></p>
<form action="{% url 'seminar_resitel_edit' %}" method="post">
<form action="{% url 'personalni_resitel_edit' %}" method="post">
{% include "personalni/udaje/udaje.html"%}
<input type="submit" value="Změnit">
</form>

View file

@ -14,7 +14,7 @@
<p><b>Tučně</b> popsaná pole jsou povinná.</p>
<form action="{% url 'seminar_prihlaska' %}" method="post">
<form action="{% url 'personalni_prihlaska' %}" method="post">
{% include "personalni/udaje/udaje.html" %}
<h4>
GDPR

View file

@ -4,7 +4,7 @@ from django.contrib.auth.models import User, Group
from django.contrib.admin.sites import AdminSite
from personalni.admin import OsobaAdmin, udelej_orgem
# Tohle bude peklo, až jednou ty modely fakt rozstřelíme… Možná vyrobit various.all_models, které půjdou importovat jako m? :-)
import seminar.models as m
import personalni.models as m
import logging
logger = logging.getLogger(__name__)

View file

@ -7,15 +7,15 @@ urlpatterns = [
path(
'org/rozcestnik/',
org_required(views.OrgoRozcestnikView.as_view()),
name='seminar_org_rozcestnik'
name='personalni_org_rozcestnik'
),
path('prihlaska/', views.prihlaskaView, name='seminar_prihlaska'),
path('prihlaska/', views.prihlaskaView, name='personalni_prihlaska'),
path(
'resitel/osobni-udaje/',
login_required(views.resitelEditView),
name='seminar_resitel_edit'
name='personalni_resitel_edit'
),
# Obecný view na profil -- orgům dá rozcestník, řešitelům jejich stránku

View file

@ -1,4 +1,3 @@
import seminar.models as m
from various.utils import bez_diakritiky_translate
import re
@ -7,9 +6,10 @@ 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 odevzdavatko.models import Reseni_Resitele
from .models import Osoba, Organizator, Skola, Resitel, Prijemce
@ -97,7 +97,7 @@ def merge_resitele(cilovy, zdrojovy):
# 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)
ct = 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')

View file

@ -16,8 +16,12 @@ from django.db import transaction
from django.http import HttpResponse
from django.utils import timezone
import seminar.models as s
import seminar.models as m
import personalni.models as m
from soustredeni.models import Soustredeni
from odevzdavatko.models import Hodnoceni
from tvorba.models import Clanek, Uloha, Tema
from various.models import Nastaveni
from .forms import PrihlaskaForm, ProfileEditForm, PoMaturiteProfileEditForm
from datetime import date
@ -91,22 +95,22 @@ class OrgoRozcestnikView(TemplateView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['posledni_soustredeni'] = s.Soustredeni.objects.order_by('-datum_konce').first()
nastaveni = s.Nastaveni.objects.first()
context['posledni_soustredeni'] = Soustredeni.objects.order_by('-datum_konce').first()
nastaveni = Nastaveni.objects.first()
aktualni_rocnik = nastaveni.aktualni_rocnik
context['posledni_cislo_url'] = nastaveni.aktualni_cislo.verejne_url()
# TODO možná chceme odkazovat na právě rozpracované číslo, a ne to poslední vydané
# pokud nechceme haluzit kód (= poradi) dalšího čísla, bude asi potřeba jít
# přes treenody (a dát si přitom pozor na MezicisloNode)
neobodovana_reseni = s.Hodnoceni.objects.filter(body__isnull=True)
reseni_mimo_cislo = s.Hodnoceni.objects.filter(deadline_body__isnull=True)
neobodovana_reseni = Hodnoceni.objects.filter(body__isnull=True)
reseni_mimo_cislo = Hodnoceni.objects.filter(deadline_body__isnull=True)
context['pocet_neobodovanych_reseni'] = neobodovana_reseni.count()
context['pocet_reseni_mimo_cislo'] = reseni_mimo_cislo.count()
u = self.request.user
os = s.Osoba.objects.get(user=u)
organizator = s.Organizator.objects.get(osoba=os)
os = m.Osoba.objects.get(user=u)
organizator = m.Organizator.objects.get(osoba=os)
context['muj_pocet_neobodovanych_reseni'] = neobodovana_reseni.filter(Q(problem__garant=organizator) | Q(problem__autor=organizator) | Q(problem__opravovatele__in=[organizator])).distinct().count()
context['muj_pocet_reseni_mimo_cislo'] = reseni_mimo_cislo.filter(Q(problem__garant=organizator) | Q(problem__autor=organizator) | Q(problem__opravovatele__in=[organizator])).count()
@ -116,11 +120,11 @@ class OrgoRozcestnikView(TemplateView):
context["pocty_neopravenych_reseni"] = [(it['problem__nazev'], it['cas'].date) for it in pocty_neopravenych_reseni.all()]
#FIXME: přidat stav='STAV_ZADANY'
temata = s.Tema.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]),
temata = Tema.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]),
rocnik=aktualni_rocnik).distinct()
ulohy = s.Uloha.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]),
ulohy = Uloha.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]),
cislo_zadani__rocnik=aktualni_rocnik).distinct()
clanky = s.Clanek.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]),
clanky = Clanek.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]),
cislo__rocnik=aktualni_rocnik).distinct()
context['temata'] = temata
@ -134,12 +138,12 @@ class OrgoRozcestnikView(TemplateView):
class ResitelView(LoginRequiredMixin,generic.DetailView):
model = s.Resitel
model = m.Resitel
template_name = 'personalni/profil/resitel.html'
def get_object(self, queryset=None):
print(self.request.user)
return s.Resitel.objects.get(osoba__user=self.request.user)
return m.Resitel.objects.get(osoba__user=self.request.user)
### Formulare
@ -157,10 +161,10 @@ def prihlaska_log_gdpr_safe(logger, gdpr_logger, msg, form_data):
@sensitive_post_parameters('jmeno', 'prijmeni', 'email', 'telefon', 'datum_narozeni', 'ulice', 'mesto', 'psc', 'skola')
def resitelEditView(request):
err_logger = logging.getLogger('seminar.prihlaska.problem')
err_logger = logging.getLogger('personalni.prihlaska.problem')
## Načtení objektů Osoba a Resitel patřících k aktuálně přihlášenému uživateli
u = request.user
osoba_edit = s.Osoba.objects.get(user=u)
osoba_edit = m.Osoba.objects.get(user=u)
if hasattr(osoba_edit,'resitel'):
resitel_edit = osoba_edit.resitel
else:
@ -195,7 +199,7 @@ def resitelEditView(request):
## Změny v osobě
fcd = form.cleaned_data
form_hash = hash(frozenset(fcd.items()))
form_logger = logging.getLogger('seminar.prihlaska.form')
form_logger = logging.getLogger('personalni.prihlaska.form')
form_logger.info("EDIT:" + str(fcd) + str(form_hash)) # TODO možná logovat jinak
osoba_edit.jmeno = fcd['jmeno']
osoba_edit.prijmeni = fcd['prijmeni']
@ -244,9 +248,9 @@ def resitelEditView(request):
@sensitive_post_parameters('jmeno', 'prijmeni', 'email', 'telefon', 'datum_narozeni', 'ulice', 'mesto', 'psc', 'skola', 'jak_se_dozvedeli')
def prihlaskaView(request):
generic_logger = logging.getLogger('seminar.prihlaska')
err_logger = logging.getLogger('seminar.prihlaska.problem')
form_logger = logging.getLogger('seminar.prihlaska.form')
generic_logger = logging.getLogger('personalni.prihlaska')
err_logger = logging.getLogger('personalni.prihlaska.problem')
form_logger = logging.getLogger('personalni.prihlaska.form')
if request.method == 'POST':
form = PrihlaskaForm(request.POST)
# TODO vyresit, co se bude v jakych situacich zobrazovat
@ -266,7 +270,7 @@ def prihlaskaView(request):
resitel_grp = Group.objects.filter(name__exact='resitel').first()
u.groups.add(resitel_grp)
o = s.Osoba(
o = m.Osoba(
jmeno = fcd['jmeno'],
prijmeni = fcd['prijmeni'],
osloveni = fcd['osloveni'],
@ -328,7 +332,7 @@ def prihlaskaView(request):
if kolize.count() > 1: # Jednu z nich jsme právě uložili
err_logger.warning(f'Zaregistrovala se osoba s kolizním jménem. ID osob: {[o.id for o in kolize]}')
r = s.Resitel(
r = m.Resitel(
prezdivka_resitele=fcd['prezdivka_resitele'] if fcd['prezdivka_resitele'] != "" else None,
rok_maturity = fcd['rok_maturity'],
zasilat = fcd['zasilat'],

View file

@ -5,7 +5,7 @@ from django.utils.safestring import mark_safe
from django.utils.html import escape
from .models import Prednaska, Seznam, STAV_NAVRH
from seminar.models import Soustredeni
from soustredeni.models import Soustredeni
class Seznam_PrednaskaInline(admin.TabularInline):

View file

@ -6,7 +6,8 @@ from django.db.models import Sum
from django.forms import Form
from prednasky.models import Prednaska, Hlasovani, Seznam, STAV_NAVRH
from seminar.models import Soustredeni, Osoba
from soustredeni.models import Soustredeni
from personalni.models import Osoba
def newPrednaska(request):
# hlasovani se vztahuje k nejnovejsimu soustredeni
@ -66,7 +67,7 @@ class SeznamListView(generic.ListView):
def get_queryset(self):
self.seznam = get_object_or_404(Seznam, id=self.kwargs["seznam"])
prednasky = Prednaska.objects.filter(seznamy=self.seznam).order_by(
'org__user__first_name', 'org__user__last_name'
'org__osoba__user__first_name', 'org__osoba__user__last_name'
)
return prednasky

View file

@ -14,7 +14,7 @@ Django<5.0
django-reversion # Version control na datech v databázi
django-countries # Políčko ve formu / field v modelu ohledně států
django-solo # Singleton model (speciálně Nastavení)
django-ckeditor # Editor htmlka (hlavně v adminu u flatpages)
django-ckeditor-5 # Editor htmlka (hlavně v adminu u flatpages)
django-cleanup # Uklízí media/ od smazaných „databázových“ souborů
django-taggit # Taggy v djangu (speciálně zaměření problémů)
django-autocomplete-light>=3.9.0 # Automatické doplňování (problémů, účastníků, …) ve formulářích

View file

@ -1,6 +1,4 @@
"""
Zde bývalo vše. Teď tu zbývají všechny modely a části webu jako archiv,
přehled orgů, aktuální (k aktuálnímu číslu) věci, titulka a jak řešit.
Také je tu generování testovacích (lokálních) dat.
"""
Zde bývalo vše. Teď tu zbývají migrace.
A kód pro `import seminar.models as m` pro ./manage.py shell.
"""

View file

@ -2,10 +2,11 @@ from __future__ import unicode_literals
from django.db import models, migrations
import django_countries.fields
import seminar.models
import django.utils.timezone
from django.conf import settings
from odevzdavatko.models import generate_filename
class Migration(migrations.Migration):
@ -75,7 +76,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(serialize=False, primary_key=True)),
('timestamp', models.DateTimeField(auto_now=True, verbose_name='vytvo\u0159eno')),
('soubor', models.FileField(upload_to=seminar.models.generate_filename, verbose_name='soubor')),
('soubor', models.FileField(upload_to=generate_filename, verbose_name='soubor')),
('poznamka', models.TextField(help_text='Neve\u0159ejn\xe1 pozn\xe1mka k p\u0159\xedloze \u0159e\u0161en\xed (plain text), nap\u0159. o p\u016fvodu', verbose_name='neve\u0159ejn\xe1 pozn\xe1mka', blank=True)),
],
options={

View file

@ -7,9 +7,12 @@ import django.db.models.deletion
import django.utils.timezone
import django_countries.fields
import imagekit.models.fields
import seminar.models
import taggit.managers
from soustredeni.models import generate_filename_konfera
from odevzdavatko.models import generate_filename
from tvorba.models import cislo_pdf_filename, cislo_png_filename
from datetime import date
from django.db.models import Q
from treenode.treelib import get_parent
@ -962,7 +965,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('timestamp', models.DateTimeField(auto_now=True, verbose_name='vytvořeno')),
('soubor', models.FileField(upload_to=seminar.models.generate_filename, verbose_name='soubor')),
('soubor', models.FileField(upload_to=generate_filename, verbose_name='soubor')),
('poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka k příloze řešení (plain text), např. o původu', verbose_name='neveřejná poznámka')),
('reseni', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='prilohy', to='seminar.Reseni', verbose_name='řešení')),
],
@ -1284,7 +1287,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='cislo',
name='pdf',
field=models.FileField(blank=True, help_text='Pdf čísla, které si mohou řešitelé stáhnout', null=True, upload_to=seminar.models.cislo_pdf_filename, verbose_name='pdf'),
field=models.FileField(blank=True, help_text='Pdf čísla, které si mohou řešitelé stáhnout', null=True, upload_to=cislo_pdf_filename, verbose_name='pdf'),
),
migrations.AlterField(
model_name='problem',
@ -1361,8 +1364,8 @@ class Migration(migrations.Migration):
('abstrakt', models.TextField(blank=True, help_text='Abstrakt konfery tak, jak byl uveden ve sborníku', verbose_name='abstrakt')),
('org_poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka ke konfeře(plain text)', verbose_name='neveřejná poznámka')),
('typ_prezentace', models.CharField(choices=[(b'veletrh', 'Veletrh (postery)'), (b'prezentace', 'Prezentace (přednáška)')], default=b'veletrh', max_length=16, verbose_name='typ prezentace')),
('prezentace', models.FileField(help_text='Prezentace nebo fotka posteru', upload_to=seminar.models.generate_filename_konfera, verbose_name='prezentace')),
('materialy', models.FileField(help_text='Další materiály ke konfeře zabalené do jednoho souboru', upload_to=seminar.models.generate_filename_konfera, verbose_name='materialy')),
('prezentace', models.FileField(help_text='Prezentace nebo fotka posteru', upload_to=generate_filename_konfera, verbose_name='prezentace')),
('materialy', models.FileField(help_text='Další materiály ke konfeře zabalené do jednoho souboru', upload_to=generate_filename_konfera, verbose_name='materialy')),
('organizator', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='konfery', to='seminar.Organizator', verbose_name='organizátor')),
('soustredeni', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='konfery', to='seminar.Soustredeni', verbose_name='soustředění')),
],
@ -1400,12 +1403,12 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='konfera',
name='materialy',
field=models.FileField(blank=True, help_text='Další materiály ke konfeře zabalené do jednoho souboru', upload_to=seminar.models.generate_filename_konfera, verbose_name='materialy'),
field=models.FileField(blank=True, help_text='Další materiály ke konfeře zabalené do jednoho souboru', upload_to=generate_filename_konfera, verbose_name='materialy'),
),
migrations.AlterField(
model_name='konfera',
name='prezentace',
field=models.FileField(blank=True, help_text='Prezentace nebo fotka posteru', upload_to=seminar.models.generate_filename_konfera, verbose_name='prezentace'),
field=models.FileField(blank=True, help_text='Prezentace nebo fotka posteru', upload_to=generate_filename_konfera, verbose_name='prezentace'),
),
migrations.AddField(
model_name='konfera',
@ -2648,12 +2651,12 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='cislo',
name='pdf',
field=models.FileField(blank=True, help_text='PDF čísla, které si mohou řešitelé stáhnout', null=True, upload_to=seminar.models.cislo_pdf_filename, verbose_name='pdf'),
field=models.FileField(blank=True, help_text='PDF čísla, které si mohou řešitelé stáhnout', null=True, upload_to=cislo_pdf_filename, verbose_name='pdf'),
),
migrations.AddField(
model_name='cislo',
name='titulka_nahled',
field=models.ImageField(blank=True, help_text='Obrázek titulní strany, generuje se automaticky', null=True, upload_to=seminar.models.cislo_png_filename, verbose_name='Obrázek titulní strany'),
field=models.ImageField(blank=True, help_text='Obrázek titulní strany, generuje se automaticky', null=True, upload_to=cislo_png_filename, verbose_name='Obrázek titulní strany'),
),
migrations.AlterField(
model_name='treenode',

View file

@ -1,10 +1,6 @@
from __future__ import unicode_literals
from django.db import models, migrations
import django_countries.fields
import seminar.models
import django.utils.timezone
from django.conf import settings
from django.db import migrations
CREATE_VIEWS="""
create view seminar_body_za_cislo as

View file

@ -1,10 +1,6 @@
from __future__ import unicode_literals
from django.db import models, migrations
import django_countries.fields
import seminar.models
import django.utils.timezone
from django.conf import settings
from django.db import migrations
CREATE_VIEWS="""
drop view seminar_body_k_cislu;

View file

@ -1,10 +1,6 @@
from __future__ import unicode_literals
from django.db import models, migrations
import django_countries.fields
import seminar.models
import django.utils.timezone
from django.conf import settings
from django.db import migrations
CREATE_VIEWS="""

View file

@ -1,7 +1,7 @@
from __future__ import unicode_literals
from django.db import models, migrations
import seminar.models
from tvorba.models import cislo_pdf_filename
class Migration(migrations.Migration):
@ -14,7 +14,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='cislo',
name='pdf',
field=models.FileField(help_text='Pdf \u010d\xedsla, kter\xe9 si mohou \u0159e\u0161itel\xe9 st\xe1hnout', upload_to=seminar.models.cislo_pdf_filename, null=True, verbose_name='pdf'),
field=models.FileField(help_text='Pdf \u010d\xedsla, kter\xe9 si mohou \u0159e\u0161itel\xe9 st\xe1hnout', upload_to=cislo_pdf_filename, null=True, verbose_name='pdf'),
preserve_default=True,
),
]

View file

@ -2,7 +2,8 @@ from __future__ import unicode_literals
from django.db import models, migrations
import django_countries.fields
import seminar.models
from tvorba.models import cislo_pdf_filename
class Migration(migrations.Migration):
@ -25,7 +26,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='cislo',
name='pdf',
field=models.FileField(help_text='Pdf \u010d\xedsla, kter\xe9 si mohou \u0159e\u0161itel\xe9 st\xe1hnout', upload_to=seminar.models.cislo_pdf_filename, null=True, verbose_name='pdf', blank=True),
field=models.FileField(help_text='Pdf \u010d\xedsla, kter\xe9 si mohou \u0159e\u0161itel\xe9 st\xe1hnout', upload_to=cislo_pdf_filename, null=True, verbose_name='pdf', blank=True),
preserve_default=True,
),
migrations.AlterField(

View file

@ -2,7 +2,8 @@ from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import seminar.models
from soustredeni.models import generate_filename_konfera
class Migration(migrations.Migration):
@ -21,8 +22,8 @@ class Migration(migrations.Migration):
('abstrakt', models.TextField(help_text='Abstrakt konfery tak, jak byl uveden ve sborn\xedku', verbose_name='abstrakt', blank=True)),
('org_poznamka', models.TextField(help_text='Neve\u0159ejn\xe1 pozn\xe1mka ke konfe\u0159e(plain text)', verbose_name='neve\u0159ejn\xe1 pozn\xe1mka', blank=True)),
('typ_prezentace', models.CharField(default=b'veletrh', max_length=16, verbose_name='typ prezentace', choices=[(b'veletrh', 'Veletrh (postery)'), (b'prezentace', 'Prezentace (p\u0159edn\xe1\u0161ka)')])),
('prezentace', models.FileField(help_text='Prezentace nebo fotka posteru', upload_to=seminar.models.generate_filename_konfera, verbose_name='prezentace')),
('materialy', models.FileField(help_text='Dal\u0161\xed materi\xe1ly ke konfe\u0159e zabalen\xe9 do jednoho souboru', upload_to=seminar.models.generate_filename_konfera, verbose_name='materialy')),
('prezentace', models.FileField(help_text='Prezentace nebo fotka posteru', upload_to=generate_filename_konfera, verbose_name='prezentace')),
('materialy', models.FileField(help_text='Dal\u0161\xed materi\xe1ly ke konfe\u0159e zabalen\xe9 do jednoho souboru', upload_to=generate_filename_konfera, verbose_name='materialy')),
('organizator', models.ForeignKey(related_name='konfery', on_delete=django.db.models.deletion.SET_NULL, verbose_name='organiz\xe1tor', to='seminar.Organizator', null=True)),
],
options={

View file

@ -2,7 +2,7 @@ from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import seminar.models
from soustredeni.models import generate_filename_konfera
class Migration(migrations.Migration):
@ -15,12 +15,12 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='konfera',
name='materialy',
field=models.FileField(help_text='Dal\u0161\xed materi\xe1ly ke konfe\u0159e zabalen\xe9 do jednoho souboru', upload_to=seminar.models.generate_filename_konfera, verbose_name='materialy', blank=True),
field=models.FileField(help_text='Dal\u0161\xed materi\xe1ly ke konfe\u0159e zabalen\xe9 do jednoho souboru', upload_to=generate_filename_konfera, verbose_name='materialy', blank=True),
),
migrations.AlterField(
model_name='konfera',
name='prezentace',
field=models.FileField(help_text='Prezentace nebo fotka posteru', upload_to=seminar.models.generate_filename_konfera, verbose_name='prezentace', blank=True),
field=models.FileField(help_text='Prezentace nebo fotka posteru', upload_to=generate_filename_konfera, verbose_name='prezentace', blank=True),
),
migrations.AlterField(
model_name='konfera',

View file

@ -1,7 +1,8 @@
# Generated by Django 2.2.9 on 2020-04-08 20:21
from django.db import migrations, models
import seminar.models
from tvorba.models import cislo_pdf_filename
class Migration(migrations.Migration):
@ -19,6 +20,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='cislo',
name='pdf',
field=models.FileField(blank=True, help_text='PDF čísla, které si mohou řešitelé stáhnout', null=True, upload_to=seminar.models.cislo_pdf_filename, verbose_name='pdf'),
field=models.FileField(blank=True, help_text='PDF čísla, které si mohou řešitelé stáhnout', null=True, upload_to=cislo_pdf_filename, verbose_name='pdf'),
),
]

View file

@ -1,7 +1,7 @@
# Generated by Django 2.2.12 on 2020-05-06 17:51
from django.db import migrations, models
import seminar.models
from tvorba.models import cislo_png_filename
class Migration(migrations.Migration):
@ -14,6 +14,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='cislo',
name='titulka_nahled',
field=models.ImageField(blank=True, help_text='Obrázek titulní strany, generuje se automaticky', null=True, upload_to=seminar.models.cislo_png_filename, verbose_name='Obrázek titulní strany'),
field=models.ImageField(blank=True, help_text='Obrázek titulní strany, generuje se automaticky', null=True, upload_to=cislo_png_filename, verbose_name='Obrázek titulní strany'),
),
]

View file

@ -1,7 +1,8 @@
# Generated by Django 2.2.24 on 2021-11-29 22:54
from django.db import migrations, models
import seminar.models.tvorba
import various.models
from tvorba.models import cislo_pdf_filename
class Migration(migrations.Migration):
@ -14,6 +15,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='cislo',
name='pdf',
field=models.FileField(blank=True, help_text='PDF čísla, které si mohou řešitelé stáhnout', null=True, storage=seminar.models.tvorba.OverwriteStorage(), upload_to=seminar.models.tvorba.cislo_pdf_filename, verbose_name='pdf'),
field=models.FileField(blank=True, help_text='PDF čísla, které si mohou řešitelé stáhnout', null=True, storage=various.models.OverwriteStorage(), upload_to=cislo_pdf_filename, verbose_name='pdf'),
),
]

View file

@ -5,7 +5,7 @@ from django.db import migrations, models
import django.db.models.deletion
from django.utils import timezone
import seminar.models as m
from tvorba.models import Deadline as mDeadline
def vytvor_deadliny(apps, schema_editor):
@ -16,7 +16,7 @@ def vytvor_deadliny(apps, schema_editor):
if cislo.rocnik.rocnik < 26:
Deadline.objects.create(
cislo=cislo,
typ=m.Deadline.TYP_CISLA,
typ=mDeadline.TYP_CISLA,
deadline=timezone.make_aware(datetime.datetime.combine(datetime.date(1994 + cislo.rocnik.rocnik, 6, int(cislo.poradi[0])), datetime.time.min)),
verejna_vysledkovka=cislo.verejna_vysledkovka,
)
@ -33,24 +33,24 @@ def vytvor_deadliny(apps, schema_editor):
if cislo.datum_deadline_soustredeni and cislo.datum_deadline_soustredeni == cislo.datum_preddeadline:
vytvor_deadline(
date=cislo.datum_deadline_soustredeni,
typ=m.Deadline.TYP_PRVNI_A_SOUS
typ=mDeadline.TYP_PRVNI_A_SOUS
)
else:
if cislo.datum_deadline_soustredeni:
vytvor_deadline(
date=cislo.datum_deadline_soustredeni,
typ=m.Deadline.TYP_SOUS
typ=mDeadline.TYP_SOUS
)
if cislo.datum_preddeadline:
vytvor_deadline(
date=cislo.datum_preddeadline,
typ=m.Deadline.TYP_PRVNI
typ=mDeadline.TYP_PRVNI
)
if cislo.datum_deadline:
vytvor_deadline(
date=cislo.datum_deadline,
typ=m.Deadline.TYP_CISLA
typ=mDeadline.TYP_CISLA
)

View file

@ -1,7 +1,7 @@
# Generated by Django 3.2.15 on 2022-10-09 10:14
from django.db import migrations
from seminar.models import Deadline
from tvorba.models import Deadline
def vrat_deadliny(apps, schema_editor):

View file

@ -1,7 +1,7 @@
# Generated by Django 3.2.15 on 2022-10-09 11:04
from django.db import migrations
from seminar.models import Deadline
from tvorba.models import Deadline
def vrat_verejnost(apps, schema_editor):

View file

@ -1,21 +0,0 @@
from .tvorba import *
from .base import *
from various.models import Nastaveni
from personalni.models import Organizator, Resitel, Skola, Prijemce, Osoba
from soustredeni.models import Soustredeni, Soustredeni_Ucastnici, Soustredeni_Organizatori, Konfera, Konfery_Ucastnici
from novinky.models import Novinky
from odevzdavatko.models import Reseni, PrilohaReseni, Reseni_Resitele, Hodnoceni
from tvorba.models import ZmrazenaVysledkovka, Deadline, Cislo, Rocnik, Pohadka, Tema, Problem, Problemy_Opravovatele, Uloha, Clanek
from treenode.models import UlohaVzorakNode, UlohaZadaniNode, CisloNode, TemaVCisleNode, OrgTextNode, Obrazek, RocnikNode, PohadkaNode, TextNode, MezicisloNode, ReseniNode, CastNode, Text, TreeNode
# Kvůli migr. 0041
from soustredeni.models import generate_filename_konfera
# migr. 0001
from odevzdavatko.models import generate_filename
# migr. 0031, 0032, 0081
from tvorba.models import cislo_pdf_filename
# migr. 0082
from tvorba.models import cislo_png_filename
# migr 0100 (hack)
import tvorba.models as tvorba

View file

@ -1,23 +0,0 @@
from django.urls import reverse
from django.db import models
class SeminarModelBase(models.Model):
class Meta:
abstract = True
def verejne(self):
return False
# def get_absolute_url(self):
# return "https://" + str(get_current_site(None)) + self.verejne_url()
def admin_url(self):
app_name = self._meta.app_label
model_name = self._meta.model_name
return reverse('admin:{}_{}_change'.format(app_name, model_name), args=(self.id, ))
# def verejne_url(self):
# return None

View file

@ -1,14 +0,0 @@
import os
import logging
from django.core.files.storage import FileSystemStorage
logger = logging.getLogger(__name__)
class OverwriteStorage(FileSystemStorage):
""" Varianta FileSystemStorage, která v případě, že soubor cílového
jména již existuje, ho smaže a místo něj uloží soubor nový"""
def get_available_name(self,name, max_length=None):
if self.exists(name):
os.remove(os.path.join(self.location,name))
return super().get_available_name(name,max_length)

View file

@ -1,7 +1,10 @@
import django.forms
from django.contrib import admin
from django.forms import widgets
from django.db import models
from django_ckeditor_5.widgets import CKEditor5Widget
import soustredeni.models as m
@ -35,9 +38,17 @@ class SoustredeniOrganizatoriInline(admin.TabularInline):
return qs.select_related('organizator', 'soustredeni')
class SoustredeniAdminForm(django.forms.ModelForm):
class Meta:
model = m.Soustredeni
widgets = {
'text': CKEditor5Widget,
}
fields = '__all__'
@admin.register(m.Soustredeni)
class SoustredeniAdmin(admin.ModelAdmin):
model = m.Soustredeni
form = SoustredeniAdminForm
inline_type = 'tabular'
inlines = [SoustredeniUcastniciInline, SoustredeniOrganizatoriInline]

View file

@ -0,0 +1,18 @@
# Generated by Django 4.2.13 on 2024-11-05 21:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('soustredeni', '0010_tvorba_post'),
]
operations = [
migrations.AddField(
model_name='soustredeni',
name='kontaktnicek_pdf',
field=models.FileField(blank=True, upload_to='kontaktnicky', verbose_name='kontaktníček'),
),
]

View file

@ -0,0 +1,23 @@
# Generated by Django 4.2.13 on 2024-11-05 21:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('soustredeni', '0011_soustredeni_kontaktnicek_pdf'),
]
operations = [
migrations.AddField(
model_name='soustredeni',
name='kontaktnicek_vcf',
field=models.FileField(blank=True, upload_to='kontaktnicky', verbose_name='kontaktníček vcf'),
),
migrations.AlterField(
model_name='soustredeni',
name='kontaktnicek_pdf',
field=models.FileField(blank=True, upload_to='kontaktnicky', verbose_name='kontaktníček pdf'),
),
]

View file

@ -0,0 +1,24 @@
# Generated by Django 4.2.16 on 2024-11-12 20:47
from django.db import migrations, models
import soustredeni.models
class Migration(migrations.Migration):
dependencies = [
('soustredeni', '0012_soustredeni_kontaktnicek_vcf_and_more'),
]
operations = [
migrations.AlterField(
model_name='soustredeni',
name='kontaktnicek_pdf',
field=models.FileField(blank=True, null=True, upload_to=soustredeni.models.generate_filemane_pdf, verbose_name='kontaktníček pdf'),
),
migrations.AlterField(
model_name='soustredeni',
name='kontaktnicek_vcf',
field=models.FileField(blank=True, null=True, upload_to=soustredeni.models.generate_filename_vcf, verbose_name='kontaktníček vcf'),
),
]

View file

@ -9,8 +9,25 @@ from django.conf import settings
from personalni.models import Resitel, Organizator
from seminar.models.base import SeminarModelBase
import tvorba.models as am
from various.models import SeminarModelBase
from tvorba.models import Rocnik, Problem, aux_generate_filename
import secrets
import string
from django.utils import timezone
def generate_filename_vcf(self, filename):
return generate_filename_kontaktnicek(self, filename, 'vcf')
def generate_filemane_pdf(self, filename):
return generate_filename_kontaktnicek(self, filename, 'pdf')
def generate_filename_kontaktnicek(self, filename, file_type):
# generate random string
length = 32
fname = timezone.now().strftime('%Y-%m-%d-%H_%M') + "-"
fname += ''.join(secrets.choice(string.ascii_uppercase + string.digits) for _ in range(length))
fname += '.' + file_type
return os.path.join(settings.SOUSTREDENI_KONTAKTNICKY_DIR, fname)
logger = logging.getLogger(__name__)
@ -27,7 +44,7 @@ class Soustredeni(SeminarModelBase):
# Interní ID
id = models.AutoField(primary_key = True)
rocnik = models.ForeignKey(am.Rocnik, verbose_name='ročník', related_name='soustredeni',
rocnik = models.ForeignKey(Rocnik, verbose_name='ročník', related_name='soustredeni',
on_delete=models.PROTECT)
datum_zacatku = models.DateField('datum začátku', blank=True, null=True,
@ -65,7 +82,9 @@ class Soustredeni(SeminarModelBase):
exportovat = models.BooleanField('export do AESOPa', db_column='exportovat', default=False,
help_text='Exportuje se jen podle tohoto flagu (ne veřejnosti)')
#using lambda to avoid circular import
kontaktnicek_vcf = models.FileField('kontaktníček vcf', upload_to=generate_filename_vcf, blank=True, null=True)
kontaktnicek_pdf = models.FileField('kontaktníček pdf', upload_to=generate_filemane_pdf, blank=True, null=True)
def __str__(self):
return '{} ({})'.format(self.misto, self.datum_zacatku)
@ -75,7 +94,7 @@ class Soustredeni(SeminarModelBase):
def verejne_url(self):
#return reverse('seminar_soustredeni', kwargs={'pk': self.id})
return reverse('seminar_seznam_soustredeni')
return reverse('soustredeni_seznam')
@reversion.register(ignore_duplicates=True)
@ -143,13 +162,13 @@ class Soustredeni_Organizatori(SeminarModelBase):
def generate_filename_konfera(self, filename):
return os.path.join(
settings.SEMINAR_KONFERY_DIR,
am.aux_generate_filename(self, filename)
aux_generate_filename(self, filename)
)
##
@reversion.register(ignore_duplicates=True)
class Konfera(am.Problem):
class Konfera(Problem):
class Meta:
db_table = 'seminar_konfera'
verbose_name = 'Konfera'

View file

@ -35,12 +35,24 @@
{% endif %}
{% endfor %}
{% endif %}
{% if soustredeni.kontaktnicek_pdf or soustredeni.kontaktnicek_vcf %}
{% for i in soustredeni.ucastnici.all %}
{% if i.osoba.user == user %}
<div class="mam-resitel-only">
{% if soustredeni.kontaktnicek_pdf %} <li><a href="../{{soustredeni.pk}}/kontaktnicek_pdf">kontaktnicek pdf</a></li>{% endif %}
{% if soustredeni.kontaktnicek_vcf %} <li><a href="../{{soustredeni.pk}}/kontaktnicek_vcf">kontaktnicek vcf</a></li>{% endif %}
</div>
{% endif %}
{% endfor %}
{% endif %}
</ul>
{% if user.je_org %}
<div class="mam-org-only">
<a href="../{{soustredeni.pk}}/fotogalerie/0/new/">Vytvořit novou fotogalerii</a><br>
<a href="../{{soustredeni.pk}}/obalky.pdf">Vygenerovat obálky pro účastníky</a><br>
<a href={% url 'seminar_soustredeni_abstrakty' soustredeni.pk %}>Vygenerovat účastníky a vedoucí do abstraktů</a><br>
<a href={% url 'soustredeni_abstrakty' soustredeni.pk %}>Vygenerovat účastníky a vedoucí do abstraktů</a><br>
Seznam účastníků -
<a href="../{{soustredeni.pk}}/seznam_ucastniku">HTML tabulka pro tisk</a>,
<a href="../{{soustredeni.pk}}/export_ucastniku">CSV</a>,
@ -73,6 +85,10 @@
Nic!
{% endfor %}
</p>
<ul>
{% if soustredeni.kontaktnicek_pdf %} <li><a href="../{{soustredeni.pk}}/kontaktnicek_pdf">kontaktnicek pdf</a></li>{% else %} <li>pdf kontaktníček není</li> {% endif %}
{% if soustredeni.kontaktnicek_vcf %} <li><a href="../{{soustredeni.pk}}/kontaktnicek_vcf">kontaktnicek vcf</a></li>{% else %} <li>vcf kontaktníček není</li> {% endif %}
</ul>
</div>
{% endif %}

View file

@ -29,7 +29,7 @@
}
{% for u in ucastnici %}
{% with o=u.osoba %}
{% with o=u.resitel.osoba %}
\stvrzenka{{o.jmeno|sloz}}{{o.prijmeni|sloz}}
{% endwith %}
{% endfor %}

View file

@ -6,7 +6,7 @@ from typing import Sequence
import lorem
from .models import Soustredeni, Konfera
import seminar.models as am # tvorba
from tvorba.models import Rocnik
import personalni.models as pm
logger = logging.getLogger(__name__)
@ -25,7 +25,7 @@ def gen_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(),
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']),

View file

@ -7,7 +7,7 @@ urlpatterns = [
path(
'probehlo/',
views.SoustredeniListView.as_view(),
name='seminar_seznam_soustredeni'
name='soustredeni_seznam'
),
path(
'<int:soustredeni>/',
@ -26,23 +26,33 @@ urlpatterns = [
),
path(
'export_ucastniku',
org_required(views.soustredeniUcastniciExportView),
org_required(views.SoustredeniUcastniciExportView.as_view()),
name='soustredeni_ucastnici_export'
),
path(
'stvrzenky.pdf',
org_required(views.soustredeniStvrzenkyView),
org_required(views.SoustredeniStvrzenkyView.as_view()),
name='soustredeni_ucastnici_stvrzenky'
),
path(
'obalky.pdf',
org_required(views.soustredeniObalkyView),
name='seminar_soustredeni_obalky'
name='soustredeni_obalky'
),
path(
'abstrakty',
org_required(views.SoustredeniAbstraktyView.as_view()),
name='seminar_soustredeni_abstrakty'
name='soustredeni_abstrakty'
),
path(
'kontaktnicek_pdf',
views.soustredeniKontaktnicekPdfView,
name='soustredeni_kontaktnicek_pdf'
),
path(
'kontaktnicek_vcf',
views.soustredeniKontaktnicekVcfView,
name='soustredeni_kontaktnicek_vcf'
),
path(
'fotogalerie/',
@ -50,5 +60,5 @@ urlpatterns = [
),
]
)
)
)
]

View file

@ -1,16 +1,14 @@
from django.shortcuts import get_object_or_404, render
from django.shortcuts import get_object_or_404
from django.http import HttpResponse
from django.views import generic
from django.contrib.staticfiles.finders import find
from django.http import Http404
from django.core.exceptions import PermissionDenied
import csv
import tempfile
import shutil
import subprocess
from pathlib import Path
import http
import personalni.views
import various.views.generic
from personalni.views import obalkyView
from .models import Soustredeni, Soustredeni_Ucastnici
from various.models import Nastaveni
@ -34,75 +32,112 @@ class SoustredeniListView(generic.ListView):
)
def soustredeniObalkyView(request, soustredeni):
soustredeni = get_object_or_404(Soustredeni, id=soustredeni)
return personalni.views.obalkyView(request, soustredeni.ucastnici.all())
class KonkretniSoustredeniMixin:
""" Přidá k View s parametrem `soustredeni` atribut `self.soustredeni` """
def setup(self, request, *args, **kwargs):
super().setup(request, *args, **kwargs)
soustredeni_id = self.kwargs["soustredeni"]
self.soustredeni = get_object_or_404(Soustredeni, id=soustredeni_id)
class SoustredeniUcastniciBaseView(generic.ListView):
class SoustredeniUcastniciBaseView(
KonkretniSoustredeniMixin,
various.views.generic.NeprazdnyListView,
):
"""
Slouží jako ListView účastníků soustředění
+ háže inteligentní chybu při soustředění bez účastníků
"""
model = Soustredeni_Ucastnici
if_prazdny_title = "K soustředění nejsou přidaní žádní účastníci"
if_prazdny_text = "K tebou zvolenému soustředění nejsou přidaní žádní účastníci, tedy není co zobrazit. Můžeš to zkusit změnit v adminu, případně se zeptej webařů :-)"
def get_queryset(self):
soustredeni = get_object_or_404(
Soustredeni,
pk=self.kwargs["soustredeni"]
)
return Soustredeni_Ucastnici.objects.filter(
soustredeni=soustredeni).select_related('resitel')
soustredeni=self.soustredeni).select_related('resitel', 'resitel__osoba')
# FIXME předělat jako ostatní (vyžaduje předělání `obalkyView`)
def soustredeniObalkyView(request, soustredeni):
soustredeni = get_object_or_404(Soustredeni, id=soustredeni)
return obalkyView(request, soustredeni.ucastnici.all())
class SoustredeniMailyUcastnikuView(SoustredeniUcastniciBaseView):
""" Seznam e-mailů řešitelů oddělených čárkami. """
model = Soustredeni_Ucastnici
template_name = 'soustredeni/maily_ucastniku.txt'
class SoustredeniUcastniciView(SoustredeniUcastniciBaseView):
""" HTML tabulka účastníků pro tisk. """
model = Soustredeni_Ucastnici
template_name = 'soustredeni/seznam_ucastniku.html'
def soustredeniUcastniciExportView(request, soustredeni):
soustredeni = get_object_or_404(Soustredeni, id=soustredeni)
ucastnici = soustredeni.ucastnici.all()
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="ucastnici.csv"'
class SoustredeniUcastniciExportView(SoustredeniUcastniciBaseView):
""" CSV tabulka účastníků. """
def render(self, request, *args, **kwargs):
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="ucastnici.csv"'
writer = csv.writer(response)
writer.writerow(["jmeno", "prijmeni", "rok_maturity", "telefon", "email", "ulice", "mesto", "psc","stat"])
for u in ucastnici:
o = u.osoba
writer.writerow([o.jmeno, o.prijmeni, str(u.rok_maturity), o.telefon, o.email, o.ulice, o.mesto, o.psc, o.stat.name])
return response
writer = csv.writer(response)
writer.writerow(["jmeno", "prijmeni", "rok_maturity", "telefon", "email", "ulice", "mesto", "psc","stat"])
for u in self.object_list:
o = u.resitel.osoba
writer.writerow([o.jmeno, o.prijmeni, str(u.resitel.rok_maturity), o.telefon, o.email, o.ulice, o.mesto, o.psc, o.stat.name])
return response
def soustredeniStvrzenkyView(request, soustredeni):
soustredeni = get_object_or_404(Soustredeni, id=soustredeni)
ucastnici = soustredeni.ucastnici.all()
if ucastnici.count() == 0:
return HttpResponse(
render(request, 'universal.html', {
'title': 'Není pro koho vyrobit stvrzenky.',
'text': 'Právě ses pokusil/a vygenerovat stvrzenky pro prázdnou množinu lidí. Můžeš to zkusit změnit, případně se zeptej webařů :-)',
}),
status=http.HTTPStatus.NOT_FOUND,
)
castka = Nastaveni.get_solo().cena_sous
tex = render(request, 'soustredeni/stvrzenky.tex', {'ucastnici': ucastnici, 'soustredeni': soustredeni, 'castka': castka}).content
with tempfile.TemporaryDirectory() as tempdirfn:
tempdir = Path(tempdirfn)
with open(tempdir / "stvrzenky.tex", "w") as texfile:
texfile.write(tex.decode())
class SoustredeniStvrzenkyView(
various.views.generic.TeXResponseMixin,
SoustredeniUcastniciBaseView,
):
template_name = 'soustredeni/stvrzenky.tex'
dalsi_potrebne_soubory = [find('soustredeni/logomm.pdf')]
shutil.copy(find('soustredeni/logomm.pdf'), tempdir)
subprocess.call(["pdflatex", "stvrzenky.tex"], cwd = tempdir, stdout=subprocess.DEVNULL)
if_prazdny_title = "Není pro koho vyrobit stvrzenky."
if_prazdny_text = "Právě ses pokusil/a vygenerovat stvrzenky pro prázdnou množinu lidí. Můžeš to zkusit změnit, případně se zeptej webařů :-)"
with open(tempdir / "stvrzenky.pdf", "rb") as pdffile:
response = HttpResponse(pdffile.read(), content_type='application/pdf')
return response
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["castka"] = Nastaveni.get_solo().cena_sous
context["soustredeni"] = self.soustredeni
context["ucastnici"] = self.object_list
return context
class SoustredeniAbstraktyView(generic.DetailView):
model = Soustredeni
template_name = 'soustredeni/export_do_abstraktu.html'
pk_url_kwarg = 'soustredeni' # v url bude <int:soustredeni> místo defaultně požadovaného <int:pk>
# Kontaktníčky
def soustredeniKontaktnicekPdfView(request, soustredeni):
return soustredeniKontaktnicekView(request, soustredeni, "pdf")
def soustredeniKontaktnicekVcfView(request, soustredeni):
return soustredeniKontaktnicekView(request, soustredeni, "vcf")
def soustredeniKontaktnicekView(request, soustredeni, typ):
soustredeni = get_object_or_404(Soustredeni, id=soustredeni)
# nebyl jsi tam, nebo nejsi org
if (not request.user in [u.osoba.user for u in soustredeni.ucastnici.all()]) and not request.user.je_org:
raise PermissionDenied()
kontaktnicky = {
'pdf': (soustredeni.kontaktnicek_pdf, 'applcation/pdf', 'rb'),
'vcf': (soustredeni.kontaktnicek_vcf, 'text/vcard', 'rb'),
}
try:
field, mime, otevreni = kontaktnicky[typ]
except KeyError as e:
raise ValueError("Neznámý typ kontaktníčku") from e
# není k dispozici
if not field:
raise Http404()
with open(field.path, otevreni) as kontaktnicek:
response = HttpResponse(kontaktnicek.read(), content_type=mime)
response['Content-Disposition'] = 'attachment; filename="kontaktnicek.{}"'.format(typ)
return response

View file

@ -4,25 +4,27 @@ from django.forms import widgets
from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter
import seminar.models as m
from .models import TreeNode, RocnikNode, CisloNode, MezicisloNode, TemaVCisleNode, UlohaZadaniNode, PohadkaNode, UlohaVzorakNode, TextNode, CastNode, OrgTextNode, ReseniNode
from .models import Text, Obrazek
# Polymorfismus pro stromy
# TODO: Inlines podle https://django-polymorphic.readthedocs.io/en/stable/admin.html
@admin.register(m.TreeNode)
@admin.register(TreeNode)
class TreeNodeAdmin(PolymorphicParentModelAdmin):
base_model = m.TreeNode
base_model = TreeNode
child_models = [
m.RocnikNode,
m.CisloNode,
m.MezicisloNode,
m.TemaVCisleNode,
m.UlohaZadaniNode,
m.PohadkaNode,
m.UlohaVzorakNode,
m.TextNode,
m.CastNode,
m.OrgTextNode,
RocnikNode,
CisloNode,
MezicisloNode,
TemaVCisleNode,
UlohaZadaniNode,
PohadkaNode,
UlohaVzorakNode,
TextNode,
CastNode,
OrgTextNode,
ReseniNode,
]
actions = ['aktualizuj_nazvy']
@ -36,64 +38,68 @@ class TreeNodeAdmin(PolymorphicParentModelAdmin):
self.message_user(request, "Názvy aktualizovány.")
aktualizuj_nazvy.short_description = "Aktualizuj vybraným TreeNodům názvy"
@admin.register(m.RocnikNode)
@admin.register(RocnikNode)
class RocnikNodeAdmin(PolymorphicChildModelAdmin):
base_model = m.RocnikNode
base_model = RocnikNode
show_in_index = True
@admin.register(m.CisloNode)
@admin.register(CisloNode)
class CisloNodeAdmin(PolymorphicChildModelAdmin):
base_model = m.CisloNode
base_model = CisloNode
show_in_index = True
@admin.register(m.MezicisloNode)
@admin.register(MezicisloNode)
class MezicisloNodeAdmin(PolymorphicChildModelAdmin):
base_model = m.MezicisloNode
base_model = MezicisloNode
show_in_index = True
@admin.register(m.TemaVCisleNode)
@admin.register(TemaVCisleNode)
class TemaVCisleNodeAdmin(PolymorphicChildModelAdmin):
base_model = m.TemaVCisleNode
base_model = TemaVCisleNode
show_in_index = True
@admin.register(m.UlohaZadaniNode)
@admin.register(UlohaZadaniNode)
class UlohaZadaniNodeAdmin(PolymorphicChildModelAdmin):
base_model = m.UlohaZadaniNode
base_model = UlohaZadaniNode
show_in_index = True
@admin.register(m.PohadkaNode)
@admin.register(PohadkaNode)
class PohadkaNodeAdmin(PolymorphicChildModelAdmin):
base_model = m.PohadkaNode
base_model = PohadkaNode
show_in_index = True
@admin.register(m.UlohaVzorakNode)
@admin.register(UlohaVzorakNode)
class UlohaVzorakNodeAdmin(PolymorphicChildModelAdmin):
base_model = m.UlohaVzorakNode
base_model = UlohaVzorakNode
show_in_index = True
@admin.register(m.TextNode)
@admin.register(TextNode)
class TextNodeAdmin(PolymorphicChildModelAdmin):
base_model = m.TextNode
base_model = TextNode
show_in_index = True
@admin.register(m.CastNode)
@admin.register(CastNode)
class TextNodeAdmin(PolymorphicChildModelAdmin):
base_model = m.CastNode
base_model = CastNode
show_in_index = True
fields = ('nadpis',)
@admin.register(m.OrgTextNode)
@admin.register(OrgTextNode)
class TextNodeAdmin(PolymorphicChildModelAdmin):
base_model = m.OrgTextNode
base_model = OrgTextNode
show_in_index = True
@admin.register(ReseniNode)
class ReseniNodeAdmin(PolymorphicChildModelAdmin):
base_model = ReseniNode
show_in_index = True
class TextAdminInline(admin.TabularInline):
model = m.Text
model = Text
formfield_overrides = {
models.TextField: {'widget': widgets.TextInput}
}
exclude = ['text_zkraceny_set', 'text_zkraceny']
admin.site.register(m.Text)
admin.site.register(m.Obrazek)
admin.site.register(Text)
admin.site.register(Obrazek)

View file

@ -1,5 +1,5 @@
from django import forms
import seminar.models as m
from .models import Obrazek
# pro přidání políčka do formuláře je potřeba
# - mít v modelu tu položku, kterou chci upravovat
@ -10,5 +10,5 @@ import seminar.models as m
class NahrajObrazekKTreeNoduForm(forms.ModelForm):
class Meta:
model = m.Obrazek
model = Obrazek
fields = ('na_web',)

View file

@ -8,7 +8,7 @@ from unidecode import unidecode # Používám pro získání ID odkazu (ještě
from polymorphic.models import PolymorphicModel
from seminar.models import SeminarModelBase
from various.models import SeminarModelBase
from personalni.models import Organizator
from odevzdavatko.models import Reseni
@ -347,4 +347,3 @@ class Obrazek(SeminarModelBase):
# TODO placement hint - chci ho tady / pred textem / za textem

View file

@ -1,7 +1,11 @@
from rest_framework import serializers
from rest_polymorphic.serializers import PolymorphicSerializer
import seminar.models as m
from odevzdavatko.models import Reseni
from tvorba.models import Problem, Uloha
from .models import RocnikNode, CisloNode, MezicisloNode, TemaVCisleNode, OrgTextNode, PohadkaNode, TextNode, TreeNode, CastNode, UlohaZadaniNode, UlohaVzorakNode, ReseniNode
from .models import Text
from treenode import treelib
DEFAULT_NODE_DEPTH = 2
@ -9,57 +13,57 @@ DEFAULT_NODE_DEPTH = 2
class TextSerializer(serializers.ModelSerializer):
class Meta:
model = m.Text
model = Text
fields = '__all__'
class ProblemSerializer(serializers.ModelSerializer):
class Meta:
model = m.Problem
model = Problem
fields = '__all__'
class UlohaSerializer(serializers.ModelSerializer):
class Meta:
model = m.Uloha
model = Uloha
fields = '__all__'
class ReseniSerializer(serializers.ModelSerializer):
class Meta:
model = m.Reseni
model = Reseni
fields = '__all__'
class RocnikNodeSerializer(serializers.ModelSerializer):
class Meta:
model = m.RocnikNode
model = RocnikNode
fields = '__all__'
depth = DEFAULT_NODE_DEPTH
class CisloNodeSerializer(serializers.ModelSerializer):
class Meta:
model = m.CisloNode
model = CisloNode
fields = '__all__'
depth = DEFAULT_NODE_DEPTH
class MezicisloNodeSerializer(serializers.ModelSerializer):
class Meta:
model = m.MezicisloNode
model = MezicisloNode
fields = '__all__'
depth = DEFAULT_NODE_DEPTH
class TemaVCisleNodeSerializer(serializers.ModelSerializer):
class Meta:
model = m.TemaVCisleNode
model = TemaVCisleNode
fields = '__all__'
depth = DEFAULT_NODE_DEPTH
class OrgTextNodeSerializer(serializers.ModelSerializer):
class Meta:
model = m.OrgTextNode
model = OrgTextNode
fields = '__all__'
depth = DEFAULT_NODE_DEPTH
class PohadkaNodeSerializer(serializers.ModelSerializer):
class Meta:
model = m.PohadkaNode
model = PohadkaNode
fields = '__all__'
depth = DEFAULT_NODE_DEPTH
@ -67,7 +71,7 @@ class TextNodeSerializer(serializers.ModelSerializer):
text = TextSerializer()
class Meta:
model = m.TextNode
model = TextNode
fields = ('id','text','polymorphic_ctype')
depth = DEFAULT_NODE_DEPTH
@ -80,7 +84,7 @@ class TextNodeWriteSerializer(serializers.ModelSerializer):
return node
class Meta:
model = m.TextNode
model = TextNode
fields = ('id','text')
depth = DEFAULT_NODE_DEPTH
@ -93,26 +97,26 @@ class TextNodeCreateSerializer(serializers.ModelSerializer):
temp_text = validated_data.pop('text')
where = validated_data.pop('where')
refnode_id = validated_data.pop('refnode')
refnode = m.TreeNode.objects.get(pk=refnode_id)
text = m.Text.objects.create(**temp_text)
refnode = TreeNode.objects.get(pk=refnode_id)
text = Text.objects.create(**temp_text)
if where == 'syn':
node = treelib.create_child(refnode,m.TextNode,text=text)
node = treelib.create_child(refnode,TextNode,text=text)
elif where == 'za':
node = treelib.create_node_after(refnode,m.TextNode,text=text)
node = treelib.create_node_after(refnode,TextNode,text=text)
elif where == 'pred':
node = treelib.create_node_before(refnode,m.TextNode,text=text)
node = treelib.create_node_before(refnode,TextNode,text=text)
node.where = None
node.refnode = None
return node
class Meta:
model = m.TextNode
model = TextNode
fields = ('text','where','refnode')
depth = DEFAULT_NODE_DEPTH
class CastNodeSerializer(serializers.ModelSerializer):
class Meta:
model = m.CastNode
model = CastNode
fields = '__all__'
depth = DEFAULT_NODE_DEPTH
@ -124,25 +128,25 @@ class CastNodeCreateSerializer(serializers.ModelSerializer):
temp_nadpis = validated_data.pop('nadpis')
where = validated_data.pop('where')
refnode_id = validated_data.pop('refnode')
refnode = m.TreeNode.objects.get(pk=refnode_id)
refnode = TreeNode.objects.get(pk=refnode_id)
if where == 'syn':
node = treelib.create_child(refnode,m.CastNode,nadpis=temp_nadpis)
node = treelib.create_child(refnode,CastNode,nadpis=temp_nadpis)
elif where == 'za':
node = treelib.create_node_after(refnode,m.CastNode,nadpis=temp_nadpis)
node = treelib.create_node_after(refnode,CastNode,nadpis=temp_nadpis)
elif where == 'pred':
node = treelib.create_node_before(refnode,m.CastNode,nadpis=temp_nadpis)
node = treelib.create_node_before(refnode,CastNode,nadpis=temp_nadpis)
node.where = None
node.refnode = None
return node
class Meta:
model = m.CastNode
model = CastNode
fields = ('nadpis','where','refnode')
depth = DEFAULT_NODE_DEPTH
class UlohaZadaniNodeSerializer(serializers.ModelSerializer):
class Meta:
model = m.UlohaZadaniNode
model = UlohaZadaniNode
fields = '__all__'
depth = DEFAULT_NODE_DEPTH
@ -157,7 +161,7 @@ class UlohaZadaniNodeWriteSerializer(serializers.ModelSerializer):
return node
class Meta:
model = m.TextNode
model = TextNode
fields = ('id','uloha')
depth = DEFAULT_NODE_DEPTH
@ -171,28 +175,28 @@ class UlohaZadaniNodeCreateSerializer(serializers.ModelSerializer):
temp_uloha = validated_data.pop('uloha')
where = validated_data.pop('where')
refnode_id = validated_data.pop('refnode')
refnode = m.TreeNode.objects.get(pk=refnode_id)
refnode = TreeNode.objects.get(pk=refnode_id)
# Z cesty ke koreni stromu zjistime, v jakem jsme tematu a v jakem cisle
cislo = None
tema = None
travelnode = refnode
while travelnode is not None:
if isinstance(travelnode, m.TemaVCisleNode):
if isinstance(travelnode, TemaVCisleNode):
tema = travelnode.tema
if isinstance(travelnode, m.CisloNode):
if isinstance(travelnode, CisloNode):
cislo = travelnode.cislo
travelnode = treelib.get_parent(travelnode)
# Vyrobime ulohu
uloha = m.Uloha.objects.create(cislo_zadani=cislo, nadproblem = tema, **temp_uloha)
uloha = Uloha.objects.create(cislo_zadani=cislo, nadproblem = tema, **temp_uloha)
# A vyrobime UlohaZadaniNode
if where == 'syn':
node = treelib.create_child(refnode,m.UlohaZadaniNode,uloha = uloha)
node = treelib.create_child(refnode,UlohaZadaniNode,uloha = uloha)
elif where == 'za':
node = treelib.create_node_after(refnode,m.UlohaZadaniNode,uloha = uloha)
node = treelib.create_node_after(refnode,UlohaZadaniNode,uloha = uloha)
elif where == 'pred':
node = treelib.create_node_before(refnode,m.UlohaZadaniNode,uloha = uloha)
node = treelib.create_node_before(refnode,UlohaZadaniNode,uloha = uloha)
node.where = None
node.refnode = None
node.max_body = None
@ -200,21 +204,21 @@ class UlohaZadaniNodeCreateSerializer(serializers.ModelSerializer):
return node
class Meta:
model = m.UlohaZadaniNode
model = UlohaZadaniNode
fields = ('uloha','where','refnode')
depth = DEFAULT_NODE_DEPTH
class UlohaVzorakNodeSerializer(serializers.ModelSerializer):
class Meta:
model = m.UlohaVzorakNode
model = UlohaVzorakNode
fields = '__all__'
depth = DEFAULT_NODE_DEPTH
class UlohaVzorakNodeWriteSerializer(serializers.ModelSerializer):
uloha = serializers.PrimaryKeyRelatedField(queryset=m.Uloha.objects.all(), many=False, read_only=False)
uloha = serializers.PrimaryKeyRelatedField(queryset=Uloha.objects.all(), many=False, read_only=False)
class Meta:
model = m.UlohaVzorakNode
model = UlohaVzorakNode
fields = ('id','uloha')
depth = DEFAULT_NODE_DEPTH
@ -226,17 +230,17 @@ class UlohaVzorakNodeCreateSerializer(serializers.ModelSerializer):
def create(self, validated_data):
uloha_id = validated_data.pop('uloha_id')
uloha = m.Uloha.objects.get(pk=uloha_id)
uloha = Uloha.objects.get(pk=uloha_id)
where = validated_data.pop('where')
refnode_id = validated_data.pop('refnode')
refnode = m.TreeNode.objects.get(pk=refnode_id)
refnode = TreeNode.objects.get(pk=refnode_id)
if where == 'syn':
node = treelib.create_child(refnode,m.UlohaVzorakNode,uloha = uloha)
node = treelib.create_child(refnode,UlohaVzorakNode,uloha = uloha)
elif where == 'za':
node = treelib.create_node_after(refnode,m.UlohaVzorakNode,uloha = uloha)
node = treelib.create_node_after(refnode,UlohaVzorakNode,uloha = uloha)
elif where == 'pred':
node = treelib.create_node_before(refnode,m.UlohaVzorakNode,uloha = uloha)
node = treelib.create_node_before(refnode,UlohaVzorakNode,uloha = uloha)
node.refnode = None
node.where = None
node.uloha_id = None
@ -244,7 +248,7 @@ class UlohaVzorakNodeCreateSerializer(serializers.ModelSerializer):
return node
class Meta:
model = m.UlohaVzorakNode
model = UlohaVzorakNode
fields = ('refnode', 'uloha_id', 'where')
depth = DEFAULT_NODE_DEPTH
@ -253,15 +257,15 @@ class UlohaVzorakNodeCreateSerializer(serializers.ModelSerializer):
class ReseniNodeSerializer(serializers.ModelSerializer):
class Meta:
model = m.ReseniNode
model = ReseniNode
fields = '__all__'
depth = DEFAULT_NODE_DEPTH
class ReseniNodeWriteSerializer(serializers.ModelSerializer):
reseni = serializers.PrimaryKeyRelatedField(queryset=m.Reseni.objects.all(), many=False, read_only=False)
reseni = serializers.PrimaryKeyRelatedField(queryset=Reseni.objects.all(), many=False, read_only=False)
class Meta:
model = m.ReseniNode
model = ReseniNode
fields = ('id','reseni')
depth = DEFAULT_NODE_DEPTH
@ -273,41 +277,41 @@ class ReseniNodeCreateSerializer(serializers.ModelSerializer):
def create(self,validated_data):
# text_zadani = validated_data.pop('text_zadani')
reseni_id = validated_data.pop('reseni_id')
reseni = m.Reseni.objects.get(pk=reseni_id)
reseni = Reseni.objects.get(pk=reseni_id)
where = validated_data.pop('where')
refnode_id = validated_data.pop('refnode')
refnode = m.TreeNode.objects.get(pk=refnode_id)
refnode = TreeNode.objects.get(pk=refnode_id)
# A vyrobime UlohaZadaniNode
if where == 'syn':
node = treelib.create_child(refnode,m.ReseniNode,reseni = reseni)
node = treelib.create_child(refnode,ReseniNode,reseni = reseni)
elif where == 'za':
node = treelib.create_node_after(refnode,m.ReseniNode,reseni = reseni)
node = treelib.create_node_after(refnode,ReseniNode,reseni = reseni)
elif where == 'pred':
node = treelib.create_node_before(refnode,m.ReseniNode,reseni = reseni)
node = treelib.create_node_before(refnode,ReseniNode,reseni = reseni)
node.where = None
node.refnode = None
node.reseni_id = None
return node
class Meta:
model = m.ReseniNode
model = ReseniNode
fields = ('reseni_id','where','refnode')
depth = DEFAULT_NODE_DEPTH
class TreeNodeSerializer(PolymorphicSerializer):
model_serializer_mapping = {
m.RocnikNode: RocnikNodeSerializer,
m.CisloNode: CisloNodeSerializer,
m.MezicisloNode: MezicisloNodeSerializer,
m.TemaVCisleNode: TemaVCisleNodeSerializer,
m.OrgTextNode: OrgTextNodeSerializer,
m.UlohaZadaniNode: UlohaZadaniNodeSerializer,
m.UlohaVzorakNode: UlohaVzorakNodeSerializer,
m.PohadkaNode: PohadkaNodeSerializer,
m.TextNode: TextNodeSerializer,
m.CastNode: CastNodeSerializer,
m.ReseniNode: ReseniNodeSerializer,
RocnikNode: RocnikNodeSerializer,
CisloNode: CisloNodeSerializer,
MezicisloNode: MezicisloNodeSerializer,
TemaVCisleNode: TemaVCisleNodeSerializer,
OrgTextNode: OrgTextNodeSerializer,
UlohaZadaniNode: UlohaZadaniNodeSerializer,
UlohaVzorakNode: UlohaVzorakNodeSerializer,
PohadkaNode: PohadkaNodeSerializer,
TextNode: TextNodeSerializer,
CastNode: CastNodeSerializer,
ReseniNode: ReseniNodeSerializer,
}

View file

@ -25,20 +25,20 @@
<button>Zvyš úroveň nadpisu</button> - nejsou testovací data
</div>
{% endif %}
{% include "seminar/treenode_name.html" %}
{% include "treenode/treenode_name.html" %}
{%if obj.children %}
<div class="borderized children">
{% with kam="před" kam_slug="syn" %} {% include "seminar/treenode_add_stub.html" %} {% endwith %}
{% with kam="před" kam_slug="syn" %} {% include "treenode/treenode_add_stub.html" %} {% endwith %}
{%for ch in obj.children %}
{# ----------- Vypisujeme podstrom ----------#}
{%with obj=ch depth=depth|add:"1" %} {%include "seminar/treenode_recursive.html" %} {%endwith%}
{%with obj=ch depth=depth|add:"1" %} {%include "treenode/treenode_recursive.html" %} {%endwith%}
{# ----------- Přidáváme mezi syny / za posledního -------- #}
{% if forloop.last %}
{% with kam="za" kam_slug="za" obj=ch %} {% include "seminar/treenode_add_stub.html" %} {% endwith %}
{% with kam="za" kam_slug="za" obj=ch %} {% include "treenode/treenode_add_stub.html" %} {% endwith %}
{% else %}
{% with kam="mezi" obj=ch kam_slug="za" %} {% include "seminar/treenode_add_stub.html" %} {% endwith %}
{% with kam="mezi" obj=ch kam_slug="za" %} {% include "treenode/treenode_add_stub.html" %} {% endwith %}
{% endif %}
{# ----------- Prohazujeme sousedy ----------#}
<div class="pink">
@ -50,6 +50,6 @@
</div>
{% else %}
{# ----------- Přidáváme prvního syna ----------#}
{% with kam="jako syna" kam_slug="syn" %} {% include "seminar/treenode_add_stub.html" %} {% endwith %}
{% with kam="jako syna" kam_slug="syn" %} {% include "treenode/treenode_add_stub.html" %} {% endwith %}
{%endif%}
</div>

View file

@ -1,6 +1,6 @@
from django import template
from enum import Enum
import seminar.models as m
from .models import RocnikNode, CisloNode, CastNode, TextNode, TemaVCisleNode, UlohaVzorakNode, UlohaZadaniNode, PohadkaNode
register = template.Library()
@ -11,8 +11,8 @@ def nodeType(value):
if isinstance(value,CastNode): return "Část"
if isinstance(value,TextNode): return "Text"
if isinstance(value,TemaVCisleNode): return "Téma v čísle"
if isinstance(value,KonferaNode): return "Konfera"
if isinstance(value,ClanekNode): return "Článek"
# if isinstance(value,KonferaNode): return "Konfera" # FIXME neexistuje
# if isinstance(value,ClanekNode): return "Článek" # FIXME neexistuje
if isinstance(value,UlohaVzorakNode): return "Vzorák"
if isinstance(value,UlohaZadaniNode): return "Zadání úlohy"
if isinstance(value,PohadkaNode): return "Pohádka"
@ -22,53 +22,57 @@ def nodeType(value):
@register.filter
def isRocnik(value):
return isinstance(value, m.RocnikNode)
return isinstance(value, RocnikNode)
@register.filter
def isCislo(value):
return isinstance(value, m.CisloNode)
return isinstance(value, CisloNode)
@register.filter
def isCast(value):
return isinstance(value, m.CastNode)
return isinstance(value, CastNode)
@register.filter
def isText(value):
return isinstance(value, m.TextNode)
return isinstance(value, TextNode)
@register.filter
def isTemaVCisle(value):
return isinstance(value, m.TemaVCisleNode)
return isinstance(value, TemaVCisleNode)
@register.filter
def isKonfera(value):
return isinstance(value, m.KonferaNode)
# FIXME neexistuje
# return isinstance(value, KonferaNode)
return False
@register.filter
def isClanek(value):
return isinstance(value, m.ClanekNode)
# FIXME neexistuje
# return isinstance(value, ClanekNode)
return False
@register.filter
def isUlohaVzorak(value):
return isinstance(value, m.UlohaVzorakNode)
return isinstance(value, UlohaVzorakNode)
@register.filter
def isUlohaZadani(value):
return isinstance(value, m.UlohaZadaniNode)
return isinstance(value, UlohaZadaniNode)
@register.filter
def isPohadka(value):
return isinstance(value, m.PohadkaNode)
return isinstance(value, PohadkaNode)
@register.filter
def isReseni(value):
return False
# return isinstance(value, m.OtisteneReseniNode)
# return isinstance(value, OtisteneReseniNode)
@register.filter
def isOrgText(value):
return False
# return isinstance(value, m.OrgTextNode)
# return isinstance(value, OrgTextNode)
###

View file

@ -1,16 +1,16 @@
from django.test import TestCase
import treenode.treelib as tl
import seminar.models as m
from .models import CastNode
class SimpleTreeLibTests(TestCase):
def setUp(self):
# Vyrobíme pár nějakých Nodů
self.root = m.CastNode(root=None, first_child=None, succ=None, nadpis="Root")
self.root = CastNode(root=None, first_child=None, succ=None, nadpis="Root")
self.root.save()
self.some_node = m.CastNode(root=self.root, first_child=None, succ=None, nadpis="Přetržené")
self.other_node = m.CastNode(root=self.root, first_child=None, succ=None, nadpis="Dítě")
self.some_orphan = m.CastNode(root=None, first_child=None, succ=None, nadpis="Ošklivé")
self.other_orphan = m.CastNode(root=None, first_child=None, succ=None, nadpis="Káčátko")
self.some_node = CastNode(root=self.root, first_child=None, succ=None, nadpis="Přetržené")
self.other_node = CastNode(root=self.root, first_child=None, succ=None, nadpis="Dítě")
self.some_orphan = CastNode(root=None, first_child=None, succ=None, nadpis="Ošklivé")
self.other_orphan = CastNode(root=None, first_child=None, succ=None, nadpis="Káčátko")
# Trochu je pospojujeme
self.root.first_child = self.some_node

View file

@ -238,7 +238,7 @@ class TreeLibError(RuntimeError):
# Editace stromu:
def create_node_after(predecessor, type, **kwargs):
from seminar.models import TreeNode
from .models import TreeNode
if predecessor is None:
raise TreeLibError("Nelze vyrábět sirotky! (predecessor=None)")
if not issubclass(type, TreeNode):
@ -255,7 +255,7 @@ def create_node_after(predecessor, type, **kwargs):
# Vyrábí prvního syna, ostatní nalepí za (existují-li)
def create_child(parent, type, **kwargs):
from seminar.models import TreeNode
from .models import TreeNode
if parent is None:
raise TreeLibError("Nelze vyrábět sirotky! (parent=None)")
if not issubclass(type, TreeNode):
@ -293,7 +293,7 @@ def insert_last_child(parent, node):
last.save()
def create_node_before(successor, type, **kwargs):
from seminar.models import TreeNode
from .models import TreeNode
if successor is None:
raise TreeLibError("Nelze vyrábět sirotky! (successor=None)")
if not issubclass(type, TreeNode):

View file

@ -2,16 +2,16 @@ from django.urls import path, re_path
from . import views
urlpatterns = [
#path('treenode/<int:pk>/', views.TreeNodeView.as_view(), name='seminar_treenode'),
#path('treenode/<int:pk>/json/', views.TreeNodeJSONView.as_view(), name='seminar_treenode_json'),
#path('treenode/text/<int:pk>/', views.TextWebView.as_view(), name='seminar_textnode_web'),
#path('treenode/<int:pk>/', views.TreeNodeView.as_view(), name='treenode'),
#path('treenode/<int:pk>/json/', views.TreeNodeJSONView.as_view(), name='treenode_json'),
#path('treenode/text/<int:pk>/', views.TextWebView.as_view(), name='treenode_textnode_web'),
#path('treenode/editor/pridat/<str:co>/<int:pk>/<str:kam>/', views.TreeNodePridatView.as_view(), name='treenode_pridat'),
#path('treenode/editor/smazat/<int:pk>/', views.TreeNodeSmazatView.as_view(), name='treenode_smazat'),
#path('treenode/editor/odvesitpryc/<int:pk>/', views.TreeNodeOdvesitPrycView.as_view(), name='treenode_odvesitpryc'),
#path('treenode/editor/podvesit/<int:pk>/<str:kam>/', views.TreeNodePodvesitView.as_view(), name='treenode_podvesit'),
#path('treenode/editor/prohodit/<int:pk>/', views.TreeNodeProhoditView.as_view(), name='treenode_prohodit'),
#path('treenode/sirotcinec/', views.SirotcinecView.as_view(), name='seminar_treenode_sirotcinec'),
#path('problem/(?P<pk>\d+)/(?P<prispevek>\d+)/', views.PrispevekView.as_view(), name='seminar_problem_prispevek'),
#path('treenode/sirotcinec/', views.SirotcinecView.as_view(), name='treenode_sirotcinec'),
#path('problem/(?P<pk>\d+)/(?P<prispevek>\d+)/', views.PrispevekView.as_view(), name='treenode_problem_prispevek'),
re_path(r'^temp/vue/.*$',views.VueTestView.as_view(),name='vue_test_view'),
path('temp/image_upload/', views.NahrajObrazekKTreeNoduView.as_view()),

View file

@ -6,8 +6,8 @@ from django.views.generic.edit import CreateView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied
import seminar.models as s
import seminar.models as m
from .models import TemaVCisleNode, RocnikNode, CisloNode, UlohaVzorakNode, UlohaZadaniNode, TreeNode, CastNode, TextNode, ReseniNode, PohadkaNode, OrgTextNode
from .models import Text, Obrazek
from treenode import treelib
import treenode.forms as f
import treenode.templatetags as tnltt
@ -29,7 +29,7 @@ class TNLData(object):
if parent:
self.tema_in_path = parent.tema_in_path
if isinstance(anode, m.TemaVCisleNode):
if isinstance(anode, TemaVCisleNode):
self.tema_in_path = True
def add_edit_options(self):
@ -51,11 +51,11 @@ class TNLData(object):
(All of them have method verejne.)"""
parent = anode # chceme začít už od konkrétního node včetně
while True:
rocnik = isinstance(parent, s.RocnikNode)
cislo = isinstance(parent, s.CisloNode)
uloha = (isinstance(parent, s.UlohaVzorakNode) or
isinstance(parent, s.UlohaZadaniNode))
tema = isinstance(parent, s.TemaVCisleNode)
rocnik = isinstance(parent, RocnikNode)
cislo = isinstance(parent, CisloNode)
uloha = (isinstance(parent, UlohaVzorakNode) or
isinstance(parent, UlohaZadaniNode))
tema = isinstance(parent, TemaVCisleNode)
if (rocnik or cislo or uloha or tema) or parent==None:
break
@ -158,7 +158,7 @@ class TNLData(object):
class TreeNodeView(generic.DetailView):
model = s.TreeNode
model = TreeNode
template_name = 'treenode/treenode.html'
def get_context_data(self,**kwargs):
@ -168,7 +168,7 @@ class TreeNodeView(generic.DetailView):
class TreeNodeJSONView(generic.DetailView):
model = s.TreeNode
model = TreeNode
def get(self,request,*args, **kwargs):
self.object = self.get_object()
@ -178,21 +178,21 @@ class TreeNodeJSONView(generic.DetailView):
class TreeNodePridatView(generic.View):
type_from_str = {
'rocnikNode': m.RocnikNode,
'cisloNode': m.CisloNode,
'castNode': m.CastNode,
'textNode': m.TextNode,
'temaVCisleNode': m.TemaVCisleNode,
'reseniNode': m.ReseniNode,
'ulohaZadaniNode': m.UlohaZadaniNode,
'ulohaVzorakNode': m.UlohaVzorakNode,
'pohadkaNode': m.PohadkaNode,
'orgText': m.OrgTextNode,
'rocnikNode': RocnikNode,
'cisloNode': CisloNode,
'castNode': CastNode,
'textNode': TextNode,
'temaVCisleNode': TemaVCisleNode,
'reseniNode': ReseniNode,
'ulohaZadaniNode': UlohaZadaniNode,
'ulohaVzorakNode': UlohaVzorakNode,
'pohadkaNode': PohadkaNode,
'orgText': OrgTextNode,
}
def post(self, request, *args, **kwargs):
######## FIXME: ROZEPSANE, NEFUNGUJE, DOPSAT !!!!!! ###########
node = s.TreeNode.objects.get(pk=self.kwargs['pk'])
node = TreeNode.objects.get(pk=self.kwargs['pk'])
kam = self.kwargs['kam']
co = self.kwargs['co']
typ = self.type_from_str[co]
@ -202,19 +202,19 @@ class TreeNodePridatView(generic.View):
if kam not in ('pred','syn','za'):
raise ValidationError('Přidat lze pouze před nebo za node nebo jako syna')
if co == m.TextNode:
new_obj = m.Text()
if co == TextNode:
new_obj = Text()
new_obj.save()
elif co == m.CastNode:
new_obj = m.CastNode()
elif co == CastNode:
new_obj = CastNode()
new_obj.nadpis = request.POST.get('pridat-castNode-{}-{}'.format(node.id,kam))
new_obj.save()
elif co == m.ReseniNode:
elif co == ReseniNode:
new_obj = m
pass
elif co == m.UlohaZadaniNode:
elif co == UlohaZadaniNode:
pass
elif co == m.UlohaReseniNode:
elif co == UlohaReseniNode:
pass
else:
new_obj = None
@ -225,15 +225,15 @@ class TreeNodePridatView(generic.View):
if kam == 'syn':
if typ == m.TextNode:
text_obj = m.Text()
if typ == TextNode:
text_obj = Text()
text_obj.save()
node = treelib.create_child(node, typ, text=text_obj)
else:
node = treelib.create_child(node, typ)
if kam == 'za':
if typ == m.TextNode:
text_obj = m.Text()
if typ == TextNode:
text_obj = Text()
text_obj.save()
node = treelib.create_node_after(node, typ, text=text_obj)
else:
@ -244,7 +244,7 @@ class TreeNodePridatView(generic.View):
class TreeNodeSmazatView(generic.base.View):
def post(self, request, *args, **kwargs):
node = s.TreeNode.objects.get(pk=self.kwargs['pk'])
node = TreeNode.objects.get(pk=self.kwargs['pk'])
if node.first_child:
raise NotImplementedError('Mazání TreeNode se syny není zatím podporováno!')
treelib.disconnect_node(node)
@ -254,7 +254,7 @@ class TreeNodeSmazatView(generic.base.View):
class TreeNodeOdvesitPrycView(generic.base.View):
def post(self, request, *args, **kwargs):
node = s.TreeNode.objects.get(pk=self.kwargs['pk'])
node = TreeNode.objects.get(pk=self.kwargs['pk'])
treelib.disconnect_node(node)
node.root = None
node.save()
@ -263,7 +263,7 @@ class TreeNodeOdvesitPrycView(generic.base.View):
class TreeNodePodvesitView(generic.base.View):
def post(self, request, *args, **kwargs):
node = s.TreeNode.objects.get(pk=self.kwargs['pk'])
node = TreeNode.objects.get(pk=self.kwargs['pk'])
kam = self.kwargs['kam']
if kam == 'pred':
treelib.lower_node(node)
@ -274,21 +274,21 @@ class TreeNodePodvesitView(generic.base.View):
class TreeNodeProhoditView(generic.base.View):
def post(self, request, *args, **kwargs):
node = s.TreeNode.objects.get(pk=self.kwargs['pk'])
node = TreeNode.objects.get(pk=self.kwargs['pk'])
treelib.swap_succ(node)
return redirect(request.headers.get('referer'))
#FIXME ve formulari predat puvodni url a vratit redirect na ni
class SirotcinecView(generic.ListView):
model = s.TreeNode
model = TreeNode
template_name = 'treenode/orphanage.html'
def get_queryset(self):
return s.TreeNode.objects.not_instance_of(s.RocnikNode).filter(root=None,prev=None,succ=None,father_of_first=None)
return TreeNode.objects.not_instance_of(RocnikNode).filter(root=None,prev=None,succ=None,father_of_first=None)
# FIXME pouzit Django REST Framework
class TextWebView(generic.DetailView):
model = s.Text
model = Text
def get(self,request,*args, **kwargs):
self.object = self.get_object()
@ -300,7 +300,7 @@ class VueTestView(generic.TemplateView):
class NahrajObrazekKTreeNoduView(LoginRequiredMixin, CreateView):
model = s.Obrazek
model = Obrazek
form_class = f.NahrajObrazekKTreeNoduForm
def get_initial(self):
@ -316,7 +316,7 @@ class NahrajObrazekKTreeNoduView(LoginRequiredMixin, CreateView):
print(form)
self.object = form.save(commit=False)
print(self.object.na_web)
self.object.text = m.Text.objects.get(pk=int(self.request.headers['Textid']))
self.object.text = Text.objects.get(pk=int(self.request.headers['Textid']))
self.object.save()
return JsonResponse({"url":self.object.na_web.url})

View file

@ -3,7 +3,12 @@ from rest_framework import status
from rest_framework.response import Response
from django.core.exceptions import PermissionDenied
from rest_framework.permissions import BasePermission, AllowAny
from seminar import models as m
from odevzdavatko.models import Reseni
from tvorba.models import Problem, Uloha
from .models import TextNode, CastNode, UlohaVzorakNode, UlohaZadaniNode, ReseniNode
from .models import Text
import treenode.serializers as views
from treenode.permissions import AllowWrite
@ -66,17 +71,17 @@ class ReadWriteSerializerMixin(object):
return self.create_serializer_class
class TextViewSet(PermissionMixin, viewsets.ModelViewSet):
queryset = m.Text.objects.all()
queryset = Text.objects.all()
serializer_class = views.TextSerializer
class TextNodeViewSet(PermissionMixin, ReadWriteSerializerMixin,viewsets.ModelViewSet):
queryset = m.TextNode.objects.all()
queryset = TextNode.objects.all()
read_serializer_class = views.TextNodeSerializer
write_serializer_class = views.TextNodeWriteSerializer
create_serializer_class = views.TextNodeCreateSerializer
class CastNodeViewSet(PermissionMixin, ReadWriteSerializerMixin,viewsets.ModelViewSet):
queryset = m.CastNode.objects.all()
queryset = CastNode.objects.all()
read_serializer_class = views.CastNodeSerializer
write_serializer_class = views.CastNodeSerializer
create_serializer_class = views.CastNodeCreateSerializer
@ -95,7 +100,7 @@ class UlohaVzorakNodeViewSet(PermissionMixin, ReadWriteSerializerMixin, viewsets
create_serializer_class = views.UlohaVzorakNodeCreateSerializer
def get_queryset(self):
queryset = m.UlohaVzorakNode.objects.all()
queryset = UlohaVzorakNode.objects.all()
nazev = self.request.query_params.get('nazev',None)
if nazev is not None:
queryset = queryset.filter(nazev__contains=nazev)
@ -114,7 +119,7 @@ class ReseniViewSet(viewsets.ModelViewSet):
serializer_class = views.ReseniSerializer
def get_queryset(self):
queryset = m.Reseni.objects.all()
queryset = Reseni.objects.all()
#FIXME upravit nazvy dle skutecnych polozek reseni
nazev = self.request.query_params.get('nazev',None)
if nazev is not None:
@ -128,7 +133,7 @@ class UlohaViewSet(viewsets.ModelViewSet):
serializer_class = views.UlohaSerializer
def get_queryset(self):
queryset = m.Uloha.objects.all()
queryset = Uloha.objects.all()
nazev = self.request.query_params.get('nazev',None)
if nazev is not None:
queryset = queryset.filter(nazev__contains=nazev)
@ -138,13 +143,13 @@ class UlohaViewSet(viewsets.ModelViewSet):
return queryset
class UlohaZadaniNodeViewSet(ReadWriteSerializerMixin, viewsets.ModelViewSet):
queryset = m.UlohaZadaniNode.objects.all()
queryset = UlohaZadaniNode.objects.all()
read_serializer_class = views.UlohaZadaniNodeSerializer
write_serializer_class = views.UlohaZadaniNodeWriteSerializer
create_serializer_class = views.UlohaZadaniNodeCreateSerializer
class ReseniNodeViewSet(ReadWriteSerializerMixin, viewsets.ModelViewSet):
queryset = m.ReseniNode.objects.all()
queryset = ReseniNode.objects.all()
read_serializer_class = views.ReseniNodeSerializer
write_serializer_class = views.ReseniNodeWriteSerializer
create_serializer_class = views.ReseniNodeCreateSerializer
@ -155,7 +160,7 @@ class ProblemViewSet(viewsets.ModelViewSet):
serializer_class = views.ProblemSerializer
def get_queryset(self):
queryset = m.Problem.objects.all()
queryset = Problem.objects.all()
ucel = self.request.query_params.get('ucel',None)
rocnik = self.request.query_params.get('rocnik',None)
tema = self.request.query_params.get('tema',None)

View file

@ -4,7 +4,7 @@ import datetime
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import seminar.models.tvorba
import various.models
import tvorba.models
import taggit.managers
@ -35,7 +35,7 @@ class Migration(migrations.Migration):
('datum_vydani', models.DateField(blank=True, help_text='Datum vydání finální verze', null=True, verbose_name='datum vydání')),
('verejne_db', models.BooleanField(db_column='verejne', default=False, verbose_name='číslo zveřejněno')),
('poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka k číslu (plain text)', verbose_name='neveřejná poznámka')),
('pdf', models.FileField(blank=True, help_text='PDF čísla, které si mohou řešitelé stáhnout', null=True, storage=seminar.models.tvorba.OverwriteStorage(), upload_to=tvorba.models.cislo_pdf_filename, verbose_name='pdf')),
('pdf', models.FileField(blank=True, help_text='PDF čísla, které si mohou řešitelé stáhnout', null=True, storage=various.models.OverwriteStorage(), upload_to=tvorba.models.cislo_pdf_filename, verbose_name='pdf')),
('titulka_nahled', models.ImageField(blank=True, help_text='Obrázek titulní strany, generuje se automaticky', null=True, upload_to=tvorba.models.cislo_png_filename, verbose_name='Obrázek titulní strany')),
('rocnik', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='cisla', to='tvorba.rocnik', verbose_name='ročník')),
],

View file

@ -0,0 +1,23 @@
# Generated by Django 4.2.16 on 2024-11-05 18:56
#
# Tahle migrace je tu jen, aby Django nechtělo vyrábět migrace. Smiřte se s tím.
# Stejně se default u Deadlinu nikdy nepoužívá.
import datetime
from django.db import migrations, models
from django.utils import timezone
class Migration(migrations.Migration):
dependencies = [
('tvorba', '0005_odstrel_treenode_post'),
]
operations = [
migrations.AlterField(
model_name='deadline',
name='deadline',
field=models.DateTimeField(default=timezone.make_aware(datetime.datetime.combine(timezone.now(), datetime.time.max))),
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 4.2.16 on 2024-11-12 18:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tvorba', '0006_alter_deadline_deadline'),
]
operations = [
migrations.AlterField(
model_name='deadline',
name='typ',
field=models.CharField(choices=[('cisla', 'Deadline celého čísla'), ('cislaasous', 'Sousový a celočíslový deadline'), ('prvni', 'První deadline'), ('prvniasous', 'Sousový a první deadline'), ('sous', 'Sousový deadline')], max_length=32, verbose_name='typ deadlinu'),
),
]

View file

@ -31,8 +31,7 @@ from polymorphic.models import PolymorphicModel
from django.core.mail import EmailMessage
from seminar.models.base import SeminarModelBase
from seminar.models.tvorba import OverwriteStorage
from various.models import SeminarModelBase, OverwriteStorage
from personalni.models import Prijemce, Organizator
logger = logging.getLogger(__name__)
@ -96,7 +95,7 @@ class Rocnik(SeminarModelBase):
return self.prvni_rok + 1
def verejne_url(self):
return reverse('seminar_rocnik', kwargs={'rocnik': self.rocnik})
return reverse('tvorba_rocnik', kwargs={'rocnik': self.rocnik})
@classmethod
def cached_rocnik(cls, r_id):
@ -171,7 +170,7 @@ class Cislo(SeminarModelBase):
verejne.boolean = True
def verejne_url(self):
return reverse('seminar_cislo', kwargs={'rocnik': self.rocnik.rocnik, 'cislo': self.poradi})
return reverse('tvorba_cislo', kwargs={'rocnik': self.rocnik.rocnik, 'cislo': self.poradi})
def absolute_url(self):
return "https://" + str(get_current_site(None)) + self.verejne_url()
@ -335,11 +334,13 @@ class Deadline(SeminarModelBase):
on_delete=models.CASCADE)
TYP_CISLA = 'cisla'
TYP_CISLA_A_SOUS = 'cislaasous' # Přidáno https://gitea.ks.matfyz.cz/mam/mamweb/pulls/74
TYP_PRVNI_A_SOUS = 'prvniasous'
TYP_PRVNI = 'prvni'
TYP_SOUS = 'sous'
TYP_CHOICES = [
(TYP_CISLA, 'Deadline celého čísla'),
(TYP_CISLA_A_SOUS, 'Sousový a celočíslový deadline'),
(TYP_PRVNI, 'První deadline'),
(TYP_PRVNI_A_SOUS, 'Sousový a první deadline'),
(TYP_SOUS, 'Sousový deadline'),
@ -508,7 +509,7 @@ class Problem(SeminarModelBase,PolymorphicModel):
# verejne.boolean = True
def verejne_url(self):
return reverse('seminar_problem', kwargs={'pk': self.id})
return reverse('tvorba_problem', kwargs={'pk': self.id})
def admin_url(self):
return reverse('admin:tvorba_problem_change', args=(self.id, ))

View file

@ -35,12 +35,12 @@
<div class="mam-org-only">
<h2> Orgovské odkazy </h2>
<ul>
<li><a href="obalky.pdf">Obálky (PDF)</a></li>
<li><a href="tituly.tex" download>Tituly (TeX, 2. deadline předchozího čísla a 1.deadline tohoto)</a></li>
<li><a href="vysledkovka.tex" download>Výsledkovka (TeX, 2. deadline předchozího čísla a 1.deadline tohoto)</a></li>
<li><a href="odmeny/{{prevcislo.rocnik.rocnik}}.{{prevcislo.poradi}}/">Odměny</a></li>
<li><a href="{% url "seminar_rocnik_titul" rocnik=cislo.rocnik.rocnik %}" download="posledni_tituly.tex">Tituly do závěrečného čísla (TeX, 2. deadline předchozího čísla a oba tohoto)</a></li>
<li><a href="{% url "seminar_rocnik_posledni_vysledkovka" rocnik=cislo.rocnik.rocnik %}" download>Výsledkovka závěrečného čísla ročníku (TeX, 2. deadline předchozího čísla a oba tohoto)</a></li>
<li><a href="{% url "tvorba_cislo_obalky" rocnik=cislo.rocnik.rocnik cislo=cislo.poradi %}">Obálky (PDF)</a></li>
<li><a href="{% url "tvorba_cislo_titul" rocnik=cislo.rocnik.rocnik cislo=cislo.poradi %}" download>Tituly (TeX, 2. deadline předchozího čísla a 1.deadline tohoto)</a></li>
<li><a href="{% url "tvorba_cislo_vysledkovka" rocnik=cislo.rocnik.rocnik cislo=cislo.poradi %}" download>Výsledkovka (TeX, 2. deadline předchozího čísla a 1.deadline tohoto)</a></li>
<li>{% if prevcislo %}<a href="{% url "tvorba_archiv_odmeny" trocnik=cislo.rocnik.rocnik tcislo=cislo.poradi frocnik=prevcislo.rocnik.rocnik fcislo=prevcislo.poradi %}">Odměny</a>{% else %}Pro toto číslo neumíme spočítat odměny.{% endif %}{# FIXME (Jediné číslo, kde toto neumíme je to úplně první.) #}</li>
<li><a href="{% url "tvorba_rocnik_titul" rocnik=cislo.rocnik.rocnik %}" download="posledni_tituly.tex">Tituly do závěrečného čísla (TeX, 2. deadline předchozího čísla a oba tohoto)</a></li>
<li><a href="{% url "tvorba_rocnik_posledni_vysledkovka" rocnik=cislo.rocnik.rocnik %}" download>Výsledkovka závěrečného čísla ročníku (TeX, 2. deadline předchozího čísla a oba tohoto)</a></li>
</ul>
</div>
{% endif %}

View file

@ -1,4 +1,4 @@
{% extends "seminar/archiv/problem.html" %}
{% extends "tvorba/archiv/problem.html" %}
{% block problem %}
{% if problem.cislo_zadani %}

View file

@ -1,4 +1,4 @@
{% extends "seminar/archiv/problem.html" %}
{% extends "tvorba/archiv/problem.html" %}
{% block problem %}
<h1>

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