Compare commits

..

696 commits

Author SHA1 Message Date
942e01f767 Fix Novější makro ještě není na gimlim 2025-10-05 15:21:05 +02:00
e8c5141408 Nové tituly 2025-10-04 14:45:24 +02:00
9a50805a69 Merge pull request 'Finální podoba frontendu korekturovátka?' (!92) from korekturovatko into master
Reviewed-on: #92
2025-06-04 18:54:55 +02:00
cf14e18e45 Merge pull request 'Zvýraznění problémů v hodnotící tabulce' (!98) from odevzdavatko_zvyrazneni_mych_problemu into master
Reviewed-on: #98
2025-05-21 18:41:55 +02:00
LEdoian
892d3c8d25 CSS pomocí třídy a ne ad-hoc 2025-05-15 14:33:27 +02:00
Pavel "LEdoian" Turinsky
b56689ade7 RFC: hodnotící tabulka zvýrazňuje problémy, které opravuji 2025-05-04 21:01:00 +02:00
8bde5c7e4b Merge pull request 'Role v odevzdávátku' (!97) from odevzdavatko_role into master
Reviewed-on: #97
2025-05-04 18:41:35 +02:00
Pavel "LEdoian" Turinsky
c274c8110c Role v odevzdávátku: Přejmenování tříd 2025-05-04 18:27:38 +02:00
Pavel "LEdoian" Turinsky
af298af58d Odevzdávátko: zobrazení rolí u problémů 2025-05-04 18:03:54 +02:00
01370bc07e Merge pull request 'Opravovatelé v odevzdávátku' (!96) from odevzdavatko_opravovatele into master
Reviewed-on: #96
2025-05-04 14:04:59 +02:00
Pavel "LEdoian" Turinsky
768d796549 Opravovátko: problémy ukazují role 2025-05-04 13:57:26 +02:00
f89e23e8f3 Merge branch 'galerie_vylepseni' 2025-04-30 21:34:19 +02:00
5787e1f1d8 S galerií smaž i obrázky 2025-04-30 21:02:45 +02:00
506fb3015a Galerie nad sebou nemusí mít soustředění (mohou být např. k témátku), takže umožníme i soustredeni=None 2025-04-30 21:00:29 +02:00
138acfd430 Mazaná galerie by neměla mít žádné podgalerie (jinak mohou přestat být doklikatelné) 2025-04-30 20:55:57 +02:00
b49ab06219 Rámečky kolem neveřejných podgalerií 2025-04-30 20:31:16 +02:00
fc83152169 Rámečky kolem neveřejných obrázků (ještě úplně neveřejné, https://github.com/nvbn/thefuck) 2025-04-30 20:10:55 +02:00
4334ad0323 Rámečky kolem neveřejných obrázků (ještě úplně neveřejné) 2025-04-30 20:09:15 +02:00
e5a61d69fe Rámečky kolem neveřejných obrázků 2025-04-30 20:06:22 +02:00
d9d2884707 V userovy jméno spíše nevedeme… 2025-04-26 23:52:33 +02:00
Pavel "LEdoian" Turinsky
e1acc5affe Smazán nikdy nepoužitý kód modelů galerie 2025-04-24 01:31:56 +02:00
Pavel "LEdoian" Turinsky
7f9318abca Admin akce už nedělají úplné blbosti 2025-04-24 01:29:21 +02:00
Pavel "LEdoian" Turinsky
819b22511a Lehká úprava chování pořadí v podgaleriích 2025-04-24 00:39:58 +02:00
Pavel "LEdoian" Turinsky
fa866170ee Snad neškodné úpravy templatů 2025-04-24 00:15:31 +02:00
Pavel "LEdoian" Turinsky
787a570117 Logika pro účastnické galerie ve views 2025-04-23 23:02:35 +02:00
Pavel "LEdoian" Turinsky
b744e844dd Práva ke galeriím funkční, UI zatím ne 2025-04-23 21:07:38 +02:00
c789775364 Merge branch 'upravy_exportu' 2025-04-04 19:07:44 +02:00
9af624e87e Merge pull request 'Sifrovacka Seznamy' (!94) from sifrovacka_seznamy into master
Reviewed-on: #94
2025-03-28 22:32:51 +01:00
72cab67dbd Merge pull request 'Sifrovacka Sifry Jako Text' (!93) from sifrovacka_sifry-jako-text into master
Reviewed-on: #93
2025-03-28 22:32:35 +01:00
3d3272ae00 Ignoruj case u šifry a lepší chybová hláška (ne číslo šifry) 2025-03-28 22:31:19 +01:00
Pavel "LEdoian" Turinsky
75fba8003d Seznamy šifer: migrace + fix 2025-03-19 22:39:48 +01:00
Pavel "LEdoian" Turinsky
56944d164f Merge branch 'sifrovacka_sifry-jako-text' into sifrovacka_seznamy 2025-03-19 22:18:54 +01:00
Pavel "LEdoian" Turinsky
c5b5813d75 Šifrovačka: podmnožiny odpovědí na šifry 2025-03-19 22:18:29 +01:00
Pavel "LEdoian" Turinsky
b795a8d751 Šifrovačka: jména šifer jsou teď texty 2025-03-19 21:35:32 +01:00
ec00d7acd3 Fix: Stránky PDF zůstaly daleko od sebe i při sbalení korektur 2025-03-19 21:00:57 +01:00
ee2b25f8f0 Lepší popisky u PDFek ke korekturování 2025-03-19 19:29:36 +01:00
6dbfae34d0 Vyhození cachí prohlížečů 2025-03-19 18:59:09 +01:00
8ec06d0c90 Merge branch 'master' into korekturovatko 2025-03-19 18:54:26 +01:00
66ec9bb33f Fix bugu s location.hash (v chromu) #1557 2025-03-19 18:50:39 +01:00
b8739d2b4a Shovávání komentářů 2025-03-19 18:35:20 +01:00
0722729bcd Okomentování mého zběsilého frontendu ke korekturovátku 2025-03-06 12:02:23 +01:00
82dc7e1a83 Ha, tady jsem zapomněl změnit op na kor v ID v kotvě 2025-03-06 11:41:45 +01:00
5f1e9d07d9 another fix 2025-03-05 19:49:48 +01:00
c1df250a46 multiple fields support added, prazdne params 2025-03-05 19:48:19 +01:00
eb41008261 pridani custom checkboxu 2025-03-05 19:14:11 +01:00
96337faed7 Přesun kódu, aby k sobě věci víc pasovaly 2025-03-05 19:12:48 +01:00
96b66f4019 link na odkazy se soustredeni 2025-03-05 18:44:32 +01:00
c780e7e35f pridano ucastnici 2025-03-05 18:33:06 +01:00
96b9f63295 img_indexes 2025-03-05 17:59:38 +01:00
c4679fe261 Zobjektizování (v JS) strany 2025-03-05 17:51:17 +01:00
b3ac4c4801 Zbavení se „Aktuální: stavpdf“ a ošklivé mezery 2025-03-05 16:57:21 +01:00
79a2092291 Přepsání korekturovátka do češtiny a oprava -> korektura (alespoň frontendu) 2025-03-05 16:55:54 +01:00
359eeeec6b Přesun kódu, aby k sobě věci víc pasovaly 2025-03-05 15:42:46 +01:00
158ee10836 Moderní přístup k přidávání eventlistenerů 2025-03-05 15:36:58 +01:00
b38b279b2f Nepotřebný atribut name 2025-03-05 15:30:28 +01:00
c609aa33b0 Přejmenování templatů v korekturovátku 2025-03-05 15:23:02 +01:00
73fb3a408b Tohle rozhodně nemá být 'toggle-button' 2025-03-05 15:16:12 +01:00
dead0e4f05 Definitivní zbavení se opraf.js 2025-03-05 15:06:45 +01:00
28e993d352 lang atribut 2025-03-05 15:04:36 +01:00
4912349577 Merge branch 'korekturovatko' 2025-03-05 14:59:38 +01:00
a9d7e6dd45 Vyhození cachí prohlížečů (jsem slepý) 2025-03-05 14:59:01 +01:00
2d6ad5f369 Merge remote-tracking branch 'origin/korekturovatko' 2025-03-05 14:55:23 +01:00
0289f5563b Vyhození cachí prohlížečů 2025-03-05 14:53:39 +01:00
ec5f6af6f9 Úprava tagů korektury 2025-03-05 14:52:11 +01:00
290f11cb2e Tlačítka na sbalení a rozbalení korektur 2025-03-05 13:51:24 +01:00
c125d7a62b Upgrade tlačítka na aktualizaci korektur. 2025-03-05 13:37:49 +01:00
392e345802 Drobné zjemnění tlačítek na skákání mezi korekturami 2025-03-05 13:22:02 +01:00
4c4e1815fd Komentář tlačítka na aktualizování korektur 2025-03-05 13:17:59 +01:00
b872e6693e Lepší odlišení zmáčknutých a nezmáčknutých tagů 2025-03-05 13:06:30 +01:00
ad6bd0b519 Merge pull request 'odmeny jsou pekny' (!90) from odmeny into master
Reviewed-on: #90
2025-02-26 21:22:32 +01:00
777a36b1f7 posunuti dokumneasasasa 2025-02-26 21:19:39 +01:00
d34e551b71 – 2025-02-26 21:18:19 +01:00
c1ae550a78 dokumentace probody 2025-02-26 21:13:48 +01:00
1fdd3dcaaf Merge pull request 'grey na opacity - a zarovnani do stran' (!86) from zpristupneni_jak_jste_se_dozvedeli into master
Reviewed-on: #86
2025-02-26 21:03:57 +01:00
ff7d36a965 Merge pull request 'ruzne exporty resitelu - zatím určitě ne merge xd spíš potřebuji zpětnou vazbu...' (!89) from export_resitelskych_dat into master
Reviewed-on: #89
2025-02-26 21:01:24 +01:00
73dcb441ef Merge pull request 'Přednášky' (!87) from prednasky into master
Reviewed-on: #87
2025-02-26 20:58:49 +01:00
95e164650e cervena text 2025-02-26 20:58:06 +01:00
80aa01d76b random inline css 2025-02-26 20:51:52 +01:00
8e8b446e09 fixint tyyyypos 2025-02-26 20:45:57 +01:00
c853e06d71 random bulshit goes... 2025-02-26 20:43:47 +01:00
380a14299d dalsi random test 2025-02-26 20:38:14 +01:00
8e25a2eb4f bodydiff 2025-02-26 20:34:59 +01:00
b25c04bf42 odmena nastrel 2025-02-26 20:13:25 +01:00
d86bdf9218 tak odebrano 2025-02-26 19:35:00 +01:00
b7ac841760 komentar maturity 2025-02-26 19:21:01 +01:00
be4fda5e7f oprava url na spravny tvar 2025-02-26 19:17:06 +01:00
3814d292ad oprava na query set osob 2025-02-26 19:11:50 +01:00
c004e4f6d2 Resetování tagů při přidávání další korektury (potenciálně vypnutelné) 2025-02-26 14:40:47 +01:00
MaM Web user
6e6b2deedb Když není jiného zbytí, tak budou předměty e-mailů šílené (by Jidáš) 2025-02-26 14:25:32 +01:00
283bb4247e Lepší? popis inline 2025-02-26 14:06:14 +01:00
ac71922472 Další dokumentace 2025-02-26 13:43:42 +01:00
b53da8c800 Okomentování hodnot enumu Stav 2025-02-26 13:36:50 +01:00
19448ce6c0 Hlasitější výstup při odstraněné znalosti/přednášce v exportu 2025-02-22 16:38:11 +01:00
46e2bb6b12 pro navíc 2025-02-21 17:17:14 +01:00
23e4f5257f Merge branch 'master' into prednasky 2025-02-21 17:16:37 +01:00
b72f3f3395 Fix: Nahardkoděná verze djanga v odkazech v dokumentaci 2025-02-21 17:16:01 +01:00
061a699f62 Chybějící backticky 2025-02-21 17:00:35 +01:00
a0ee238334 Závorky nechci jako součást odkazu 2025-02-21 16:42:57 +01:00
52c5d18595 Nekonzumovatelný kontext 2025-02-21 16:42:11 +01:00
4d5800f3b8 Pouze CSV export přednášek (smazání ostatních) 2025-02-19 18:54:25 +01:00
7a70b9dcff Pojmenované URL 2025-02-19 18:45:24 +01:00
a8e9f03cc1 <div>y kolem jednotlivých hlasování 2025-02-19 18:41:13 +01:00
dcba5b6b30 Hlasování má mít i osobu, nejen string 2025-02-19 18:38:38 +01:00
MaM Web user
42380643df Merge branch 'korekturovatko' 2025-02-19 18:09:09 +01:00
bfd8619505 Funkční kotva na korekturu 2025-02-19 18:06:56 +01:00
0c43c56698 Zasílání e-mailu při přidání komentáře 2025-02-19 17:50:48 +01:00
c1eb44e599 Lepší mazání korektur a komentářů 2025-02-19 17:44:50 +01:00
2c53acc214 Merge branch 'korekturovatko' 2025-02-19 17:08:13 +01:00
cb8f0aab18 <hr> uvnitř divu komentáře 2025-02-19 17:06:07 +01:00
0d653acd2e Mazání korektur a komentářů 2025-02-19 17:05:04 +01:00
c41281378b Merge branch 'korekturovatko' 2025-02-19 16:30:36 +01:00
136aca5b83 Počty stavů musíme inicializovat na nulu, jinak se nezmění 2025-02-19 16:29:23 +01:00
1624fdecc2 Zapomenuté importy 2025-02-19 16:24:55 +01:00
de0f0d3040 KorekturyView je DetailView místo TemplateView 2025-02-19 15:51:14 +01:00
0e600c7113 Zbytečné volání funkce 2025-02-19 15:34:49 +01:00
eb9232305f Typ 2025-02-19 15:34:30 +01:00
2f30d8d27f Čistka nepotřebných věcí 2025-02-19 15:30:10 +01:00
0f1cd2e32a Aktualizování počtu korektur a zásluh 2025-02-19 15:22:14 +01:00
b99b4ffdc4 Debugovací výpisy smazány 2025-02-13 18:28:25 +01:00
c75697b221 Merge branch 'korekturovatko' 2025-02-13 18:16:54 +01:00
7da38fbc8a hotfix: aha, strany obsahují pouze stránky, kde jsou korektury 2025-02-13 18:16:27 +01:00
23620e178b Merge branch 'korekturovatko' 2025-02-12 13:48:45 +01:00
b81648cdc8 Přidáni ?version=… pro vyhození cachí prohlížeče 2025-02-12 13:47:19 +01:00
83efe094b8 Méně zesvětlené tagy 2025-02-12 13:45:12 +01:00
c2ed4b9cc2 Tlačítko na refresh (FUJ!) 2025-02-12 13:01:06 +01:00
074c9414b2 Autorefresh korektur 2025-02-12 12:46:07 +01:00
63285485ca Schování tlačítek v adminu za pomoci css 2025-02-11 21:35:34 +01:00
d59cba7c0d fix: Když po chybě znovu otevřu komentovací okénko, nemá být disabed 2025-02-11 21:08:58 +01:00
9a6b66f7d9 Fix přijímání prázdné množiny tagů 2025-02-11 21:07:28 +01:00
283320b161 Buttonky další/předchozí korektura 2025-02-11 20:53:46 +01:00
773cd7d419 Tagy u korektur 2025-02-11 18:54:15 +01:00
326be3eaa0 Znemožnění poslání editace/přidání komentáře vícekrát 2025-02-11 15:59:44 +01:00
6bfeab0a5a Umožnění rozdílu mezi autory komentářů a orgy informovanými při přidání komentáře 2025-02-11 15:44:39 +01:00
befb013e58 fix: zobrazování tlačítek na okomentování 2025-02-11 14:57:20 +01:00
82a1efc965 Odkaz na úpravu korektury 2025-02-11 14:52:40 +01:00
aed8e0ea44 button má mít asi type 2025-02-11 12:21:22 +01:00
7237364ba5 Hotfix: chybějící mezery za korekturami 2025-02-11 12:03:47 +01:00
6eb4633af0 Dynamické přidávání korektur a komentářů a úprava komentářů 2025-02-11 12:00:47 +01:00
9ccacaecb5 Merge branch 'master' into korekturovatko 2025-02-11 08:56:18 +01:00
955dd60235 Aktualizace přidávátka úloh a problémů (desetinná čísla a číslo místo dílu) 2025-02-09 22:46:29 +01:00
0a58751155 Přednášky odřádkování (odstavce) 2025-02-09 22:32:16 +01:00
d1ba5057f1 Překlep (HlasovaniOZnalostech.Odpoved) 2025-02-09 22:11:46 +01:00
683796ea7e Merge branch 'refs/heads/prednasky' 2025-02-06 16:57:15 +01:00
4a771b802b Průhledné pruhy 2025-02-06 14:48:32 +01:00
84ed9e09a7 Průhledné pruhy 2025-02-06 13:50:01 +01:00
9460c484f7 Zobrazení Znalostí (stejně jako Přednášek) u daného seznamu 2025-02-04 23:09:47 +01:00
2767e82f11 Merge branch 'master' into prednasky 2025-02-04 22:49:06 +01:00
42c651ceb7 Zlepšení dokumentace Djanga ve Sphinxu 2025-02-04 22:47:18 +01:00
5d4b600b00 Otočení významu odpovědí na hlasování o znalostech + WTF proč to byl string 2025-02-04 21:21:15 +01:00
c1da67dbb4 Dobře, příště už při dokumentaci nebudu hrabat na typové anotace. 2025-02-04 20:33:03 +01:00
5a1eedb7b1 ruzne exporty resitelu 2025-01-29 18:17:00 +01:00
5125525238 Dokumentace aplikace prednasky 2025-01-29 01:06:00 +01:00
34f0dffd79 Merge branch 'master' into prednasky 2025-01-29 00:57:24 +01:00
9e513bba9a Kopírování je častým zdrojem chyb 2025-01-29 00:30:55 +01:00
ef9d51d922 hotfix: WTF se stalo v django-autocomplete-light=3.12.0 (%20 místo mezer apod.) 2025-01-28 19:03:51 +01:00
ca5e6728dd hotfix: Tohle by mělo opravit problém s ukládáním bodů. Nejsem si tím ale moc jistý. 2025-01-28 18:48:57 +01:00
7563dd728c Fix make/deploy 2025-01-24 22:51:09 +01:00
cb14e4a91e A očividně nevygeneroval migraci k přepsání stringů u Hlasovani.Body v commitu e933c697 2025-01-24 21:07:06 +01:00
1719e8be9a Zapomněl jsem přidat CSS místo smazaného <i> 2025-01-24 21:03:44 +01:00
0634cdad87 Očividně každý systém žere uvozovky v f-stringu jinak 2025-01-24 20:42:44 +01:00
90e7b97b85 Zakomentován starý export 2025-01-24 20:35:24 +01:00
4001822842 Oprava práv pro aplikaci přednášky 2025-01-24 20:32:28 +01:00
7ca7093371 Export hlasování do CSV 2025-01-24 20:22:38 +01:00
fbd75d2f72 Hlasování o přednáškách pomocí formsetů… 2025-01-24 19:40:54 +01:00
e12b614e1c ODPOVED -> Odpoved 2025-01-24 16:01:39 +01:00
bcda95f0b3 Stringifikace hlasování o znalostech 2025-01-24 15:51:06 +01:00
6c35a5b6f3 Uhlazení prednasky.models 2025-01-24 15:49:55 +01:00
e933c6978d Choices na Enum (u přednášek) 2025-01-24 15:36:33 +01:00
2f814956a7 Nepoužívaný kus kódu 2025-01-24 15:20:00 +01:00
f61533df0a Přidání Znalosti do modelu 2025-01-24 15:13:37 +01:00
7676b0ef60 Merge branch 'korekturovatko-full' into korekturovatko 2025-01-22 20:28:31 +01:00
95b46541c0 Uhlazení JavaScriptu… 2025-01-22 20:28:24 +01:00
0af99d4f3e Aktualizace všech komentářů jako funkce (a aktualizace, ne vytvoření) 2025-01-22 20:23:12 +01:00
f369110cd3 Stránky PDF zvlášť 2025-01-22 19:36:44 +01:00
c54e11f25a Uhlazení JavaScriptu 2025-01-22 19:31:42 +01:00
e205ca52d3 Korektury načtené z API (místo v templatu)… 2025-01-22 18:38:28 +01:00
44a8649d0e Vytváření korektur z jednoho prototypu 2025-01-22 15:39:34 +01:00
291d4e2d56 grey na opacity - a zarovnani do stran 2025-01-22 13:12:09 +01:00
a4175f836e Zvýrazňování čar (pointrů) pomocí atributu místo třídy 2025-01-22 11:25:19 +01:00
174087edc7 Merge pull request 'Zpřístupnění informací z "jak se o nás dozvěděli" propagaci' (!85) from zpristupneni_jak_jste_se_dozvedeli into master
Reviewed-on: #85
Reviewed-by: Pavel Turinský <ksgitea@pokemon.ledoian.cz>
2025-01-21 22:15:10 +01:00
4906f82365 Inteligentní hláška, pokud nejsou žádné přednášky. 2025-01-21 22:05:04 +01:00
41032be9eb Žádné vzpomínky na seminar.models! 2025-01-21 21:59:29 +01:00
422caadb9e Smazání nadbytečné vazebné tabulky (vazba bude zase viditelná v adminu) 2025-01-21 21:58:42 +01:00
aa997bfcd8 Aktuální sous jsme chtěli blank=True 2025-01-21 21:51:30 +01:00
1a2bef328b Inteligentnější přiřazování seznamu přednášek hlasovátku a upozornění na neexistující seznam 2025-01-21 21:43:43 +01:00
a84df1909b Lepší hláška po odeslání přednášek. 2025-01-21 21:14:19 +01:00
0724030bef nazev branche splnen 2025-01-21 20:39:31 +01:00
833893f233 Merge pull request 'odevzdavatko: odesílání emailu řešiteli při změně zpětné vazby' (!83) from notifikace-zpetne-vazby into master
Reviewed-on: #83
2025-01-21 18:10:48 +01:00
a7746cddda Merge branch 'master' into notifikace-zpetne-vazby 2025-01-21 18:05:10 +01:00
2c627b3d60 Když už jsem u toho, tak event.keyCode -> event.code 2025-01-21 13:01:57 +01:00
5f904b5c66 Vytažení commform konstant z funkce showform (budu potřebovat pro submit) 2025-01-21 12:58:23 +01:00
94ca903cec Chybějící const 2025-01-21 12:01:47 +01:00
50936e2b50 Prázdné okno editace komentáře klidně zavřít 2025-01-21 11:53:12 +01:00
4a35a63f31 Zbavení se deprecated věci 2025-01-21 10:49:14 +01:00
4006ecd6b8 A ještě jedno uhlazení CSS korekturovátka 2025-01-21 10:24:43 +01:00
ce2d183446 Ještě jedno uhlazení CSS korekturovátka 2025-01-21 10:23:48 +01:00
1ff69f943e Uhlazení CSS korekturovátka 2025-01-21 10:15:42 +01:00
050ebba03b Nepoužívaná css 2025-01-21 10:06:11 +01:00
443a226943 box -> oprava 2025-01-21 10:05:27 +01:00
7da9aa5fe3 Upozorňovat při zavírání okénka editace komentáře 2025-01-21 09:40:42 +01:00
e257f31ea5 x,y uložené v opravě (nemusíme tahat z čáry (pointru)) 2025-01-21 09:28:46 +01:00
87466ce2b6 box -> oprava 2025-01-21 09:23:20 +01:00
291f39990d Vyčištění IDček v html opravy 2025-01-21 09:12:36 +01:00
b637aed7ab Zapomenutý switch podle document.body.class místo podle atributu 2025-01-21 08:22:34 +01:00
3e2469fc45 Tady je těch prázných stringů nějak moc 2025-01-21 08:20:34 +01:00
d5c57da921 Čára (pointer) jako atribut opravy místo grepování "opid-pointer" 2025-01-21 08:17:12 +01:00
370dd7d841 Už nemáme text v opravě, takže nemá smysl ho editovat 2025-01-21 08:12:42 +01:00
d818ce251b Box_edit za pomoci objektu opravy místo id 2025-01-21 08:10:13 +01:00
4121de260e Ha, tohle jsem chtěl tady 2025-01-21 07:48:05 +01:00
a8b7788d35 Merge branch 'master' into korekturovatko 2025-01-21 07:40:42 +01:00
9aa3a5154d Přesnější query (aby nebral i jiné formuláře) 2025-01-21 07:39:55 +01:00
7a4c8239f6 Čára (pointer) opravy nemá vůbec interagovat s klikáním 2025-01-21 07:37:36 +01:00
0d67b5ff83 Prozatím takhle (proházel jsem kanály) 2025-01-21 00:07:14 +01:00
2deccfada4 Oprava URL 2025-01-20 23:48:54 +01:00
d2e199e509 Dynamický update stavu opravy 2025-01-20 23:40:06 +01:00
9b0fe3d32f Tohle tu nějak zůstalo 2025-01-20 22:35:46 +01:00
af41ca5784 Čistka scrollování 2025-01-20 22:11:53 +01:00
ad2d1e676c Zobrazení tlačítek podle atributu (ne v podle stavu v templatech) 2025-01-20 22:11:02 +01:00
00e0ed0a50 Skrývání korektur pomocí atributu 2025-01-20 21:45:37 +01:00
5563eb681c Status korektury jako atribut a ne třída 2025-01-20 21:06:33 +01:00
3b74772949 Přeházení JavaScriptových věcí 2025-01-20 19:12:27 +01:00
ee69bf4c4f Drobný úklid 2025-01-20 18:38:47 +01:00
071c66ee10 personalni: změna nastavení upozorňování u existujících řešitelů 2025-01-15 18:24:22 +01:00
1bee36d9b6 personalni: přejmenování sloupce pro upozornění na zpětnou vazbu 2025-01-14 21:00:33 +01:00
aa364b3f49 Výsledkovky do TeXu (a tituly) si odteď více hlídají, že jsou správně stažené. 2025-01-09 20:28:38 +01:00
9a11491dcc Merge pull request 'Admin strxfrm mac' (!81) from strxfrm_pro_mac into master
Reviewed-on: #81
2025-01-07 18:30:51 +01:00
a97071cd03 not dumb error log message 2025-01-07 17:49:16 +01:00
9c4c60765e logger done 2025-01-07 17:44:22 +01:00
d67d2f372b Oprava zobrazování deadlinů v aktuálním zadání 2025-01-03 18:00:47 +01:00
dd86fc1fcb Dynamický update stavu 2024-12-12 13:52:55 +01:00
341ae7ce45 Rozlišení lokálního/testovacího/produkčního webu v korekturovátku 2024-12-12 12:51:32 +01:00
7906c87733 Zanášení a zastaralé pomocí atributu místo třídy 2024-12-12 12:50:56 +01:00
daf24ff981 Rozstřílení korekturovátka (html) 2024-12-12 12:33:33 +01:00
0dde05f102 Odstranění textu a autora z opravy (přesun do prvního komentáře) 2024-12-12 11:50:17 +01:00
3528a44d3c Úprava chování korektury (schovává text korektury!!!) 2024-12-12 11:24:17 +01:00
524593b7ef Merge branch 'master' into korekturovatko 2024-12-10 19:03:22 +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
6ea212cdf8 odevzdavatko: odesílání emailu řešiteli při změně zpětné vazby
Toto se rozbíjí, když dojde ke smazání hodnocení v pořadí dříve, než
nějaké hodnocení s neprázdnou zpětnou vazbou, neboť řádky formsetu jsou
přečíslovány a pak špatně spárovány s původními hodnotami, takže se
nesprávně detekuje změna.
2024-12-03 21:30:35 +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
22e88daf02 Korektury api init 2024-12-03 20:10:41 +01:00
04508206bb Funkce na posílání e-mailu rozhodně nemá být uvnitř View 2024-12-03 19:41:12 +01:00
8d09fd5389 Status opravy řešit přes enum 2024-12-03 19:10:36 +01:00
9df34c22e0 Moderní přístup k choices (umožňuje např. vytáhnout seznam všech hodnot) 2024-12-03 19:01:33 +01:00
d3d5484d0e Form se nikde nepoužívá 2024-12-03 18:37:23 +01:00
62160e8440 PDF už dostáváme v URL, není to potřeba shánět znovu 2024-12-03 18:33:26 +01:00
1e6e6118a7 Nepoužívat náhodné stringy, když už máme nějaké definované… 2024-12-03 18:20:25 +01:00
c1440687aa Nepotřebné importy… 2024-12-03 17:51:33 +01:00
69e870f958 Podle mě funguje… 2024-12-03 17:50:48 +01:00
ddd12b684d komentar 2024-12-02 11:51:19 +01:00
eb2b861d48 try except na sort 2024-12-01 15:47:49 +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
om
0e83f96318 <p> se souctem bodu pod odevzdana reseni 2024-11-05 22:20:48 +01:00
om
e660a96df2 <p> se souctem bodu pod odevzdana reseni 2024-11-05 22:10:45 +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
om
8156f7828e +5 let limit maturity 2024-11-05 19:25:36 +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
7b3708d7ab Merge pull request 'Oprava Testu Delani Orgem' (!68) from oprava_testu_delani_orgem into master
Reviewed-on: #68
2024-11-05 14:40:37 +01:00
c0825691a6 Merge branch 'master' into podezrele-seminare 2024-11-05 14:29:02 +01:00
b606f03191 Merge pull request 'Odstřel modelu TreeNode' (!67) from odstrel_modelu_treenode into master
Reviewed-on: #67
2024-11-05 14:19:50 +01:00
Pavel "LEdoian" Turinsky
461cfa4253 Treenode: komentář o tom, proč tam je i Text a Obrazek 2024-11-03 03:20:52 +01:00
Pavel "LEdoian" Turinsky
a2517abade Odstřel: závěrečná migrace 2024-11-03 03:12:16 +01:00
Pavel "LEdoian" Turinsky
51618e0d89 Oprava testu dělání orgem 2024-11-03 02:51:27 +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
Pavel "LEdoian" Turinsky
e10747aa9f odstřel Treenode: zapomenutá závislost na contenttypes 2024-11-02 22:46:14 +01:00
Pavel "LEdoian" Turinsky
8ae19988b1 Vyrábět schéma semináře už nemá moc smysl :-) 2024-11-02 22:21:47 +01:00
Pavel "LEdoian" Turinsky
2659d72d2e odstřel TreeNode: 🔫💥💥💥 2024-11-02 22:00:08 +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
MaM Web user
5c7710ed3a Formátování jsonu (jidáš) 2024-11-01 12:09:04 +01:00
6cdc37e80b Synchronizace sitetree s produkcí (než se v tom začnu hrabat) 2024-11-01 12:01:24 +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
87a76b17ef Merge pull request 'Odstrel Modelu Tvorba' (!66) from odstrel_modelu_tvorba into master
Reviewed-on: #66
2024-10-31 10:54:50 +01:00
Pavel "LEdoian" Turinsky
ad9a496cee Kus kódu není potřeba (a navíc obsahuje slovo seminář :-P) 2024-10-31 00:03:00 +01:00
Pavel "LEdoian" Turinsky
26d37d96f7 Frontendové náhrady semináře
(některé netestované, smůla.)
2024-10-31 00:01:07 +01:00
Pavel 'LEdoian' Turinsky
062f70e947 odstřel tvorby: relink – post 2024-10-30 22:41:11 +01:00
5db14ea242 Částečné řešení #1465 (Podezřelé seminare) 2024-10-30 15:03:17 +01:00
Pavel 'LEdoian' Turinsky
92c05342fb odstřel tvorby: pre – relink 2024-10-30 14:36:06 +01:00
b8f377b15d Merge pull request 'Odstrel Modelu Odevzdavatko' (!64) from odstrel_modelu_odevzdavatko into master
Reviewed-on: #64
2024-10-29 19:31:54 +01:00
a6085a64f4 Přepojení importů 2024-10-29 19:21:00 +01:00
c71195fba1 Merge branch 'master' into odstrel_modelu_odevzdavatko 2024-10-29 19:15:36 +01:00
9ee82f72a7 SeminarModelBase.admin_url nyní umí i modely v jiných appkách… 2024-10-29 19:12:42 +01:00
ce83247fe0 Doplnění nepovinného nesting selektoru &, abychom rozšířili support na starší prohlížeče 2024-10-28 12:29:08 +01:00
46a8f136fa Fix formularOKView (TemplateDoesNotExist) 2024-10-25 20:21:47 +02:00
e0dc75763c Jiný pokus o opravu stylů na starých iOSech 2024-10-25 13:31:50 +02:00
788e8d22a2 Pokus o opravu stylů na starých iOSech 2024-10-25 12:57:07 +02: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
Pavel "LEdoian" Turinsky
c7fe1cb386 post – odevzdávátko odstřeleno! 2024-10-23 23:11:56 +02:00
Pavel "LEdoian" Turinsky
07e9c8b7e4 manage 2024-10-23 23:09:19 +02:00
Pavel "LEdoian" Turinsky
10a824719b delete 2024-10-23 23:01:54 +02:00
Pavel "LEdoian" Turinsky
d288fefecc Zrušení dočasných odkazů do seminar.models.*
Poznámky:
- `seminar.models.base` je v pořádku (není to dočasný soubor)
- dočasné importy v `seminar/models/*.py` jsou taky validní, protože
  odtamtud zmizí. Jde mi o to, aby náhodné věci buď používaly
  už-správnou aplikaci, nebo postaru `seminar.models` jako celek, aby
  během odstřelu nebylo potřeba všechny věci přepisovat.

Ano, tohle je potenciálně kontroverzní commit. Ale je dočasný a mně
poněkud rozbíjí workflow, když mi v náhodné okamžiky odmítne běžet
náhodná část webu na kterou nesahám jen kvůli tomu, že importuje věci
odněkud, odkud to nečekám.

Alternativní řešení: dát správné importy (s dočasnými
`seminar.models.*`) do správných `aplikace/models.py` už teď a
importovat věci rovnou z výsledných modulů. To zajišťuje jak
konzistenci, tak to, že při odstřelu se cesta změní na jednom očekávaném
místě (resp. spíš se prostě smaže) a všechno ostatní bude pokračovat ve
fungování.
2024-10-23 22:54:14 +02:00
Pavel "LEdoian" Turinsky
130907174d relink 2024-10-23 21:54:49 +02:00
Pavel "LEdoian" Turinsky
33381b4307 create 2024-10-23 21:46:19 +02:00
c7910ed72a Merge pull request 'Více orgů k jednomu PDF v korekturovátku' (!52) from vice_orgu_v_korekturovatku into master
Reviewed-on: #52
2024-10-23 12:47:49 +02:00
35fa97d828 Merge branch 'master' into vice_orgu_v_korekturovatku 2024-10-23 12:25:19 +02:00
35131c96ec Novinky byly bůhví proč BigAutoField 2024-10-23 12:22:46 +02:00
acdadc06ab Merge branch 'ghoul_font' 2024-10-23 11:57:29 +02:00
2ae906495d Opravil jsem font (aby fungovala diakritika apod.) 2024-10-23 11:49:48 +02:00
Pavel "LEdoian" Turinsky
49e93025c0 unm 2: změna jména 2024-10-23 01:01:09 +02:00
Pavel "LEdoian" Turinsky
d1172b9d38 unm 2024-10-23 00:31:58 +02:00
Pavel "LEdoian" Turinsky
792a8fa4dc pre 2024-10-23 00:31:21 +02:00
622b632773 Merge pull request 'ghoul_font' (!63) from ghoul_font into master
Reviewed-on: #63
2024-10-22 23:49:55 +02:00
91b6220f24 Merge branch 'master' into ghoul_font 2024-10-22 22:31:05 +02:00
bb0cf871ec Merge pull request 'Oddělení sousových věcí' (!56) from split_sous into master
Reviewed-on: #56
2024-10-22 22:15:14 +02:00
3db6231a77 Název id v url 2024-10-22 22:11:59 +02:00
d5d55d76a9 Lepší komentář 2024-10-22 21:57:01 +02:00
0423bce762 Merge branch 'master' into split_sous
# Conflicts:
#	seminar/testutils.py
#	soustredeni/urls.py
2024-10-22 21:49:15 +02:00
f9a28689b0 views_all do __init__ 2024-10-22 21:30:33 +02:00
7108702e36 Merge pull request 'Rozstřílení seminářové aplikace' (!60) from split into master
Reviewed-on: #60
2024-10-22 21:27:20 +02:00
e443ecf33d views_all do __init__ 2024-10-22 21:23:11 +02:00
8d846647f6 Merge branch 'master' into split
# Conflicts:
#	various/templates/various/titulnistrana/titulnistrana.html
2024-10-22 20:18:48 +02:00
8ff66cb631 WTF? 2024-10-22 20:15:58 +02:00
227b83b701 halloween do settings 2024-10-22 20:10:27 +02:00
192f5d2ee7 Merge pull request 'Velká revize stylů (a některých JS)' (!44) from static-files-upgrade into master
Reviewed-on: #44
2024-10-22 20:00:11 +02:00
77d158a3a7 Změna version, aby počítače přenačetly CSSka 2024-10-22 19:56:08 +02:00
05e6e5fb59 Merge branch 'master' into static-files-upgrade 2024-10-22 19:29:54 +02:00
fbd5087c02 Změna version, aby počítače přenačetly CSSka 2024-10-22 19:29:29 +02:00
8dca676edc script presunut dolu pod april 2024-10-22 19:29:28 +02:00
ffcc2e04a3 TS -> TITULNI_STRANA a AZAD -> AKTUALNI_ZADANI 2024-10-22 19:28:12 +02:00
b491dcff7e Komentář k kontejneru 2024-10-22 19:19:31 +02:00
592ae29d35 ghoul font na posledních 7 dní října 2024 2024-10-22 19:05:41 +02:00
133c487637 Sloupeček ostatní ve výsledkovce (nová makra) 2024-10-13 23:39:47 +02:00
9f421e9d77 Sloupeček ostatní ve výsledkovce (nová makra) 2024-10-13 23:38:17 +02:00
e87bbe6048 Sloupeček ostatní ve výsledkovce (nová makra) 2024-10-13 23:31:05 +02:00
6ec2a10bf5 setuptools v requirements.txt 2024-10-10 13:23:51 +02:00
c45bf88d40 Merge pull request 'Export do abstraktů (soustředění)' (!61) from sbornicek into master
Reviewed-on: #61
2024-10-10 09:08:09 +02:00
733484b503 Export do abstraktů (soustředění) 2024-10-02 21:20:44 +02:00
e250f1d5dc Admin_url soustředění (aby mohlo být v login-baru) 2024-10-02 21:16:53 +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
b41b912aa4 Merge branch 'refs/heads/master' into split_sous 2024-08-06 02:39:08 +02:00
348096024e seminar/testutils.py 2024-08-06 02:33:53 +02:00
d952ab13a5 Generování konfer s konkrétními řešiteli 2024-08-05 13:15:29 +02:00
bf748b55ee Odstraněn zakomentovaný zbytečně složitý kód 2024-08-05 13:15:25 +02:00
6a781323e0 Typové anotace a další detaily v generování testdat k soustredeni 2024-08-05 13:15:21 +02:00
0b0a939de5 Oddělení generování testdat k sous věcem 2024-08-05 13:15:17 +02:00
c34716e134 seminar/utils.py 2024-08-05 11:46:38 +02:00
85c3969c50 seminar/templatetags 2024-08-04 19:21:46 +02:00
18364eb531 seminar/static 2024-08-04 19:15:15 +02:00
95ab0ee1dc Commandy zatím do various… 2024-08-04 19:08:24 +02:00
731c795ee6 Text a Obrazek 2024-08-04 19:01:09 +02:00
31b7cbb8d7 Tvorba (templates, admin, views) 2024-08-04 18:53:35 +02:00
ba2ea74a04 Obálky do personálního 2024-08-04 18:23:58 +02:00
a6eebb2d59 Seznam organizátorů do personálního 2024-08-04 18:15:01 +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
5f7ec853fa Přesun náhodných views do various 2024-08-04 17:41:24 +02:00
0cab9a8286 Přesun csrf_error 2024-08-04 17:02:25 +02:00
9920465f99 Split novinek 2024-08-04 16:57:39 +02:00
99a1fd9a9f Merge pull request 'Vyčištění náhodných věcí (co mě tak kde štvalo)' (!58) from vycisteni into master
Reviewed-on: #58

Není tam nic sporného ani kritického[^1], takže rovnou mergeuji…

[^1]: a máme docela málo kapacity na pročítání pull requestů.
2024-08-03 13:08:46 +02:00
431978e626 Starý kus kódu, který by se mohl hodit, ale je příšerně zastaralý (ObrazekAutocomplete) 2024-08-03 13:03:09 +02:00
69635b4234 TODO jsou v kanboardu 2024-08-03 13:01:10 +02:00
f0b642ca44 Nepoužívaný kus kódu 2024-08-03 12:58:38 +02:00
8dc3a2ba98 Odstraněňo staré testování e-mailů 2024-08-03 11:50:14 +02:00
8fef21900c Autogenerované komentáře a prázdné testy 2024-08-03 11:28:03 +02:00
1d36cd0761 Tohle nastavení už je v settings 2024-08-03 11:09:39 +02:00
e21a93f9e7 Middleware řešící sessioh mezi http a https se už fakt dlouho nepoužívá a navíc je toto téma dnes dávno pasé 2024-08-03 11:06:00 +02:00
ead2a4ede3 Tahle middleware se vůbec nepoužívá (používá se contextprocessor vzhled) 2024-08-03 11:04:19 +02:00
5f9bda9afe Ha, tohle mělo smazáno být už v 75344c2c 2024-08-03 10:56:51 +02:00
0817012130 Tahle moje „dokumentace“ je k ničemu, mažu 2024-08-03 10:55:03 +02:00
7a6a7cb0f5 Návod na nahrátí obrázků do odměn (a jinak o flatpage) nemá smysl mít v kořenové složce 2024-08-03 10:42:31 +02:00
75344c2c3a Kontroly codestylu nechceme 2024-08-03 10:40:03 +02:00
350623c6ac Python už je dávno defaultně utf-8, netřeba deklarace na začátku souboru 2024-08-03 10:38:00 +02:00
2bb732959a Nepoužívaný kód 2024-08-03 10:31:20 +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
4a3681b1a6 Drobnost 2024-08-02 20:32:07 +02:00
ccf3ec07f7 Generování konfer s konkrétními řešiteli 2024-08-02 20:17:31 +02:00
b44bdadb0a Odstraněn zakomentovaný zbytečně složitý kód 2024-08-02 20:15:02 +02:00
a6220e8d50 Typové anotace a další detaily v generování testdat k soustredeni 2024-08-02 20:13:45 +02:00
27beb34153 Oddělení generování testdat k sous věcem 2024-08-02 19:38:06 +02:00
62a65af40e Odstranění zbytečného importu 2024-08-02 19:29:13 +02:00
036af434c6 Oddělení urlpatterns konkrétního sousu 2024-08-02 19:25:25 +02:00
d1db1b952f Obsah modulu soustredeni 2024-08-02 19:17:49 +02:00
ddda7052ae Zbavení se zbytečného importu modelu Resitel 2024-08-02 19:17:11 +02:00
0fa2fb8e2b Úprava importů v soustredeni 2024-08-02 19:01:50 +02:00
b094347b7c Vytažení prefixu 'soustredeni/' do aplikace soustredeni 2024-08-02 18:34:05 +02:00
3188e024da Logy týracího skriptu do .gitignore 2024-08-02 13:24:29 +02:00
69019baf4e Merge pull request 'Přidání chybové hlášky :3' (!55) from divni-uzivatele into master
Reviewed-on: #55
2024-08-02 11:52:40 +02:00
ec474530e4 add .DS_store to .gitignore 2024-07-19 11:41:53 +02:00
f564d4e145 Správný odkaz na diff (na wiki)? 2024-07-09 19:32:09 +02:00
49aea5b8b4 Změny (z gimliho) v konfiguraci nginxu, které asi chceme 2024-07-09 19:30:32 +02:00
Pavel "LEdoian" Turinsky
ca8e8506b3 Přebytečná HttpResponse 2024-07-08 22:52:58 +02:00
Pavel "LEdoian" Turinsky
2444e5f985 Nemusím shazovat web 2024-07-08 22:39:46 +02:00
476f8263d4 Merge pull request 'Otevírání článků na správné straně' (!54) from clanek-strana into master
Reviewed-on: #54
2024-06-20 14:42:39 +02:00
1ce9cb445f Tady bylo „.cislo“ navíc… 2024-06-20 14:38:27 +02:00
6225630f9f Migrační kekel 2024-06-20 14:31:58 +02:00
0bebee5329 Merge branch 'master' into clanek-strana 2024-06-19 20:34:56 +02:00
Riki
3c3b9f755a Add strana column to Clanek
Optionally add page fragment to `resitelske_clanky.html`
2024-06-15 23:35:46 +02:00
40ca76d414 Merge pull request 'akce na sjednoceni cisel do +420 123 456 789' (!53) from sjednoceni_telefonu into master
Reviewed-on: #53
2024-06-15 23:33:24 +02:00
cf3c9f6f80 prazdy string -> continue 2024-06-15 23:27:42 +02:00
fad2c6940d oprava chybové hlášky 2024-06-15 23:20:35 +02:00
491f91cf47 akce na sjednoceni cisel do +420 123 456 78 2024-06-15 22:54:26 +02:00
Pavel "LEdoian" Turinsky
2304a8282c PDF nemusí mít zodpovědného orga! 2024-06-15 22:50:20 +02:00
Pavel "LEdoian" Turinsky
8a5659420d Na výběr orgů pro PDF chceme autocomplete 2024-06-12 02:25:01 +02:00
Pavel "LEdoian" Turinsky
d895cd0636 Víc orgů k jednomu PDF v korekturovátku 2024-06-12 02:02:37 +02:00
c0958d79db Merge pull request 'Úpravy Admina před víkendovkou' (!50) from upravy-admina-pred-vikendovkou into master
Reviewed-on: #50
2024-06-04 20:20:02 +02:00
737c296da9 Merge pull request 'Vylepšení seznamu aplikací v Adminu' (!51) from pojmenovane_aplikace into master
Reviewed-on: #51
2024-06-04 20:19:57 +02:00
Pavel "LEdoian" Turinsky
067cd07352 Zapomenutý choice u osoby 2024-06-03 16:31:38 +02:00
Pavel "LEdoian" Turinsky
f75408e936 fix závislostí seminar/0117 2024-06-03 14:10:41 +02:00
Pavel "LEdoian" Turinsky
c917655a22 Při make/init_local nesyncujeme flatpages
Je to otravné a navíc to vyžaduje klíč…
2024-06-03 14:02:05 +02:00
Pavel "LEdoian" Turinsky
c78b932587 Oprava odhlášeného admina 2024-06-03 03:02:06 +02:00
Pavel "LEdoian" Turinsky
381c5ca3de Hezká jména a pořadí aplikací 2024-06-03 02:06:00 +02:00
Pavel "LEdoian" Turinsky
15b09c23a7 Posunutí odstřelu novinek až za reformu pohlaví 2024-06-03 01:28:18 +02:00
7c1d9a0329 Merge pull request 'Split Novinky' (!48) from split-novinky into master
Reviewed-on: #48
2024-06-02 22:49:15 +02:00
Pavel "LEdoian" Turinsky
e431e23ad4 Vyrábění orgů z řešitelů 2024-06-02 18:08:18 +02:00
Pavel "LEdoian" Turinsky
8dbb92d3a4 Filtrováni řešitelů 2024-06-02 17:17:57 +02:00
e06080ae31 Merge pull request 'Reforma pohlaví' (!49) from reforma_pohlavi into master
Reviewed-on: #49
2024-05-25 18:21:24 +02:00
Pavel "LEdoian" Turinsky
6165c5916b Zapomenutá závislost 2024-05-21 21:35:06 +02:00
Pavel "LEdoian" Turinsky
bb1fb87665 Oprava migrace kolem pohlaví 2024-05-21 21:33:42 +02:00
Pavel "LEdoian" Turinsky
8949424f48 Zapomenutá migrace post_sous 2024-05-21 21:33:25 +02:00
37f988f478 Merge branch 'master' into reforma_pohlavi 2024-05-21 21:20:37 +02:00
Pavel "LEdoian" Turinsky
a3a9c629d8 Reforma pohlaví, I guess 2024-05-21 21:02:53 +02:00
Pavel "LEdoian" Turinsky
607129a899 Úprava orgorozcestníku 2024-05-13 23:45:47 +02:00
Pavel "LEdoian" Turinsky
b8328c0932 Merge branch 'master' into split-novinky 2024-05-13 23:45:30 +02:00
Pavel "LEdoian" Turinsky
2ce45b3d89 Co dalšího nezapomenout při odstřelu aplikací 2024-05-13 23:44:54 +02:00
Pavel "LEdoian" Turinsky
e618d390de Orgorozcestník používá urls, ne hardcodované cesty 2024-05-13 23:44:26 +02:00
af9f244207 Drobný nesting 2024-05-13 23:26:16 +02:00
Pavel "LEdoian" Turinsky
7e6dd72722 Přesunutí admina pro novinky 2024-05-13 23:13:51 +02:00
Pavel "LEdoian" Turinsky
3be9578ec3 Novinky split
It's everything, everything at once.
2024-05-13 23:10:22 +02:00
20691f483a Merge remote-tracking branch 'origin/master' into static-files-upgrade 2024-05-13 22:41:09 +02:00
64465a0471 Merge pull request 'Oddělení soustředění do vlastní aplikace' (!47) from split-soustredeni into master
Reviewed-on: #47
2024-05-13 22:16:52 +02:00
Pavel "LEdoian" Turinsky
7569447baa Oprava assertEquals na assertEqual
první je deprecated a v Py3.12 už nefunguje.
2024-05-13 22:12:51 +02:00
Pavel "LEdoian" Turinsky
235bd096e0 Merge branch 'master' into split-soustredeni 2024-05-01 16:35:19 +02:00
Pavel "LEdoian" Turinsky
6c59c3c2ed Oprava jmen modelů po odstřelu nastavení 2024-05-01 16:32:54 +02:00
Pavel "LEdoian" Turinsky
9f08ec332f Oprava jmen modelů po odstřelu nastavení 2024-05-01 16:31:54 +02:00
Pavel "LEdoian" Turinsky
e075a9e749 Oprava jmen modelů po odstřelu personálií 2024-05-01 16:27:39 +02:00
Pavel "LEdoian" Turinsky
8c4bf4d19a Oprava jmen modelů v org právech 2024-05-01 15:48:33 +02:00
Pavel "LEdoian" Turinsky
e3771f865d Relink – post, asi done? 2024-05-01 15:37:54 +02:00
Pavel "LEdoian" Turinsky
cdc1472595 Pre, unmanage, create. Snad ve finální podobě. 2024-05-01 01:15:27 +02:00
4d13b0eb25 Tohle je myslím zastaralé (stará verze bootstrapu) 2024-04-30 23:57:55 +02:00
6cc1b1fbe7 Tohle je spíše nepoužívané 2024-04-30 23:57:32 +02:00
72f11a25ce Myslím, že funguje, že jsem byl jen hloupý 2024-04-30 23:54:57 +02:00
3f1a570878 Nějaké další přechody na CSS nesting 2024-04-30 23:53:50 +02:00
c21d040202 Merge pull request 'Split Apps: oddělení personálních modelů' (!43) from split-apps into master
Reviewed-on: #43

Muhahaha
2024-04-30 23:48:35 +02:00
53caf7f73a Novinka je rozhodně modul (můžeme ji použít kdekoliv, nejen na jedné konkrétní stránce) 2024-04-30 23:28:37 +02:00
ed703b10a1 Vize, jak by měly vypadat CSSka konkrétních stránek (a možná CSSka obecně) 2024-04-30 23:27:53 +02:00
Pavel "LEdoian" Turinsky
99c0095465 Fix migrací: přibyl FK na masteru během překopávání 2024-04-30 23:00:28 +02:00
b8b0c5440f Nějaký základ nové verze CSSek 2024-04-30 22:47:03 +02:00
Pavel "LEdoian" Turinsky
676b94ca1b Oprava pořadí migrací v Šifrovačce 2024-04-30 22:27:06 +02:00
Pavel "LEdoian" Turinsky
6ab00392cc Použít přímo nový model
… když už můžeme
2024-04-30 22:20:36 +02:00
Pavel "LEdoian" Turinsky
5070a4d914 Merge branch 'master' into split-apps
Aby se to snáz mergeovalo
2024-04-30 22:20:06 +02:00
Pavel 'LEdoian' Turinsky
77d392f14f fixup! Fix importů v personalni.models.Resitel 2024-04-30 21:36:59 +02:00
Pavel 'LEdoian' Turinsky
03589e484c Fix importů v personalni.models.Resitel
Lokální, přehlédl jsem je…
2024-04-30 21:34:54 +02:00
Pavel 'LEdoian' Turinsky
8ab25545a4 Poznámky k FK/1to1F v creaet 2024-04-30 21:07:11 +02:00
Pavel 'LEdoian' Turinsky
4eb6746c75 Chybějící ForeignKeys/1-2-1Keys 2024-04-30 21:05:10 +02:00
48123a8ce2 Merge branch 'refs/heads/master' into static-files-upgrade
# Conflicts:
#	mamweb/templates/base.html
2024-04-30 21:03:12 +02:00
d09a08f53f fix: Přeskakování v šifrovačce (je tam faaaaakt hodně sněhu) 2024-04-22 23:27:29 +02:00
9a93f95490 fix: Nápovědy v šifrovačce 2024-04-21 21:06:56 +02:00
fd736829c2 Merge pull request 'Nápovědy v šifrovačce' (!46) from sifrovackav2 into master
Reviewed-on: #46
2024-04-21 20:56:41 +02:00
026efe2467 Nápovědy v šifrovačce 2024-04-14 15:09:03 +02:00
a301b122fd Apríl 2024 2024-03-31 19:12:24 +02:00
Pavel 'LEdoian' Turinsky
51f730fe71 typo… 2024-03-26 22:27:48 +01:00
Pavel 'LEdoian' Turinsky
e0d45ddf5d Pomigrační migrace
lol wording
2024-03-26 22:26:33 +01:00
Pavel 'LEdoian' Turinsky
9b12681e45 pomigrační safeguardy 2024-03-26 22:23:40 +01:00
Pavel 'LEdoian' Turinsky
d21ac37187 vazba 2024-03-26 22:19:38 +01:00
Pavel 'LEdoian' Turinsky
49f9b05285 nezapomenout vazby… 2024-03-26 22:19:02 +01:00
Pavel 'LEdoian' Turinsky
a17914f49c Personalni: managed = True + makemigrations 2024-03-26 22:18:25 +01:00
Pavel 'LEdoian' Turinsky
b4b41b9499 Přidání závislostí do delete 2024-03-26 22:18:12 +01:00
Pavel 'LEdoian' Turinsky
1a3dd5d6b2 Jak zařídit, že model bude managed 2024-03-26 22:17:53 +01:00
Pavel 'LEdoian' Turinsky
54db53805a Nezapomenout na vazby 2024-03-26 22:16:59 +01:00
Pavel 'LEdoian' Turinsky
b17b97e4e4 OrgSkolyAutocompleteTestCase: jde vypnout
Když máme jiný model, tak se nepotkají typy dumpů.

Cherry-pickable.
2024-03-26 22:09:12 +01:00
Pavel 'LEdoian' Turinsky
9c1c393395 delete notes 2024-03-19 23:11:15 +01:00
Pavel 'LEdoian' Turinsky
858b5ce054 Smazání, makemigrations 2024-03-19 22:58:15 +01:00
Pavel 'LEdoian' Turinsky
ace30c931e Neimportovat z mazaného modulu 2024-03-19 22:57:51 +01:00
Pavel 'LEdoian' Turinsky
e083f74a83 Další poznámka 2024-03-19 22:51:23 +01:00
Pavel 'LEdoian' Turinsky
53f40e193d Doplnění chybějícího jména tabulky (do předchozí migrace, protože yolo) 2024-03-19 22:48:42 +01:00
Pavel 'LEdoian' Turinsky
9e21b2ca1a relink 2024-03-19 22:47:45 +01:00
Pavel 'LEdoian' Turinsky
1f0e6cccf6 Další poznámky 2024-03-19 22:47:11 +01:00
Pavel 'LEdoian' Turinsky
46f1d3d42d I did something! 2024-03-19 22:15:29 +01:00
5eb936d891 Fix: z flip-card mi vypadlo jejich zarovnání na střed 2024-03-19 21:52:04 +01:00
Pavel 'LEdoian' Turinsky
a23daf8b97 Další pokus o migraci, I guess (po týdnu, lol) 2024-03-19 21:35:52 +01:00
Pavel 'LEdoian' Turinsky
457236c21a Vzor migrace pro create 2024-03-12 22:25:32 +01:00
Pavel 'LEdoian' Turinsky
4182bd542e Oprava nejvíc haluz migrace, wtf, nevím co dělám
Best commit.
2024-03-12 22:15:12 +01:00
Pavel 'LEdoian' Turinsky
17b4a4764c Vyrábíme personální v personálních + oprava sem/models.
Nezapomenout na závislost v migraci!
2024-03-12 22:12:08 +01:00
Pavel 'LEdoian' Turinsky
8cc5864257 Ještě poznámka k related_names 2024-03-12 22:09:52 +01:00
df5ae2c12a Fix: bootstrap upravuje .container 2024-03-12 22:09:32 +01:00
Pavel 'LEdoian' Turinsky
d4b92854c2 Poznámky: nezapomenout na defaultní related_names 2024-03-12 21:57:09 +01:00
Pavel 'LEdoian' Turinsky
559297240d Další poznámky 2024-03-12 21:53:17 +01:00
Pavel 'LEdoian' Turinsky
e3ce5efdd6 Odmanagovaný seminář 2024-03-12 21:47:02 +01:00
Pavel 'LEdoian' Turinsky
a1c1b9f280 Poznámky k odmanagování 2024-03-12 21:46:50 +01:00
Pavel 'LEdoian' Turinsky
b550857a8d Wtf nastaveni.aktualni_cislo nebylo??!?!
Nějaká bullshit migrace, whatever.
2024-03-12 21:45:50 +01:00
Pavel 'LEdoian' Turinsky
ae8040fb04 Přidávám (si) poznámky o tom, jak se to bastlí :-) 2024-03-12 21:30:58 +01:00
Pavel 'LEdoian' Turinsky
7a34fced0a Příprava na zrušení personálních věcí ze Semináře
Modely: Osoba, Řešitel, Organizátor, Škola, Příjemce
2024-03-12 21:27:26 +01:00
81494a7152 Merge pull request 'Fix obálek' (!45) from fix-obalky into master 2024-03-03 23:29:41 +01:00
Pavel "LEdoian" Turinsky
a1000ad2bf Chybová hláška i pro stvrzenky 2024-03-03 23:12:39 +01:00
Pavel "LEdoian" Turinsky
88ae103ec1 Lepší složky i pro stvrzenky 2024-03-03 23:09:18 +01:00
Pavel "LEdoian" Turinsky
213d3cc7b2 Lepší vyrábění dočasných adresářů
Chceme je po sobě nejspíš mazat i když to spadne. Možná to zesložití
vývoj, ale je to odolnější proti náhodnému pádu čehokoliv.
2024-03-03 23:07:51 +01:00
Pavel "LEdoian" Turinsky
4ecd2a7a61 Ještě status kód 2024-03-03 23:06:43 +01:00
Pavel "LEdoian" Turinsky
d2926bd1a7 Správné hledání lišáka pro obálky 2024-03-03 23:05:39 +01:00
Pavel "LEdoian" Turinsky
a59e2f9977 Užitečná chybová stránka pro neúspěšné generování obálek 2024-03-03 22:44:59 +01:00
bd1d04802b Oprava výsledkovek 2024-02-24 11:15:01 +01:00
8cd3250bb0 Zkrocení stylů okolo otáčecích karet 2024-02-24 10:42:54 +01:00
cc09980632 Lepší řešení otáčecích karet na dotykových zařízeních 2024-02-24 08:55:09 +01:00
3363fbdc7c Tohle asi sem 2024-02-24 08:33:08 +01:00
3f8756148d Drobné úpravy CSSek 2024-02-20 19:34:50 +01:00
7b1e3ab58b Rozhození layoutovací @media (styly na menších displejích) 2024-02-20 19:26:57 +01:00
ee7771bdb3 Přesun CSS menu mimo hlavní layout 2024-02-20 19:16:47 +01:00
10c252cd16 Rozházení @media, o kterých je jasné, kam patří 2024-02-20 19:14:17 +01:00
a4a4af5f93 Tohle asi patří sem 2024-02-20 18:52:18 +01:00
5048439aee Nějaké nepoužívané css třídy a hodnoty tagů 2024-02-20 18:46:26 +01:00
14e8520b20 Úprava tabulky odevzdaných = mých řešení 2024-02-20 17:51:01 +01:00
31ee8937f8 Pruhy (local/test/prod) dozadu 2024-02-14 17:24:46 +01:00
d469019c44 Oprava mobilního meníčka na nový bootstrap (ještě jsem zapomněl jednu věc) 2024-02-14 17:00:06 +01:00
a1fa654bec Oprava mobilního meníčka na nový bootstrap 2024-02-14 16:56:47 +01:00
81004d7cdc Upgrade výsledkovky (uchycení druhého sloupce – toho se jménem) 2024-02-14 15:53:09 +01:00
da705927c2 Drobné opravy v tabulce uchycující první řádek a sloupec 2024-02-14 15:46:51 +01:00
ce54684680 Drobné opravy v tabulce uchycující první řádek a sloupec 2024-02-14 15:44:53 +01:00
837986bad8 Drobné opravy v oramovane tabulce a tabulce uchycující první řádek a sloupec 2024-02-14 12:35:44 +01:00
d7195a42f1 Drobné opravy v barevné tabulce a tabulce uchycující první řádek a sloupec 2024-02-14 11:43:28 +01:00
264f4d7646 Chybějící tag v nastylování barevné tabulky 2024-02-14 11:02:00 +01:00
5bf2df563b Rozdělení stylu tabulky došlých řešení na jednotlivé části a použití jinde 2024-02-14 10:58:21 +01:00
6d270b7af5 Ještě jedna drobná úprava stylů výsledkovky 2024-02-14 10:07:46 +01:00
d41eb64ab5 Oprava předchozího commitu (Předělání stylů výsledkovky) 2024-02-14 09:48:59 +01:00
805ed9204c Oprava předchozího commitu (Předělání stylů výsledkovky) 2024-02-14 09:42:08 +01:00
3d2f0f08c3 Předělání stylů výsledkovky 2024-02-14 09:35:49 +01:00
9cca7beba5 Update bootstrapu 2024-02-14 09:19:45 +01:00
c5b81871d2 Přemýšlel jsem, zda apríly nevyhodit do *.js, ale nakonec jen takhto do separátního template 2024-02-14 09:03:15 +01:00
52dc337a20 Dospěl jsem k názoru, že jCarousel se na webu také nepoužívá 2024-02-14 08:41:13 +01:00
eec9abb9c1 Dospěl jsem k názoru, že PrettyPhoto se na webu nepoužívá 2024-02-14 08:32:36 +01:00
c731af9ccd Přesun css galerie do galerie 2024-02-14 08:28:30 +01:00
f5e8f5bb77 První krůčky předělání css 2024-02-13 22:17:41 +01:00
72c20d2b94 Separace prettyPhoto 2024-02-12 19:04:46 +01:00
b2911d5e9f Separace bootstrapu 2024-02-12 18:55:41 +01:00
239a324a19 Ještě lepší vyřešení rozlišení webů 2024-02-05 20:34:25 +01:00
c0a3e3df8f Lepší vyřešení rozlišení webů 2024-02-05 20:08:25 +01:00
b4c693a9ab Uchycení řádku a sloupce v tabulce 2024-02-05 19:50:17 +01:00
c130ab8426 Zrychlení načítání archivu soustředění pro účastníky 2024-01-30 00:11:13 +01:00
a72435dd72 Zrychlení načítání archivu soustředění 2024-01-30 00:02:27 +01:00
8e1a03863f PLS, Windowsy… 2023-12-28 09:50:13 +01:00
b8f2e1da3b Merge pull request 'Odstranění starých dependencí a okomentování většiny stávajících' (!42) from odstraneni_starych_dependenci into master
Reviewed-on: #42
2023-12-18 23:08:56 +01:00
824a4d9eb3 Fixnuto přetékání selectů přes title a login bar 2023-12-18 22:13:17 +01:00
Pavel 'LEdoian' Turinsky
39d618834b fixup! Pokus o hack: při výrobě modelu na něj rovnou přesměrujeme původní contenttype. 2023-12-18 21:32:58 +01:00
46fd51e7d9 Odstraněno sekizai (nepoužívalo se, zbytečně zesložiťuje, jde to dělat i jinak a házelo někdy někde chyby) 2023-12-18 21:24:14 +01:00
Pavel 'LEdoian' Turinsky
f41d5587fc Pokus o hack: při výrobě modelu na něj rovnou přesměrujeme původní contenttype. 2023-12-18 21:21:17 +01:00
8babbd988c A další komentáře 2023-12-11 22:20:38 +01:00
ea5ee85e7a Odstranění django-crispy-forms (nějaké fancy formy, ale vypadá nepoužívaně) 2023-12-11 22:03:42 +01:00
187ca0ec93 Další komentáře v requirements.txt 2023-12-11 22:02:22 +01:00
7a28649436 Odstranění django-flat-theme (dávno, dávno, předávno (Django 1.9) includované do Djanga) 2023-12-11 21:59:13 +01:00
1802e90952 Odstranění django-mptt (knihovna pro stromy v Djangu, např. v adminu, na první pohled nepoužívaná, navíc unmaintained) 2023-12-11 21:51:20 +01:00
0f3874beb5 Nějaké komentáře k requirements.txt 2023-12-11 21:49:14 +01:00
191177aea0 Odstranění traitlets (kontrola typování, ale pokud správně chápu, tak nějakého svého) 2023-12-11 21:33:07 +01:00
Pavel 'LEdoian' Turinsky
37586d7433 Opravení práv
TODO: je na těch ContentTypech navěšené ještě něco dalšího? Pro nastavení asi ne, ale co ostatní aplikace?
2023-12-11 21:31:54 +01:00
3bfdde10e8 Odstranění pexpect (knihovna pro spawnování podprocesů z pythonu, nepoužívaná?) 2023-12-11 21:30:09 +01:00
b5de60d681 Odstranění html5lib (pro parsování html v Pythonu, nepoužívaná, navíc už 3 roky neudržovaná) 2023-12-11 21:26:56 +01:00
0204bd2444 Odstranění pytz (použit pouze v testdatech, ale tam naopak budeme lépe simulovat aktuální stav, když tam nacpeme UTC) 2023-12-11 21:25:36 +01:00
3fd0c7f917 Odstranění six (knihovna pro přechod mezi python2 a python3) 2023-12-11 21:17:12 +01:00
6cb41a1263 Odstranění django comments, data jsou v /akce/mam/www/old_data/django_comments.json.gz 2023-12-11 21:07:18 +01:00
Pavel 'LEdoian' Turinsky
ca0bbb1247 Manage 2023-12-11 20:32:22 +01:00
Pavel 'LEdoian' Turinsky
158c0e4d90 Druhá zapomenutá závislost 2023-12-11 20:28:58 +01:00
Pavel 'LEdoian' Turinsky
f7382fb946 Zapomenutá závislost
Ještě mi chybí jedna :-/
2023-12-11 20:28:10 +01:00
Pavel 'LEdoian' Turinsky
0bbb860b16 Zrušení seminar.Nastaveni 2023-12-11 20:25:33 +01:00
Pavel 'LEdoian' Turinsky
6a5390cdf6 Přidání Nastaveni do various 2023-12-11 20:21:27 +01:00
Pavel 'LEdoian' Turinsky
f197261271 Opravení related_name
Tohle by mělo být nezávislé, nemůžeme vyrobit foreign key na něco, co už existuje…

ERRORS:
seminar.Nastaveni.aktualni_cislo: (fields.E304) Reverse accessor for 'seminar.Nastaveni.aktualni_cislo' clashes with reverse accessor for 'various.Nastaveni.aktualni_cislo'.
	HINT: Add or change a related_name argument to the definition for 'seminar.Nastaveni.aktualni_cislo' or 'various.Nastaveni.aktualni_cislo'.
various.Nastaveni.aktualni_cislo: (fields.E304) Reverse accessor for 'various.Nastaveni.aktualni_cislo' clashes with reverse accessor for 'seminar.Nastaveni.aktualni_cislo'.
	HINT: Add or change a related_name argument to the definition for 'various.Nastaveni.aktualni_cislo' or 'seminar.Nastaveni.aktualni_cislo'.
2023-12-11 20:20:48 +01:00
Pavel 'LEdoian' Turinsky
df2e4f086a Unmanage seminar.Nastaveni
Sry, nebudu to psát česky :-)
2023-12-11 20:20:24 +01:00
ffa0c682f4 Pokus o řešení problémů s výsledkovkou posledního čísla 2023-12-11 20:16:04 +01:00
0fbfb1e3cd Fix „get_app_list() takes 2 positional arguments but 3 were given“ 2023-12-04 10:51:28 +01:00
c55fbb9dca WTF (fix admin soustředění) 2023-11-22 13:42:15 +01:00
MaM Web user
7a4213a61e Jidáš: django chce migraci related_name -> , tak ji dostane 2023-11-20 22:06:42 +01:00
MaM Web user
37437b9674 Jidáš: oprava padání sphinxu (dokumentace) 2023-11-20 21:46:42 +01:00
2afa250df6 Merge branch 'django4' 2023-11-20 20:30:44 +01:00
78866fbc93 Merge pull request 'Neodkazovat neveřejné výsledkovky' (!41) from neodkazovat_neverejne_vysledkovky into master
Reviewed-on: #41
2023-11-20 20:09:22 +01:00
Pavel 'LEdoian' Turinsky
a9a426ca91 Odkaz na výsledkovku a ne na horní část stránky 2023-11-13 20:59:06 +01:00
Pavel 'LEdoian' Turinsky
b456b8c861 Neodkazovat z archivu na výsledkovku, když tam není 2023-11-13 20:58:39 +01:00
6fe9beb0f0 Deprecated věci 2023-10-31 21:20:48 +01:00
f457520d7d Takhle už by šlo pustit (ugettext->gettext, force_text->force_str) 2023-10-30 23:33:13 +01:00
cda00bbc4c Merge pull request 'Lepsi Delani Orgu' (!35) from lepsi-delani-orgu into master
Reviewed-on: #35
2023-10-30 22:21:13 +01:00
Pavel "LEdoian" Turinsky
668546c720 Fix testu 2023-10-30 22:19:50 +01:00
e889620ddb Merge remote-tracking branch 'origin/master' 2023-10-30 21:09:42 +01:00
c673d04082 Merge branch 'prehlednejsi_hodnotitko' 2023-10-30 21:08:02 +01:00
36124fa69e Merge pull request 'Dokumentace závislostí webu' (!38) from doc_prereq into master
Reviewed-on: #38
2023-10-30 20:56:19 +01:00
14b3caaa04 Merge pull request 'Úpravy výsledkovky' (!36) from pregenerovavani-deadlinu into master
Reviewed-on: #36
2023-10-30 20:19:53 +01:00
e9782c73e4 V předchozím commitu bylo špatně trefené {% endif %} 2023-10-24 13:16:48 +02:00
Pavel "LEdoian" Turinsky
93cfa504a8 Nepadat pro hodně nevalidní CSRF
Když to nemá referer, tak je to hodně divné, ale mail o tom nechci.
2023-10-22 15:21:24 +02:00
29b5361545 (sifrovacka) stringifikace správných odpovědí 2023-10-16 20:01:41 +02:00
e589564840 (sifrovacka) odlišení přijmutých a nepřijmutých odpovědí 2023-10-16 19:55:37 +02:00
10c6f4be16 Merge pull request 'sifrovacka' (!40) from sifrovacka into master
Reviewed-on: #40
2023-10-15 20:02:24 +02:00
04e2007c3a (sifrovacka) Méně řádků v tajence v účastnickém formuláři (bez diakritiky bylo přidáno už v předchozím commitu) 2023-10-15 19:55:23 +02:00
1f7b770a5c Odpovědi od účastníků mají nově i timestamp 2023-10-15 19:52:11 +02:00
b8eee44ed0 Hrajeme si se styly. 2023-10-14 12:37:23 +02:00
b36c030af5 Merge branch 'sifrovacka' of gitea.ks.matfyz.cz:mam/mamweb into sifrovacka 2023-10-14 12:28:48 +02:00
dca7a7bedd Trochu přepsány textíky. 2023-10-14 12:28:22 +02:00
c635b0a280 K formuláři mají mít přístup i orgové… 2023-10-14 12:09:14 +02:00
1aa389414d Návrh na webappku na šifrovačku (na Sklené) 2023-10-14 11:26:38 +02:00
MaM Web user
aff9a39262 uwsgi v requirements.txt nevypadá potřebně
a navíc to pak Jidášovi nefunguje, zatím nevím proč.

P.
2023-10-09 23:00:49 +02:00
Pavel "LEdoian" Turinsky
2d416472e8 Tak superuser už může
lol…
2023-10-09 22:50:28 +02:00
Pavel "LEdoian" Turinsky
7e1644d5c1 Parciální fix testu dělání orgů 2023-10-09 22:43:27 +02:00
b69ebcd6b6 U CSRF chyby má být reason asi předvyplněný 2023-10-09 22:29:29 +02:00
Pavel "LEdoian" Turinsky
002e33002c Přegenerovávat výsledkovky nemůže běžný org 2023-10-09 22:25:25 +02:00
205409abc5 Merge pull request 'oprava_chybovych_hlasek' (!39) from oprava_chybovych_hlasek into master
Reviewed-on: #39 (by @ledoian)
2023-10-09 22:19:33 +02:00
68d51a0bf1 Překlep 2023-10-09 22:14:27 +02:00
0e24c1d9ad Komentář k CSRF chybám 2023-10-09 22:04:01 +02:00
29b3271200 CSRF chyba má vrátit 403 2023-10-09 22:02:05 +02:00
60346d6839 Stránka pro CSRF chybu 2023-10-09 21:50:50 +02:00
bb127832dc Oprava „názvu záložky“ při Ojojojojoj 2023-10-09 21:11:12 +02:00
Pavel "LEdoian" Turinsky
1f0d70b31e Dokumentace závislostí
podle dnešní schůzky, zatím velmi předběžná verze
2023-10-02 23:29:51 +02:00
0738aedc9e Oprava pipu instalujícího prereleasy 2023-10-02 21:32:09 +02:00
d006f6ebfa Oprava vytváření galerie (Pillow odstranil něco, co django-imagekit používá) 2023-10-02 10:08:49 +02:00
bfb7a75b97 Oprava vytváření galerie (Pillow odstranil něco, co django-imagekit používá) 2023-10-02 09:28:23 +02:00
MaM Web user
8a473a5097 Popisky v záhlaví výsledkovky
Sice to na mobilu nic moc neudělá, ale aspoň myší se to má šanci používat trochu snáz.
2023-09-20 11:10:55 +02:00
MaM Web user
f14df7b579 Nové kódy úloh ve výsledkovce
(Zprasený commit, má být na jiné větvi, já vím :-D)
2023-09-20 10:47:11 +02:00
MaM Web user
e182785e48 Přidány akce pro přegenerování deadlinů do Admina
Nikdo o tom neví, a když se o tom dozví, tak se buď nic nezmění, nebo to bylo
tak dávno, že si toho nikdo nevšimne :-D Ale na testování se to hodí…

Also: někde jsem přepsal mezery na taby.
2023-09-20 01:11:03 +02:00
fb31b10d66 Oprava odevzdávátka (čtyřrozměrné body) 2023-09-16 12:31:30 +02:00
30ce5a0888 Vynucení UTF-8 (v html a css) 2023-09-06 12:59:04 +02:00
0c90c2bd06 Lepší řazení problémů ve výsledkovce 2023-08-30 22:06:46 +02:00
Pavel "LEdoian" Turinsky
df657953ab Pročištění ALLOWED_HOSTS 2023-08-13 13:52:41 +02:00
527c06d91e Připomenutí orgům, že mají psát webařům. (Malilinkato rozbíjí styl na užších displejích.) 2023-08-12 00:58:09 +02:00
MaM Web user
449a3db56b Odebrání práv na mazání lidí (diff je trochu pokažený, protože save_org_prava nedrží jednotné pořadí, ale co už) 2023-08-10 23:23:48 +02:00
Pavel "LEdoian" Turinsky
3eb9c7998d Zprávy do admina 2023-06-21 15:51:08 +02:00
Pavel "LEdoian" Turinsky
36a7a5af5d Test pro dělání orgů. Nefunguje :-/ 2023-06-21 15:41:36 +02:00
Pavel "LEdoian" Turinsky
844c55d5a7 Oprava drobností 2023-06-21 15:41:17 +02:00
Pavel "LEdoian" Turinsky
2cf4d87c6b Marginální zlepšení vyrábítka orgů
Podle diskuse na Matrixu
2023-06-21 14:45:46 +02:00
Pavel "LEdoian" Turinsky
feffa99d79 Fix typu v hodnotítku 2023-06-20 00:26:40 +02:00
Pavel "LEdoian" Turinsky
d304e46ceb Barvičky jdou vypnout 2023-05-23 00:28:13 +02:00
Pavel "LEdoian" Turinsky
fe144e6de7 Barvičkyyy! 2023-05-22 23:58:04 +02:00
Pavel "LEdoian" Turinsky
86f6d47f10 Součty
Hnusně, ale přece…
2023-05-22 23:30:57 +02:00
Pavel "LEdoian" Turinsky
bef932345c Kladivoo! 2023-05-22 22:47:13 +02:00
Pavel "LEdoian" Turinsky
468443e062 V tabulce potřebujeme znát i Hodnocení, kvůli bodům… 2023-05-22 22:46:47 +02:00
Pavel "LEdoian" Turinsky
53032a49d8 Neumím psát filtry.
todo squash
2023-05-22 22:23:15 +02:00
Pavel "LEdoian" Turinsky
8f2aa72f6d Fix zapomenuté fieldy v tabulce
TODO: rebase squash
2023-05-22 22:21:42 +02:00
Pavel "LEdoian" Turinsky
ed5e7a1d9f Template pro zobrazení řešení přímo v tabulce
Snad, ještě jsem to netestoval :-)
2023-05-22 22:16:02 +02:00
Pavel "LEdoian" Turinsky
96bc21a727 Zrušen SourhnReseni
Už není potřeba, v tabulce stejně jsou už přímo řešení.
2023-05-22 22:15:27 +02:00
Pavel "LEdoian" Turinsky
659ad62b52 Ještě docstring k ReseniProblemuView 2023-05-22 22:15:07 +02:00
Pavel "LEdoian" Turinsky
dd49c0e1ba Malá úprava TabulkaOdevzdanychReseniView.get_context_data.pridej_reseni 2023-05-22 21:48:33 +02:00
Pavel "LEdoian" Turinsky
dd5d8886ee Trocha popisu TabulkaOdevzdanychReseniView, ať se v tom dá vyznat 2023-05-22 21:27:04 +02:00
537 changed files with 32302 additions and 20784 deletions

15
.gitignore vendored
View file

@ -25,12 +25,21 @@ TODO
# .htpasswd kvůli přihlášení # .htpasswd kvůli přihlášení
.htpasswd .htpasswd
# .htpasswd pro AESOPa
/.htpasswd-aesop
# reversion kvůli historii objektů v reversion # reversion kvůli historii objektů v reversion
**/reversion **/reversion
# pro lidi, co programují v nástrojích od JetBrains
.idea
# dokumentace # dokumentace
docs/_build docs/_build
docs/modules docs/modules
# logy týracího skriptu (./checklinks.sh)
/wget.log.*
# pro lidi, co programují v nástrojích od JetBrains
.idea
# Mac users
.DS_Store

3
aesop/__init__.py Normal file
View file

@ -0,0 +1,3 @@
"""
Obsahuje vše, co se týká aesopu (exportu, který po nás vyžaduje OPMK).
"""

5
aesop/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class AesopConfig(AppConfig):
name = 'aesop'
verbose_name = 'Export do AESOPa'

30
aesop/ovvpfile.py Normal file
View file

@ -0,0 +1,30 @@
from django.http import HttpResponse
from django.utils.encoding import force_str
class OvvpFile:
def __init__(self):
# { header: value, ... }
self.headers = {}
# [ 'column-name', ... ]
self.columns = []
# [ { column: value, ...}, ...]
self.rows = []
def to_lines(self):
# header
for hk in sorted(self.headers.keys()):
yield f'{hk}\t{self.headers[hk]}\n'
yield '\n'
# columns
yield '\t'.join(self.columns) + '\n'
# rows
for r in self.rows:
yield '\t'.join([force_str(r[c]) for c in self.columns]) + '\n'
def to_string(self):
return ''.join(self.to_lines())
# Pozn: tohle je ta jediná funkce, která se reálně používá…
def to_HttpResponse(self):
return HttpResponse(self.to_string(), content_type='text/plain; charset=utf-8')

20
aesop/urls.py Normal file
View file

@ -0,0 +1,20 @@
from django.urls import path
from aesop import views
urlpatterns = [
path(
'aesop-export/mam-rocnik-<int:prvni_rok>.csv',
views.ExportRocnikView.as_view(),
name='aesop_export_rocnik'
),
path(
'aesop-export/mam-sous-<str:datum_zacatku>.csv',
views.ExportSousView.as_view(),
name='aesop_export_sous'
),
path(
'aesop-export/index.csv',
views.ExportIndexView.as_view(),
name='aesop_export_index'
),
]

16
aesop/utils.py Normal file
View file

@ -0,0 +1,16 @@
import datetime
from django.utils.encoding import force_str
from aesop.ovvpfile import OvvpFile
def default_ovvpfile(event, rocnik):
of = OvvpFile()
of.headers['version'] = '1'
of.headers['event'] = event
of.headers['year'] = force_str(rocnik.prvni_rok)
of.headers['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
of.headers['id-scope'] = 'mam'
of.headers['id-generation'] = '1'
return of

97
aesop/views.py Normal file
View file

@ -0,0 +1,97 @@
import django
from django.shortcuts import get_object_or_404
from django.http import HttpResponse
from django.urls import reverse
from django.views import generic
from django.utils.encoding import force_str
from .utils import default_ovvpfile
from soustredeni.models import Soustredeni
from tvorba.models import Rocnik
from vysledkovky import utils
from tvorba.utils import aktivniResitele
class ExportIndexView(generic.View):
def get(self, request):
ls = []
for r in Rocnik.objects.filter(exportovat = True):
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('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')
class ExportSousView(generic.View):
def get(self, request, datum_zacatku=None):
try:
dz = django.utils.dateparse.parse_date(datum_zacatku)
except:
dz = None
if dz is None:
raise django.http.Http404()
s = get_object_or_404(Soustredeni, datum_zacatku=dz, exportovat=True)
akce = {Soustredeni.TYP_JARNI: 'MaM.sous.jaro',
Soustredeni.TYP_PODZIMNI: 'MaM.sous.podzim',
Soustredeni.TYP_VIKEND: 'MaM.vikend',
}[s.typ]
of = default_ovvpfile(akce, s.rocnik)
of.headers['x-event-begin'] = s.datum_zacatku.isoformat()
of.headers['x-event-end'] = s.datum_konce.isoformat()
of.headers['x-event-location'] = s.misto
of.headers['comment'] = u'MaM-Web export ucastniku soustredeni v {x-event-location} od {x-event-begin} do {x-event-end}'.format(**of.headers)
of.columns = ['id', 'name', 'surname', 'gender', 'email', 'end-year', 'school', 'school-name']
for u in s.ucastnici.all():
of.rows.append(u.export_row())
return of.to_HttpResponse()
# POZOR! Předělání na nový model neotestováno v reálu (ale zase jen drobné změny)
class ExportRocnikView(generic.View):
def get(self, request, prvni_rok=None):
try:
pr = int(prvni_rok)
except:
pr = None
if pr is None:
raise django.http.Http404()
rocnik = get_object_or_404(Rocnik, prvni_rok=pr, exportovat=True)
cislo = rocnik.posledni_zverejnena_vysledkovka_cislo()
resitele = aktivniResitele(cislo, True)
slovnik_body = utils.secti_body_za_rocnik(cislo, resitele, False)
setrizeni_resitele, body = utils.setrid_resitele_a_body(slovnik_body)
of = default_ovvpfile('MaM.rocnik', rocnik)
of.headers['comment'] = u'MaM-Web export aktivnich resitelu rocniku {rocnik} do cisla {cislo}'.format(rocnik=rocnik, cislo=cislo)
of.columns = ['id', 'name', 'surname', 'gender', 'born', 'email', 'end-year',
'street', 'town', 'postcode', 'country', 'spam-flag', 'spam-date',
'school', 'school-name', 'points', 'rank',]
resitele_slovnik = {}
for r in resitele:
resitele_slovnik[r.id] = r
# počítání pořadí řešitelů
posledni_body = 100000
posledni_poradi = 0
for i in range(len(setrizeni_resitele)):
rd = resitele_slovnik[setrizeni_resitele[i]].export_row()
if posledni_body > body[i]:
posledni_body = body[i]
posledni_poradi = i + 1
rd['rank'] = posledni_poradi
rd['points'] = body[i]
of.rows.append(rd)
return of.to_HttpResponse()

View file

@ -3,3 +3,4 @@ from django.apps import AppConfig
class ApiConfig(AppConfig): class ApiConfig(AppConfig):
name = 'api' name = 'api'
verbose_name = 'Různá webová API'

View file

@ -1,9 +1,9 @@
from django.test import TestCase from django.test import TestCase, tag
from django.urls import reverse from django.urls import reverse
from personalni.models import Skola from personalni.models import Skola
import seminar.views as v
from personalni.utils import sync_skoly from personalni.utils import sync_skoly
@tag('stejny-model-na-produkci')
class OrgSkolyAutocompleteTestCase(TestCase): class OrgSkolyAutocompleteTestCase(TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):

View file

@ -4,18 +4,18 @@ from personalni.utils import org_required
urlpatterns = [ urlpatterns = [
# Export škol # Export škol
path('export/skoly/', views.exportSkolView, name='export_skoly'), path('api/export/skoly/', views.exportSkolView, name='export_skoly'),
# Autocomplete # Autocomplete
path('autocomplete/skola/', views.SkolaAutocomplete.as_view(), name='autocomplete_skola'), path('api/autocomplete/skola/', views.SkolaAutocomplete.as_view(), name='autocomplete_skola'),
path('autocomplete/resitel/', org_required(views.ResitelAutocomplete.as_view()), name='autocomplete_resitel'), path('api/autocomplete/resitel/', org_required(views.ResitelAutocomplete.as_view()), name='autocomplete_resitel'),
path('autocomplete/resitel_public/', views.PublicResitelAutocomplete.as_view(), name='autocomplete_resitel_public'), path('api/autocomplete/resitel_public/', views.PublicResitelAutocomplete.as_view(), name='autocomplete_resitel_public'),
path('autocomplete/problem/odevzdatelny', views.OdevzdatelnyProblemAutocomplete.as_view(), name='autocomplete_problem_odevzdatelny'), path('api/autocomplete/problem/odevzdatelny', views.OdevzdatelnyProblemAutocomplete.as_view(), name='autocomplete_problem_odevzdatelny'),
path('autocomplete/problem/vsechny', views.ProblemAutocomplete.as_view(), name='autocomplete_problem'), path('api/autocomplete/problem/vsechny', views.ProblemAutocomplete.as_view(), name='autocomplete_problem'),
# Ceka na autocomplete v3 # Ceka na autocomplete v3
# path('autocomplete/organizatori/', # path('autocomplete/organizatori/',
# org_member_required(views.OrganizatorAutocomplete.as_view()), # org_member_required(views.OrganizatorAutocomplete.as_view()),
# name='seminar_autocomplete_organizator') # name='autocomplete_organizator')
] ]

View file

@ -7,7 +7,7 @@ from django.db.models import Q
from personalni.models import Skola, Resitel from personalni.models import Skola, Resitel
from tvorba.models import Problem from tvorba.models import Problem
from seminar.models.nastaveni import Nastaveni from various.models import Nastaveni
from .helpers import LoginRequiredAjaxMixin from .helpers import LoginRequiredAjaxMixin
# TODO filosofie - zkratky, jak v databázi, tak ve vyhledávání (SPŠE, GASOŠ, Kpt., soukr) # TODO filosofie - zkratky, jak v databázi, tak ve vyhledávání (SPŠE, GASOŠ, Kpt., soukr)

View file

@ -1,4 +1,4 @@
from personalni.models import Skola import personalni.models as m
from django.core import serializers as ser from django.core import serializers as ser
from django.http import HttpResponse from django.http import HttpResponse
def exportSkolView(request): def exportSkolView(request):
@ -8,7 +8,7 @@ def exportSkolView(request):
# Některé fieldy nechceme: Kontaktní osoby, AESOP ID, org poznámky. # Některé fieldy nechceme: Kontaktní osoby, AESOP ID, org poznámky.
fields = ('id', 'izo', 'nazev', 'kratky_nazev', 'ulice', 'mesto', 'psc', 'stat', 'je_zs', 'je_ss') fields = ('id', 'izo', 'nazev', 'kratky_nazev', 'ulice', 'mesto', 'psc', 'stat', 'je_zs', 'je_ss')
# TODO: Použít JSONL, aby protistrana mohla číst po řádkách a nesežralo to tunu paměti úplně hned # TODO: Použít JSONL, aby protistrana mohla číst po řádkách a nesežralo to tunu paměti úplně hned
skoly_json = ser.serialize("json", Skola.objects.all(), fields=fields) skoly_json = ser.serialize("json", m.Skola.objects.all(), fields=fields)
response = HttpResponse( response = HttpResponse(
content = skoly_json, content = skoly_json,
content_type = 'text/json', content_type = 'text/json',

17
convert_spaces_to_tabs.sh Executable file
View file

@ -0,0 +1,17 @@
#!/bin/sh
if test "$#" -lt 1
then
echo "Usage: $0 file ..."
exit 2
fi
for file in "$@"
do
# Do the sed magic: keep replacing 4 spaces at the begining of line
sed -i -re '
: loop
s/^( *) /\1 /
t loop
' "$file"
done

View file

@ -78,11 +78,6 @@
"header_fotky", "header_fotky",
"fotkaheader" "fotkaheader"
], ],
[
"delete_fotkaheader",
"header_fotky",
"fotkaheader"
],
[ [
"view_fotkaheader", "view_fotkaheader",
"header_fotky", "header_fotky",
@ -98,11 +93,6 @@
"header_fotky", "header_fotky",
"fotkaurlvazba" "fotkaurlvazba"
], ],
[
"delete_fotkaurlvazba",
"header_fotky",
"fotkaurlvazba"
],
[ [
"view_fotkaurlvazba", "view_fotkaurlvazba",
"header_fotky", "header_fotky",
@ -168,6 +158,26 @@
"korektury", "korektury",
"oprava" "oprava"
], ],
[
"add_novinky",
"novinky",
"novinky"
],
[
"change_novinky",
"novinky",
"novinky"
],
[
"delete_novinky",
"novinky",
"novinky"
],
[
"view_novinky",
"novinky",
"novinky"
],
[ [
"change_organizator", "change_organizator",
"personalni", "personalni",
@ -218,36 +228,36 @@
"personalni", "personalni",
"resitel" "resitel"
], ],
[
"add_skola",
"personalni",
"skola"
],
[ [
"change_skola", "change_skola",
"personalni", "personalni",
"skola" "skola"
], ],
[
"delete_skola",
"personalni",
"skola"
],
[ [
"view_skola", "view_skola",
"personalni", "personalni",
"skola" "skola"
], ],
[
"add_hlasovani",
"prednasky",
"hlasovani"
],
[
"change_hlasovani",
"prednasky",
"hlasovani"
],
[
"delete_hlasovani",
"prednasky",
"hlasovani"
],
[ [
"view_hlasovani", "view_hlasovani",
"prednasky", "prednasky",
"hlasovani" "hlasovani"
], ],
[
"view_hlasovanioznalostech",
"prednasky",
"hlasovanioznalostech"
],
[ [
"add_prednaska", "add_prednaska",
"prednasky", "prednasky",
@ -289,34 +299,24 @@
"seznam" "seznam"
], ],
[ [
"change_nastaveni", "add_znalost",
"seminar", "prednasky",
"nastaveni" "znalost"
], ],
[ [
"view_nastaveni", "change_znalost",
"seminar", "prednasky",
"nastaveni" "znalost"
], ],
[ [
"add_novinky", "delete_znalost",
"seminar", "prednasky",
"novinky" "znalost"
], ],
[ [
"change_novinky", "view_znalost",
"seminar", "prednasky",
"novinky" "znalost"
],
[
"delete_novinky",
"seminar",
"novinky"
],
[
"view_novinky",
"seminar",
"novinky"
], ],
[ [
"add_konfera", "add_konfera",
@ -418,6 +418,46 @@
"soustredeni", "soustredeni",
"soustredeni_ucastnici" "soustredeni_ucastnici"
], ],
[
"add_tag",
"taggit",
"tag"
],
[
"change_tag",
"taggit",
"tag"
],
[
"delete_tag",
"taggit",
"tag"
],
[
"view_tag",
"taggit",
"tag"
],
[
"add_taggeditem",
"taggit",
"taggeditem"
],
[
"change_taggeditem",
"taggit",
"taggeditem"
],
[
"delete_taggeditem",
"taggit",
"taggeditem"
],
[
"view_taggeditem",
"taggit",
"taggeditem"
],
[ [
"add_cislo", "add_cislo",
"tvorba", "tvorba",
@ -468,11 +508,6 @@
"tvorba", "tvorba",
"deadline" "deadline"
], ],
[
"delete_deadline",
"tvorba",
"deadline"
],
[ [
"view_deadline", "view_deadline",
"tvorba", "tvorba",
@ -577,6 +612,26 @@
"view_uloha", "view_uloha",
"tvorba", "tvorba",
"uloha" "uloha"
],
[
"add_nastaveni",
"various",
"nastaveni"
],
[
"change_nastaveni",
"various",
"nastaveni"
],
[
"delete_nastaveni",
"various",
"nastaveni"
],
[
"view_nastaveni",
"various",
"nastaveni"
] ]
] ]
}, },

View file

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

3
deploy_v2/README Normal file
View file

@ -0,0 +1,3 @@
Tahle slozka obsahuje vsechny detaily a popisy, jak nasadit "druhou verzi" M&M webu.
TODO: chybi tu popis na zprovozneni flatpages, na loaddata &c.

BIN
deploy_v2/Schema_new.dia Normal file

Binary file not shown.

513
deploy_v2/db_compare.py Normal file
View file

@ -0,0 +1,513 @@
#!/usr/bin/env python3
import psycopg2
import psycopg2.extras
OLD_DB = "mam_old"
NEW_DB = "mamweb"
oldconn = psycopg2.connect(f"dbname={OLD_DB}")
newconn = psycopg2.connect(f"dbname={NEW_DB}")
oldcur = oldconn.cursor(cursor_factory=psycopg2.extras.DictCursor)
newcur = newconn.cursor(cursor_factory=psycopg2.extras.DictCursor)
# Uses global variables oldcur, newcur!
def execute_simple(old_query, new_query=None):
if new_query is None:
new_query = old_query
oldcur.execute(old_query)
newcur.execute(new_query)
if oldcur.rowcount != newcur.rowcount:
raise ValueError(f"Queries '{old_query}' and '{new_query}' returned different number of rows ({oldcur.rowcount} and {newcur.rowcount})")
return(oldcur.fetchall(), newcur.fetchall())
def check_same(old_row, new_row, old_fields, new_fields=None):
if type(old_fields) != list:
old_fields = [old_fields]
if new_fields is None:
new_fields = old_fields
fields = zip(old_fields, new_fields)
for old_field, new_field in fields:
if old_row[old_field] == new_row[new_field]:
continue
raise ValueError(f"Fields '{old_field}'({old_row[old_field]}) and '{new_field}'({new_row[new_field]}) differs for rows \n'{old_row}' and \n'{new_row}'")
return True
def get_user_id_for_org_id(org_id):
query = """SELECT auth_user.id FROM auth_user
INNER JOIN seminar_osoby ON seminar_osoby.user_id = auth_user.id
INNER JOIN seminar_organizator ON seminar_organizator.osoba_id = seminar_osoby.id
WHERE seminar_organizator.id = %s """
newcur.execute(query,(org_id,))
return newcur.fetchone()['id']
def check_skola():
old_query = "SELECT * FROM seminar_skoly ORDER BY id"
old_res, new_res = execute_simple(old_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n,['id','aesop_id','izo','nazev','kratky_nazev','ulice','mesto','psc','stat','je_zs','je_ss','poznamka'])
def check_resitel():
old_query = 'SELECT * FROM seminar_resitele ORDER BY id'
new_query = '''SELECT seminar_resitele.id, skola_id, rok_maturity, zasilat, seminar_resitele.poznamka,
o.jmeno AS jmeno, o.prijmeni AS prijmeni, o.user_id AS user_id, o.pohlavi_muz AS pohlavi_muz, o.email AS email, o.telefon AS telefon, o.datum_narozeni AS datum_narozeni,
o.datum_souhlasu_udaje AS datum_souhlasu_udaje, o.datum_souhlasu_zasilani AS datum_souhlasu_zasilani, o.datum_registrace AS datum_prihlaseni, o.ulice AS ulice, o.mesto AS mesto, o.psc AS psc, o.stat AS stat
FROM seminar_resitele JOIN seminar_osoby AS o ON seminar_resitele.osoba_id = o.id ORDER BY seminar_resitele.id'''
old_res, new_res = execute_simple(old_query,new_query)
res = zip(old_res,new_res)
fields_osoba = [
'jmeno',
'prijmeni',
'user_id',
'pohlavi_muz',
#'email', #vyreseno separatne
'telefon',
'datum_narozeni',
'datum_souhlasu_udaje',
'datum_souhlasu_zasilani',
'datum_prihlaseni',
'ulice',
'mesto',
'psc',
'stat',
]
fields_keep = [
'id',
'skola_id',
'rok_maturity',
'zasilat',
'poznamka',
]
fields = fields_keep+fields_osoba
for o,n in res:
check_same(o,n,fields)
if o['email'] != n['email'] and o['email'] != '':
print(f"WARNING: Emails differ: old: {o['email']}, new: {n['email']}")
def check_reseni():
# Migrace 0058 zamerne meni (zmensuje) pocet reseni, aby kazdy clanek mel
# jen jedno reseni (s vice resiteli, coz postaru neslo)
# Kvuli tomu je potreba kontrolovat dve veci:
# 1) Ze kazdy resitel dostal za kazdy problem spravne bodu
# 2) Ze detaily reseni zustaly zachovany
# Cast 1)
old_query = 'SELECT * FROM seminar_reseni ORDER BY problem_id, resitel_id, body, timestamp'
new_query = '''SELECT seminar_reseni.id, forma, seminar_reseni.poznamka, cas_doruceni, hodnoceni.problem_id AS problem_id, hodnoceni.body AS body, hodnoceni.cislo_body_id AS cislo_body_id, res.id AS resitel_id
FROM seminar_reseni
JOIN seminar_hodnoceni AS hodnoceni ON seminar_reseni.id = hodnoceni.reseni_id
JOIN seminar_reseni_resitele AS rr ON seminar_reseni.id = rr.reseni_id
JOIN seminar_resitele AS res ON res.id = rr.resitele_id
ORDER BY problem_id, resitel_id, body, cas_doruceni'''
# Po spojeni nekterych problemu se lisi casy doruceni a poznamky, proto je nebudeme kontrolovat (jde v podstate o triviality, tak je to snad jedno)
same_fields = ['forma', 'problem_id', 'body', 'cislo_body_id', 'resitel_id']
renamed_fields = [
#('timestamp', 'cas_doruceni'),
]
old_fields = same_fields + [f[0] for f in renamed_fields]
new_fields = same_fields + [f[1] for f in renamed_fields]
old_res, new_res = execute_simple(old_query, new_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n,old_fields, new_fields)
# Cast 2)
# Query se lisi tim, ze uz nejoinujeme resitele.
old_query = 'SELECT * FROM seminar_reseni ORDER BY id'
new_query = '''SELECT seminar_reseni.id, forma, poznamka, cas_doruceni AS timestamp, h.problem_id AS problem_id, h.body AS body, h.cislo_body_id AS cislo_body_id
FROM seminar_reseni
JOIN seminar_hodnoceni AS h ON h.reseni_id = seminar_reseni.id
ORDER BY id'''
# execute_simple kontroluje stejnost poctu radku, to nechceme.
oldcur.execute(old_query)
newcur.execute(new_query)
old_res, new_res = oldcur.fetchall(), newcur.fetchall()
# Zkontrolujeme, ze pro kazde nove reseni ma stare reseni spravna data.
new_ids = [n['id'] for n in new_res]
spravna_old = list(filter(lambda o: o['id'] in new_ids, old_res))
res = zip(spravna_old,new_res)
for o,n in res:
# Tady by se poznamky i timestampy mely zachovat
# Z nejakeho duvodu se ale poznamky lisi ve whitespace, tak je zkontrolujeme separatne
check_same(o,n,['id', 'forma', 'timestamp', 'problem_id', 'body', 'cislo_body_id'])
old_pozn = o['poznamka'].strip()
new_pozn = n['poznamka'].strip()
if old_pozn != new_pozn:
raise ValueError('Poznamky se lisi pro radky {dict(o)} a {dict(n)}')
def check_organizator():
old_query = 'SELECT * FROM seminar_organizator ORDER BY id'
new_query = '''SELECT seminar_organizator.id AS id, studuje, strucny_popis_organizatora, users.id AS uid, osoba.prezdivka AS o_prezdivka, osoba.foto AS o_foto, organizuje_od, organizuje_do
FROM seminar_organizator
JOIN seminar_osoby AS osoba ON osoba_id = osoba.id
JOIN auth_user AS users ON osoba.user_id = users.id
ORDER BY seminar_organizator.id'''
same_fields = ['studuje', 'strucny_popis_organizatora']
renamed_fields = [
('user_id', 'uid'),
#('prezdivka', 'o_prezdivka'),
('foto', 'o_foto'),
]
old_fields = same_fields + [f[0] for f in renamed_fields]
new_fields = same_fields + [f[1] for f in renamed_fields]
old_res, new_res = execute_simple(old_query,new_query)
res = zip(old_res, new_res)
for o,n in res:
check_same(o,n,old_fields, new_fields)
# organizuje od, do:
# Migrace prirazuje aktualni casovou zonu, takze chceme tady rucne vynutit CET.
from datetime import timedelta, timezone
cet = timezone(timedelta(hours=1))
if o['organizuje_od_roku'] is None and n['organizuje_od'] is None:
pass
elif o['organizuje_od_roku'] != n['organizuje_od'].astimezone(cet).year:
raise ValueError(f'Not matching organizuje_od for org id={o["id"]}: old {o["organizuje_od_roku"]}, new {n["organizuje_od"]}')
if o['organizuje_do_roku'] is None and n['organizuje_do'] is None:
pass
elif o['organizuje_do_roku'] != n['organizuje_do'].astimezone(cet).year:
raise ValueError(f'Not matching organizuje_do for org id={o["id"]}: old {o["organizuje_do_roku"]}, new {n["organizuje_do"]}')
if o['prezdivka'] == n['o_prezdivka']:
continue
if o['prezdivka'] is None and n['o_prezdivka'] == '':
continue
raise ValueError(f'Not matching prezdivka for org id={o["id"]}: old {o["prezdivka"]}, new {n["o_prezdivka"]}')
def check_rocnik():
old_query = "SELECT * FROM seminar_rocniky ORDER BY id"
old_res, new_res = execute_simple(old_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n,['id','prvni_rok', 'rocnik', 'exportovat'])
def check_cislo():
old_query = "SELECT * FROM seminar_cisla ORDER BY id"
old_res, new_res = execute_simple(old_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n, ['id','rocnik_id','cislo', 'datum_vydani','datum_deadline','verejne','poznamka','pdf'],
['id','rocnik_id','poradi','datum_vydani','datum_deadline','verejne','poznamka','pdf'])
def check_priloha_reseni():
old_query = "SELECT * FROM seminar_priloha_reseni"
old_res, new_res = execute_simple(old_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n, ['id','reseni_id', 'timestamp', 'soubor', 'poznamka'],
['id','reseni_id', 'vytvoreno', 'soubor', 'poznamka'])
def check_soustredeni():
old_query = "SELECT * FROM seminar_soustredeni ORDER BY id"
old_res, new_res = execute_simple(old_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n,['id','rocnik_id','datum_zacatku','datum_konce','verejne','misto','text','typ','exportovat'])
#Kontrola ucasnici, organizatori v samostatnych funkcich
def check_soustredeni_ucastnici():
old_query = "SELECT * FROM seminar_soustredeni_ucastnici ORDER BY id"
old_res, new_res = execute_simple(old_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n,['id','resitel_id','soustredeni_id','poznamka'])
def check_soustredeni_organizatori():
old_query = "SELECT * FROM seminar_soustredeni_organizatori ORDER BY id"
old_res, new_res = execute_simple(old_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n,['id','organizator_id','soustredeni_id','poznamka'])
def check_nastaveni():
old_query = "SELECT * FROM seminar_nastaveni ORDER BY id"
old_res, new_res = execute_simple(old_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n,['id','aktualni_cislo_id'])
def check_novinky():
old_query = "SELECT * FROM seminar_novinky ORDER BY id"
old_res, new_res = execute_simple(old_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n,['id','datum','text','obrazek','zverejneno'])
if get_user_id_for_org_id(n['autor_id']) != o['autor_id']:
raise ValueError("Nesedi autori u novinek")
def check_pohadka():
old_query = "SELECT * FROM seminar_pohadky ORDER BY id"
new_query = """SELECT sp.id AS id, sp.autor_id AS autor_id, sp.vytvoreno AS vytvoreno, snp.treenode_ptr_id AS treenode_ptr_id, st.na_web AS text,
zn_pred.uloha_id AS uloha_pred, zn_po.uloha_id AS uloha_po
FROM seminar_pohadky AS sp
-- Text pohádky
INNER JOIN seminar_nodes_pohadka AS snp ON sp.id = snp.pohadka_id
INNER JOIN seminar_nodes_treenode AS snt ON snt.id = snp.treenode_ptr_id
INNER JOIN seminar_nodes_obsah AS sno ON sno.treenode_ptr_id = snt.first_child_id
INNER JOIN seminar_texty AS st ON sno.text_id = st.id
-- Predchozí úloha
LEFT OUTER JOIN seminar_nodes_treenode AS ztn_pred ON ztn_pred.succ_id = snt.id
LEFT OUTER JOIN seminar_nodes_uloha_zadani AS zn_pred ON zn_pred.treenode_ptr_id = ztn_pred.id
-- Následující úloha
LEFT OUTER JOIN seminar_nodes_uloha_zadani AS zn_po ON zn_po.treenode_ptr_id = snt.succ_id
ORDER BY sp.id"""
old_res, new_res = execute_simple(old_query,new_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n,['id','timestamp','text'],['id','vytvoreno','text'])
if o['autor_id'] is not None:
if get_user_id_for_org_id(n['autor_id']) != o['autor_id']:
raise ValueError("Nesedi autori u pohadky")
# Správné úlohy
# NOTE: o['pred'] rika, zda je pohadka pred ulohou, nikoliv zda je relevantni uloha pred pohadkou!
spravny_klic = 'uloha_po' if o['pred'] else 'uloha_pred'
if o['uloha_id'] != n[spravny_klic]:
raise ValueError(f"Pohádka přidružená ke špatné úloze! old: {o['uloha_id']}, new: {n[spravny_klic]}, pozice: {spravny_klic}")
# Problémy jsou rozdělené podle typů:
def check_problem_common():
old_query = "SELECT id, nazev, stav, kod, autor_id, text_org, timestamp, typ FROM seminar_problemy ORDER BY id"
new_query = """SELECT sp.id AS id, sp.nazev AS nazev, sp.stav AS stav, sp.kod AS kod, au.id AS autor_id, sp.poznamka AS poznamka, sp.vytvoreno AS vytvoreno
FROM seminar_problemy AS sp
LEFT OUTER JOIN seminar_organizator AS so ON sp.autor_id = so.id
LEFT OUTER JOIN seminar_osoby AS sos ON so.osoba_id = sos.id
LEFT OUTER JOIN auth_user AS au ON sos.user_id = au.id
ORDER BY sp.id"""
same_fields = ['id', 'nazev', 'stav', 'autor_id', 'kod']
renamed_fields = [
('text_org', 'poznamka'),
('timestamp', 'vytvoreno'),
]
old_fields = same_fields + [f[0] for f in renamed_fields]
new_fields = same_fields + [f[1] for f in renamed_fields]
old_res, new_res = execute_simple(old_query,new_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n, old_fields, new_fields)
# Opravovatelé
# Po staru byli opravovatele organizatori, takze je potreba je dohledat.
old_query = """SELECT seminar_problemy.id, org.id AS opravovatel_id FROM seminar_problemy
JOIN seminar_organizator AS org ON seminar_problemy.opravovatel_id = org.user_id;"""
new_query = "SELECT problem_id, organizator_id FROM seminar_problemy_opravovatele"
# Simple cursors
#oldcur = oldconn.cursor()
oldcur.execute(old_query)
old_results = oldcur.fetchall()
#newcur = newconn.cursor()
newcur.execute(new_query)
new_results = newcur.fetchall()
for oldr in old_results:
if oldr not in new_results:
raise ValueError(f'Opravovatel pair {oldr} not found in new db.')
# Zaměření se vyřeší okometricky (#1186)
def check_uloha():
old_query = "SELECT * FROM seminar_problemy WHERE typ = 'uloha' ORDER BY id"
new_query = """SELECT cislo_zadani_id, cislo_reseni_id, problem_ptr_id, max_body, COALESCE(uzt.na_web, '') AS text_zadani, COALESCE(uvt.na_web, '') AS text_reseni, cislo_deadline_id
FROM seminar_ulohy
-- Problém:
JOIN seminar_problemy AS problem ON problem_ptr_id = problem.id
-- Text zadání:
-- ZadaniNode a VzorakNode maji existovat vzdy, ale obsah nemusi (pokud ho nemaji)
INNER JOIN seminar_nodes_uloha_zadani AS uzn ON problem.id = uzn.uloha_id
INNER JOIN seminar_nodes_treenode AS uztn ON uztn.id = uzn.treenode_ptr_id
LEFT OUTER JOIN seminar_nodes_obsah AS uzo ON uzo.treenode_ptr_id = uztn.first_child_id
LEFT OUTER JOIN seminar_texty AS uzt ON uzo.text_id = uzt.id
-- Text vzoráku:
INNER JOIN seminar_nodes_uloha_vzorak AS uvn ON problem.id = uvn.uloha_id
INNER JOIN seminar_nodes_treenode AS uvtn ON uvtn.id = uvn.treenode_ptr_id
LEFT OUTER JOIN seminar_nodes_obsah AS uvo ON uvo.treenode_ptr_id = uvtn.first_child_id
LEFT OUTER JOIN seminar_texty AS uvt ON uvo.text_id = uvt.id
ORDER BY problem_ptr_id"""
same_fields = ['cislo_zadani_id', 'cislo_reseni_id', 'text_zadani', 'text_reseni']
renamed_fields = [
('id', 'problem_ptr_id'),
('body', 'max_body'),
]
old_fields = same_fields + [f[0] for f in renamed_fields]
new_fields = same_fields + [f[1] for f in renamed_fields]
old_res, new_res = execute_simple(old_query, new_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n, old_fields, new_fields)
# Datum deadline vypadá prázdně, tak to budeme předpokládat.
if n['cislo_deadline_id'] is not None:
raise ValueError("Úloha má deadline.")
def check_tema():
old_query = """SELECT text_zadani, text_reseni, typ, c.rocnik_id AS rocnik_id
FROM seminar_problemy
LEFT OUTER JOIN seminar_cisla AS c ON c.id = cislo_zadani_id
WHERE typ IN ('tema', 'serial')
ORDER BY seminar_problemy.id"""
new_query = """SELECT tema_typ, COALESCE(zad_text.na_web, '') AS text_zadani, COALESCE(res_text.na_web, '') AS text_reseni, rn.rocnik_id AS rocnik_id
FROM seminar_temata
-- Problém:
JOIN seminar_problemy AS problem ON problem_ptr_id = problem.id
-- Text:
-- TvCNode dva potomky, oba TextNode. První drží původní text zadání, druhý řešení.
INNER JOIN seminar_nodes_temavcisle AS tvcn ON tvcn.tema_id = id
INNER JOIN seminar_nodes_treenode AS ttn ON tvcn.treenode_ptr_id = ttn.id
LEFT OUTER JOIN seminar_nodes_treenode AS zad_tn ON ttn.first_child_id = zad_tn.id -- jen 33 z nich ma zadani
LEFT OUTER JOIN seminar_nodes_treenode AS res_tn ON zad_tn.succ_id = res_tn.id -- jen 4 z nich ma reseni
LEFT OUTER JOIN seminar_nodes_obsah AS zad_on ON zad_on.treenode_ptr_id = zad_tn.id
LEFT OUTER JOIN seminar_nodes_obsah AS res_on ON res_on.treenode_ptr_id = res_tn.id
LEFT OUTER JOIN seminar_texty AS zad_text ON zad_on.text_id = zad_text.id
LEFT OUTER JOIN seminar_texty AS res_text ON res_on.text_id = res_text.id -- vsechny 4
-- Ročník tématu:
-- Podle rootu TvCN
LEFT OUTER JOIN seminar_nodes_rocnik AS rn ON ttn.root_id = rn.treenode_ptr_id
ORDER BY problem_ptr_id"""
same_fields = ['text_zadani', 'text_reseni', 'rocnik_id']
renamed_fields = [
('typ', 'tema_typ'),
]
old_fields = same_fields + [f[0] for f in renamed_fields]
new_fields = same_fields + [f[1] for f in renamed_fields]
old_res, new_res = execute_simple(old_query, new_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n, old_fields, new_fields)
def check_konfera():
old_query = "SELECT * FROM seminar_problemy WHERE typ = 'konfera'"
new_query = "SELECT * FROM seminar_konfera"
oldcur.execute(old_query)
newcur.execute(new_query)
if oldcur.rowcount != 0 or newcur.rowcount != 0:
raise ValueError('There exists a Konfera!')
def check_org_clanek():
old_query = "SELECT * FROM seminar_problemy WHERE typ = 'org-clanek'"
oldcur.execute(old_query)
if oldcur.rowcount != 0:
raise ValueError('There exists a Org-clanek!')
def check_res_clanek():
# Dva(!) články mají text (zadání), který se má zachovat.
old_query = "SELECT * FROM seminar_problemy WHERE typ = 'res-clanek' ORDER BY id"
new_query = """SELECT cislo_id, text.na_web AS text_zadani
FROM seminar_clanky
JOIN seminar_problemy AS problem ON problem_ptr_id = problem.id
INNER JOIN seminar_hodnoceni AS hodn ON problem.id = hodn.problem_id
INNER JOIN seminar_reseni AS rese ON rese.id = hodn.reseni_id
INNER JOIN seminar_nodes_otistene_reseni AS rn ON rese.text_cely_id = rn.treenode_ptr_id -- Tenhle radek neni potreba, ale ujistuje se mj. o spravnem typu TreeNode.
INNER JOIN seminar_nodes_treenode AS tn ON rn.treenode_ptr_id = tn.id
-- Nektere clanky vubec nemely text, tak jim migr 0058 nevyrobila dalsi treenody
LEFT OUTER JOIN seminar_nodes_obsah AS son ON son.treenode_ptr_id = tn.first_child_id
LEFT OUTER JOIN seminar_texty AS text ON text.id = son.text_id
ORDER BY problem_ptr_id"""
same_fields = ['text_zadani']
renamed_fields = [
('cislo_zadani_id', 'cislo_id'),
]
old_fields = same_fields + [f[0] for f in renamed_fields]
new_fields = same_fields + [f[1] for f in renamed_fields]
old_res, new_res = execute_simple(old_query, new_query)
res = zip(old_res,new_res)
for o,n in res:
# text_zadani po novu mohl byt None
if n['text_zadani'] is None:
n['text_zadani'] = ''
check_same(o,n, old_fields, new_fields)
assert(o['text_reseni'] == '')
def check_untyped_problem():
old_query = "SELECT * FROM seminar_problemy WHERE typ NOT IN ('uloha', 'tema', 'serial', 'konfera', 'org-clanek', 'res-clanek')"
oldcur.execute(old_query)
if oldcur.rowcount != 0:
raise ValueError('There exists a Problem without type!')
check_skola()
check_resitel()
check_reseni()
check_organizator()
check_rocnik()
check_cislo()
check_priloha_reseni()
check_soustredeni()
check_soustredeni_ucastnici()
check_soustredeni_organizatori()
check_nastaveni()
check_novinky()
check_pohadka()
check_problem_common()
check_uloha()
check_tema()
check_konfera()
check_org_clanek()
check_res_clanek()
check_untyped_problem()

View file

@ -0,0 +1,21 @@
#!/bin/bash
set -u
deactivate || true
cd /akce/mam/www/mamweb-test/
make sync_test
systemctl --user stop mamweb-test.service
rm -rvf env
make install_venv
. env/bin/activate
make install
deploy_v2/pre_migration.py
make deploy_test
./manage.py loaddata data/*
systemctl --user start mamweb-test.service
./manage.py generate_thumbnails
echo 'Et voilá!'
echo 'Nezapomeň opravit práva pro sitetree!'

14
deploy_v2/mail_resitelum Normal file
View file

@ -0,0 +1,14 @@
Milí řešitelé M&M,
web M&Mka dostal nový kabátek a zároveň se v něm objevilo odevzdávátko. Navíc,
pokud se zaregistrujete pod e-mailem, na který jsme vám poslali tuhle zprávu,
uvidíte rovnou i body za svá dosud odevzdaná řešení.
Web budeme i nadále vylepšovat, a zajímá nás i jak to vidíte vy. Pokud byste
na webu našli nějaký nedostatek, nebo nám prostě jen chtěli napsat, nebojte se
k tomu použít adresu mam@matfyz.cz.
Těšíme se na vaše řešení! (Připomínáme termín pro účast na soustředění 21. září.)
Za časopis M&M,
Pavel Turinský

View file

@ -0,0 +1,3 @@
Jsou špatně práva k sitetree (protože se používají primární klíče, které jsou jiné :-()
Špatné položky se dají najít pomocí následujícího příkazu:
grep -E 'access_permissions": \[$' data/sitetree.json -A17 | grep -E 'acc|tit' -A2

42
deploy_v2/pre_migration.py Executable file
View file

@ -0,0 +1,42 @@
#!/usr/bin/env python3
import os
import sys
import django
#### Inicializace Djanga
sys.path.append(os.path.dirname(os.path.realpath(__file__))+'/..')
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mamweb.settings')
django.setup()
## Pozor, nejde pouzit ORM, protoze kod je na jine verzi nez databaze a nejde namigrovat.
from django.db import connection
def smaz_zle_clanky():
# Tyhle clanky vubec nejsou clanky, bude potreba je udelat cele jinak a spravne.
#m.Problem.objects.filter(id__in=[1981, 1970, 2222]).delete()
## with connection.cursor() as cursor:
## # Nejdriv musime smazat reseni:
## cursor.execute('DELETE FROM seminar_reseni WHERE problem_id IN (1981, 1970, 2222);')
## # Nakonec i ty clanky samotne
## cursor.execute('DELETE FROM seminar_problemy WHERE id IN (1981, 1970, 2222);')
# Update: stejně je v DB bordel, tak z nich prostě jen udělám témata a všechno zhruba přežije…
with connection.cursor() as cursor:
cursor.execute("UPDATE seminar_problemy SET typ = 'tema' WHERE id IN (1981, 1970, 2222);")
def smaz_divne_uzivatele():
# U techto uzivatelu neexistuje Organizator s nimi spojeny
# Takze pak delaji akorat neporadek
with connection.cursor() as cursor:
# Jeste je potreba zrusit vazby
cursor.execute('UPDATE django_comments SET user_id = NULL WHERE user_id = 34;')
cursor.execute('UPDATE seminar_problemy SET autor_id = NULL WHERE autor_id = 34;')
cursor.execute('DELETE FROM django_admin_log WHERE user_id = 34;')
cursor.execute('DELETE FROM auth_user_groups WHERE user_id = 34;')
cursor.execute('DELETE FROM auth_user WHERE id IN (34, 40, 30, 50, 54, 58, 43);')
smaz_zle_clanky()
smaz_divne_uzivatele()

View file

@ -13,6 +13,7 @@
import os import os
import sys import sys
import django import django
from django.utils.version import get_docs_version
sys.path.insert(0, os.path.abspath('..')) sys.path.insert(0, os.path.abspath('..'))
os.environ['DJANGO_SETTINGS_MODULE'] = 'mamweb.settings' os.environ['DJANGO_SETTINGS_MODULE'] = 'mamweb.settings'
django.setup() django.setup()
@ -36,6 +37,7 @@ extensions = [
'sphinx.ext.intersphinx', 'sphinx.ext.intersphinx',
'sphinx.ext.autosectionlabel', 'sphinx.ext.autosectionlabel',
'myst_parser', 'myst_parser',
'sphinxcontrib_django',
] ]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
@ -72,8 +74,8 @@ html_static_path = ['_static']
# Provázání s jinými dokumentacemi # Provázání s jinými dokumentacemi
intersphinx_mapping = {'python': ('https://docs.python.org/3', None), intersphinx_mapping = {'python': ('https://docs.python.org/3', None),
'django': ('http://docs.djangoproject.com/en/3.2/', 'django': (f'http://docs.djangoproject.com/en/{get_docs_version()}/',
'http://docs.djangoproject.com/en/3.2/_objects/'),} f'http://docs.djangoproject.com/en/{get_docs_version()}/_objects/'),}
# Generování tříd/funkcí/atributů v pořádí jak jsou naprogramované # Generování tříd/funkcí/atributů v pořádí jak jsou naprogramované
autodoc_member_order = "bysource" autodoc_member_order = "bysource"

27
docs/css.rst Normal file
View file

@ -0,0 +1,27 @@
CSS (a další styly na webu)
===========================
Inspirován `css-trick článkem <https://css-tricks.com/methods-organize-css/>`_ jsem se rozhodl rozdělit
CSSka do
- Konstant (``constants.css``), které jsou využívány na mnoha místech CSSek
- Nastylování html tagů (``base.css``)
- Layoutu (``layout.css``), což je to, co určuje celkové rozložení stránky
- Jednotlivých prvků (``modules.css``)
Dále jsem separoval CSSka pro **galerii** (potřebuje hodně specifických stylů). Stejně tak **korekturovátko** má styly separátně.
Dále web (asi) používá externí frameworky (v separátních složkách mají k sobě i JS a podobné věci):
- bootstrap: dělá nějaké basic stylování, *web je na něm hodně závislý* (například jsem zjistil, že bootstrap kdysi přidával ``font-size:14px``, bez čehož se web úplně rozpadnul) (také na něm běží mobilní meníčko, které navíc vyžaduje Popper, tedy bootstrap.bundle.js místo bootstrap.js)
Pak jsou tu ``mamweb-dev.css`` a ``printtable.css``, co jsem si ještě nerozmyslel, co s tím.
Pár myšlenek
------------
- Až na pár výjimek (galerii a korekturovátko) bych styly držel v jedné složce a málo souborech,
protože CSS šíleně dědí všechno možné
- Chce to dobře pojmenovávat třídy (speciálně aby bylo vidět, co ta třída dělá nebo kde se používá)
- Chce to hodně komentovat kód (speciálně tam, kde není splněn předchozí bod)

View file

@ -9,12 +9,6 @@ static
------ ------
Složka, kam django nakopíruje všechno ze složek static a pak na to z templatů / kódu jde ukazovat pomocí ``static``. Složka, kam django nakopíruje všechno ze složek static a pak na to z templatů / kódu jde ukazovat pomocí ``static``.
_git_hooks
----------
Hooky do gitu pro kontrolu Pythoního stylu. Především ``flake8``.
Zbylo tu z minulosti mamwebu.
data data
---- ----
Obsahuje data, která patří do databáze, ale jsou přímo součástí webu jako Obsahuje data, která patří do databáze, ale jsou přímo součástí webu jako
@ -31,10 +25,9 @@ nebo (v případě meníčka)::
./manage.py dumpdata sitetree --natural-foreign > data/sitetree_new.json ./manage.py dumpdata sitetree --natural-foreign > data/sitetree_new.json
./fix_json.py data/sitetree_new.json data/sitetree.json ./fix_json.py data/sitetree_new.json data/sitetree.json
nebo (v případě práv):: deploy_v2
---------
./manage.py dumpdata auth.group --natural-foreign > data/prava_skupin_new.json Věci, které byly potřeba při nasazování nového (2021) webu.
./fix_json.py data/prava_skupin_new.json data/prava_skupin.json
docs docs
---- ----

View file

@ -27,8 +27,10 @@ Dokumentace (jak v ``docs/``, tak přímo v kódu) je psaná ve
:titlesonly: :titlesonly:
vyvoj vyvoj
zavislosti
sphinx sphinx
skripty skripty
zkratky
modules/modules modules/modules
dalsi_soubory dalsi_soubory
zapisy/zapisy zapisy/zapisy

View file

@ -1,4 +1,4 @@
TODO přepsat do rst případně přesunout na wiki FIXME přepsat do rst, přidat i další věci a případně přesunout na wiki
Přidání obrázků do odměn: Přidání obrázků do odměn:
admin -> flatpage odměn -> ikona přidat obrázek admin -> flatpage odměn -> ikona přidat obrázek
záložka odeslat, vybrat obrázek, odeslat záložka odeslat, vybrat obrázek, odeslat

View file

@ -1,13 +1,15 @@
Sphinx na našem webu 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!**) 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`_ 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: 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 .. _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. i veškeré csv.
Další jsou pak jednotlivé aplikace (pokud něco hledáte, tak zřejmě chcete najít 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 tu aplikaci, která tomu odpovídá, respektive se k ní dostat přes url).
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.
**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, 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``, 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 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 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>`. 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 Základy djanga
-------------- --------------

View file

@ -1,25 +0,0 @@
.. Není odkázaná z menu, je to záměr
Tabulka prerekvizit v různých distribucích
=========
.. admonition:: Metodika
Na čistém repozitáři (``git clean -fxd``) a čistém systému spouštíme
``make/init_local``. Když to spadne, tak do tabulky zapíšeme, co jsme
přiinstalovali. Protože větev ``makefiles`` aktuálně není mergenutá do
masteru, nefunguje synchronizace flatpages (a stejně nemáme SSH klíč), takže
tam ``make/init_local`` sestřelíme a vyzkoušíme, že ``make/test`` spustí
testy.
.. Grafické tabulky (grid-tables, simple-tables) jsou strašný porod vyrábět, dlabu na to a cpu to do CSV…
.. csv-table:: Prerekvizity v jednotlivých distribucích
:header: Distribuce / OS, Repozitář s Py3.9, venv, py knihovny, PostgreSQL knihovna, poznámky
Ubuntu 22.10, ??, ``python3-venv``, ``python3-dev``, ``libpq-dev``, "Je potřeba zapnout zdroj ``universe`` a nainstalovat kompilátor C (``gcc``)?"
Linux Mint 21, ??, ``python3-venv``, ``python3-dev``, ``libpq-dev``, ""
Archlinux 2022.11.01, AUR, vestavěný, vestavěné, ``postgresql-libs``, "Je potřeba céčkový kompilátor (``gcc``)"
openSUSE Leap 15.4, oficiální (``python39``), předinstalovaný?, ``python39-devel``, ??FIXME!!, "Výchozí verze pythonu je 3.6 a ta je moc stará, potřeba instalovat ``gcc``. Nevím jak sehnat pg_config."
Debian 11, "oficiální, výchozí", ??, ??, ??, "Určitě to tam rozběhat jde, protože Gimli. Nejspíš bude relativně podobné Ubuntu."

View file

@ -37,7 +37,7 @@ Kromě toho je potřeba mít účet na `Gitee <https://gitea.ks.matfyz.cz>`_, kd
bydlí gitový repozitář s kódem. bydlí gitový repozitář s kódem.
.. tip:: Potřebné balíčky v různých distribucích jsou sepsané v :ref:`tabulce .. tip:: Potřebné balíčky v různých distribucích jsou sepsané v :ref:`tabulce
prerekvizit <Tabulka prerekvizit v různých distribucích>`. prerekvizit <Alternativní jména balíčků>`.
Doporučené Doporučené
^^^^^^^^^^ ^^^^^^^^^^

View file

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

97
docs/zavislosti.rst Normal file
View file

@ -0,0 +1,97 @@
Závislosti webu
@@@@@@@@@@@@@@@
Web ke svému běhu potřebuje různé další programy. Tahle stránka se snaží je pokrýt.
Stránka je koncipována jako odrážkový seznam balíčků pro Ubuntu s případnými
komentáři, na konci stránky jsou uvedena :ref:`jména balíčků <Alternativní jména
balíčků>` v různých dalších distribucích. (Seznam mj. cílí na lokální
rozchození, proto popisuji Ubuntu a ne Debian. I tak se ale snažíme popsat web
v úplnosti.)
.. I use Arch, btw.
Základ webu
===========
- ``python3`` Ideálně Python 3.9, jenž je na Gimlim
- ``python3-pip`` pro instalaci dalších Pythoních balíčků podle ``requirements.txt``
- ``python3-venv``
- ``gcc`` kompilace Pythoních knihoven ze zdrojových distribucí (sdist), možná (neotestováno) jde jako alternativu použít ``python3-wheel`` a stahovat bdists
- ``python3-dev`` taktéž
- ``libpq-dev`` do třetice…
- ``ghostscript`` TODO konverze PDF v korekturovátku
- ``pdflatex`` FIXME! generování obálek a stvrzenek
- ``git`` používán :ref:`Make skripty`
- ``locales`` pro české formáty
Nasazení na produkci / testweb
==============================
(nejsou nutně potřeba k provozu lokální instance)
- ``rsync``
- ``pg_utils`` FIXME
- ``htpasswd`` FIXME aby testweb nepoužívali náhodní kolemjdoucí
- ``postgresql-server`` TODO
- ``acl`` pro nastavování práv přes ``setfacl``
Pro testweb je potřeba i všechno pro :ref:`dokumentaci <Dokumentace>`, vizte níž.
Předpokládá se nasazení v uWSGI pod Nginxem a služba běžící pod systemd, nicméně to už je spíš záležitost infrastruktury a ne specifikum mamwebu.
Dokumentace
===========
- ``make`` pro zbuildění
- Pythoní balíčky podle příslušné části ``requirements.txt``
Vývojové nástroje
=================
(Nejsou nezbytně nutné, ale předpokládáme jejich užitečnost. Mohou se hodit i na produkci.)
- ``psql`` TODO pro manuální dotazy do PostgreSQL
- ``sqlite3`` TODO totéž pro SQLite3
- ``ssh``
- ``graphviz`` pro vygenerování schématu
- ``rsync``
- ``ipython3`` hezčí interaktivní shell (stačí z ``requirements.txt``)
Potenciální usnadnění života
============================
(Úplně zbytečné, ale sdílíme pozitivní zkušenosti :-))
- ``tea`` CLI klient pro Giteu, aby člověk nepotřeboval otevírat web pro založení PR
Alternativní jména balíčků
==========================
Různé distribuce balí SW různě, takže to, co je v jedné distribuci jeden
balíček může být v jiné rozděleno do víc. Pro usnadnění nasazení je tady
přehled známých alternativních jmen.
TODO: tabulka není úplná. Pokud na něco narazíte, tak ji prosím doplňte.
.. admonition:: Jak se pozná, že web funguje, pro účely tabulky?
Na čistém repozitáři (``git clean -fxd``) a čistém systému spouštíme
``make/init_local``. Když to spadne, tak do tabulky zapíšeme, co jsme
přiinstalovali. Protože nefunguje synchronizace flatpages (nemáme SSH klíč),
``make/init_local`` sestřelíme při pokusu o synchronizaci a vyzkoušíme, že
``make/test`` spustí testy.
.. Grafické tabulky (grid-tables, simple-tables) jsou strašný porod vyrábět, dlabu na to a cpu to do CSV…
.. csv-table:: Prerekvizity v jednotlivých distribucích
:header: Distribuce / OS, Repozitář s Py3.9, venv, py knihovny, PostgreSQL knihovna, poznámky
Ubuntu 22.10, ??, ``python3-venv``, ``python3-dev``, ``libpq-dev``, "Je potřeba zapnout zdroj ``universe`` a nainstalovat kompilátor C (``gcc``)?"
Linux Mint 21, ??, ``python3-venv``, ``python3-dev``, ``libpq-dev``, ""
Archlinux 2022.11.01, AUR, vestavěný, vestavěné, ``postgresql-libs``, "Je potřeba céčkový kompilátor (``gcc``)"
openSUSE Leap 15.4, oficiální (``python39``), předinstalovaný?, ``python39-devel``, ??FIXME!!, "Výchozí verze pythonu je 3.6 a ta je moc stará, potřeba instalovat ``gcc``. Nevím jak sehnat pg_config."
Debian 11, "oficiální, výchozí", ??, ??, ??, "Určitě to tam rozběhat jde, protože Gimli. Nejspíš bude relativně podobné Ubuntu."

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

@ -1,5 +1,4 @@
from galerie.models import Obrazek, Galerie, VZDY, ORG, NIKDY, UCASTNIK
from galerie.models import Obrazek, Galerie
from django.contrib import admin from django.contrib import admin
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django import forms from django import forms
@ -9,8 +8,9 @@ from django.db import models
def zverejnit_fotogalerii(modeladmin, request, queryset): def zverejnit_fotogalerii(modeladmin, request, queryset):
'''zverejni vybranou fotogalerii i jeji vsechny podgalerie''' '''zverejni vybranou fotogalerii i jeji vsechny podgalerie'''
queryset = queryset.filter(zobrazit=ORG)
for galerie in queryset: for galerie in queryset:
galerie.zobrazit = 0 galerie.zobrazit = VZDY
galerie.save() galerie.save()
zverejnit_fotogalerii(modeladmin, request, zverejnit_fotogalerii(modeladmin, request,
Galerie.objects.filter(galerie_up = galerie)) Galerie.objects.filter(galerie_up = galerie))
@ -19,8 +19,9 @@ def zverejnit_fotogalerii(modeladmin, request, queryset):
def prepnout_fotogalerii_do_org_rezimu(modeladmin, request, queryset): def prepnout_fotogalerii_do_org_rezimu(modeladmin, request, queryset):
'''zneverjni vybranou fotogalerii i jeji vsechny podgalerie''' '''zneverjni vybranou fotogalerii i jeji vsechny podgalerie'''
queryset = queryset.filter(zobrazit=VZDY)
for galerie in queryset: for galerie in queryset:
galerie.zobrazit = 1 galerie.zobrazit = ORG
galerie.save() galerie.save()
prepnout_fotogalerii_do_org_rezimu(modeladmin, request, prepnout_fotogalerii_do_org_rezimu(modeladmin, request,
Galerie.objects.filter(galerie_up = galerie)) Galerie.objects.filter(galerie_up = galerie))

View file

@ -1,6 +1,4 @@
from django import forms from django import forms
from soustredeni.models import Soustredeni
class KomentarForm(forms.Form): class KomentarForm(forms.Form):
komentar = forms.CharField(label = "Komentář:", max_length = 300, required=False) komentar = forms.CharField(label = "Komentář:", max_length = 300, required=False)

View file

@ -1,29 +0,0 @@
# Generated by Django 2.2.28 on 2023-08-09 19:30
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('galerie', '0010_auto_20200819_0947'),
('soustredeni', '0001_initial'),
]
run_before = [
('seminar', '0121_smazani_soustredeni'),
]
operations = [
migrations.SeparateDatabaseAndState(
state_operations=[
migrations.AlterField(
model_name='galerie',
name='soustredeni',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='soustredeni.Soustredeni'),
),
],
database_operations=[],
),
]

View file

@ -0,0 +1,13 @@
# Generated by Django 4.2.11 on 2024-04-30 21:53
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('galerie', '0010_auto_20200819_0947'),
]
operations = [
]

View file

@ -0,0 +1,20 @@
# Generated by Django 4.2.11 on 2024-05-01 13:07
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('soustredeni', '0001_split_from_seminar'),
('galerie', '0011_pre_split_soustredeni'),
]
operations = [
migrations.AlterField(
model_name='galerie',
name='soustredeni',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='soustredeni.soustredeni'),
),
]

View file

@ -0,0 +1,14 @@
# Generated by Django 4.2.11 on 2024-05-01 13:35
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('galerie', '0012_soustredeni_relink'),
('soustredeni', '0003_post_split_soustredeni'),
]
operations = [
]

View file

@ -0,0 +1,18 @@
# Generated by Django 4.2.20 on 2025-04-23 18:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('galerie', '0013_post_split_soustredeni'),
]
operations = [
migrations.AlterField(
model_name='galerie',
name='zobrazit',
field=models.IntegerField(choices=[(0, 'Vždy'), (1, 'Organizátorům'), (3, 'Účastníkům a orgům'), (2, 'Nikdy')], default=1, verbose_name='Zobrazit?'),
),
]

View file

@ -0,0 +1,19 @@
# Generated by Django 4.2.16 on 2025-04-30 18:53
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('galerie', '0014_alter_galerie_zobrazit'),
]
operations = [
migrations.AlterField(
model_name='galerie',
name='galerie_up',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='galerie.galerie'),
),
]

View file

@ -0,0 +1,19 @@
# Generated by Django 4.2.16 on 2025-04-30 19:02
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('galerie', '0015_alter_galerie_galerie_up'),
]
operations = [
migrations.AlterField(
model_name='obrazek',
name='galerie',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='galerie.galerie'),
),
]

View file

@ -1,7 +1,5 @@
from django.db import models from django.db import models
#from django.db.models import Q #from django.db.models import Q
from django.utils.encoding import force_text
from imagekit.models import ImageSpecField from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFit, Transpose from imagekit.processors import ResizeToFit, Transpose
@ -12,9 +10,11 @@ from soustredeni.models import Soustredeni
VZDY=0 VZDY=0
ORG=1 ORG=1
NIKDY=2 NIKDY=2
UCASTNIK=3
VIDITELNOST = ( VIDITELNOST = (
(VZDY, 'Vždy'), (VZDY, 'Vždy'),
(ORG, 'Organizátorům'), (ORG, 'Organizátorům'),
(UCASTNIK, 'Účastníkům a orgům'),
(NIKDY, 'Nikdy'), (NIKDY, 'Nikdy'),
) )
@ -56,7 +56,7 @@ class Obrazek(models.Model):
nazev = models.CharField('Název', max_length=50, blank=True, null=True) nazev = models.CharField('Název', max_length=50, blank=True, null=True)
popis = models.TextField('Popis', blank=True, null=True) popis = models.TextField('Popis', blank=True, null=True)
datum_vlozeni = models.DateTimeField('Datum vložení', auto_now_add=True) datum_vlozeni = models.DateTimeField('Datum vložení', auto_now_add=True)
galerie = models.ForeignKey('Galerie', blank=True, null=True, on_delete=models.SET_NULL) galerie = models.ForeignKey('Galerie', blank=True, null=True, on_delete=models.CASCADE)
poradi = models.IntegerField('Pořadí', blank=True, null=True) poradi = models.IntegerField('Pořadí', blank=True, null=True)
def __str__(self): def __str__(self):
@ -90,7 +90,7 @@ class Galerie(models.Model):
titulni_obrazek = models.ForeignKey(Obrazek, blank = True, null = True, related_name = "+", on_delete = models.SET_NULL) titulni_obrazek = models.ForeignKey(Obrazek, blank = True, null = True, related_name = "+", on_delete = models.SET_NULL)
zobrazit = models.IntegerField('Zobrazit?', default = ORG, choices = VIDITELNOST) zobrazit = models.IntegerField('Zobrazit?', default = ORG, choices = VIDITELNOST)
galerie_up = models.ForeignKey('Galerie', blank = True, null = True, galerie_up = models.ForeignKey('Galerie', blank = True, null = True,
on_delete=models.SET_NULL) on_delete=models.PROTECT)
soustredeni = models.ForeignKey(Soustredeni, blank = True, null = True, soustredeni = models.ForeignKey(Soustredeni, blank = True, null = True,
on_delete=models.PROTECT) on_delete=models.PROTECT)
poradi = models.IntegerField('Pořadí', blank = True, null = False, default = 0) poradi = models.IntegerField('Pořadí', blank = True, null = False, default = 0)
@ -100,25 +100,3 @@ class Galerie(models.Model):
class Meta: class Meta:
verbose_name = 'Galerie' verbose_name = 'Galerie'
verbose_name_plural = 'Galerie' verbose_name_plural = 'Galerie'
#def link_na_preview(self):
#"""Odkaz na galerii, používá se v admin rozhranní. """
#return '<a href="/fotogalerie/galerie/%s/">Preview</a>' % self.id
#link_na_preview.allow_tags = True
#link_na_preview.short_description = 'Zobrazit galerii'
#
#def je_publikovano(self):
#"""Vraci True, pokud je tato galerie publikovana. """
#if self.zobrazit == VZDY:
#return True
#if self.zobrazit == PODLE_CLANKU:
#for clanek in self.clanek_set.all():
#if clanek.je_publikovano():
#return True
#return False
#
#@staticmethod
#def publikovane_galerie():
#"""Vraci galerie, ktere uz maji byt publikovane."""
#clanky = Blog.models.Clanek.publikovane_clanky()
#return Galerie.objects.filter(Q(zobrazit=VZDY) | (Q(clanek__in=clanky) & Q(zobrazit=PODLE_CLANKU))).distinct()

View file

@ -0,0 +1,191 @@
@charset "utf-8"; /* vynuť utf-8 */
/* Galerie */
/* velká fotka */
/* zmenšování spolu s oknem prohlížeče */
.galerie .obrazek, .titulni_obrazek {
max-width: 100%;
height: auto;
width: auto\9; /* ie8 */
}
.predchozi_obrazek{
position: absolute;
z-index: 1;
width: 33%;
height: 100%;
left: 0;
top: 0;
}
.predchozi_obrazek:hover{
background-image: url("/static/galerie/prvky/predchozi.svg");
filter: drop-shadow(0px 5px 5px rgba(0, 0, 0, 0.4));
background-position: left center;
background-repeat: no-repeat;
}
.dalsi_obrazek{
position: absolute;
z-index: 1;
width: 33%;
height: 100%;
left: 67%;
top: 0;
}
.dalsi_obrazek:hover{
background-image: url("/static/galerie/prvky/dalsi.svg");
filter: drop-shadow(0px 5px 5px rgba(0, 0, 0, 0.4));
background-position: right center;
background-repeat: no-repeat;
}
.galerie {
position: relative;
text-align: center;
margin: 20px auto 0 auto;
}
.galerie h1 {
text-align: center;
}
.galerie_hlavicka {
margin: 30px auto 30px auto;
}
.popis {
margin: 10px 10px 30px 0px;
text-align: center;
}
#nahoru {
text-align: center;
}
/* titulní obrázek hlavní galerie soustředění */
.galerie_nahledy{
/*margin: 1em 0;*/
margin: auto;
padding: 10px;
text-align: center;
overflow: auto;
}
.galerie_nahledy img {
margin: 10px;
}
.galerie_nahledy div.navigace {
display: inline-block;
}
.galerie_nahled, .podgalerie_nahled { /* frame */
display: block;
position: relative;
float: left;
width: 200px;
height: 200px;
text-align: center;
border: solid;
border-width: 1px;
border-radius: 4px;
border-color: var(--svetla-oranzova);
background-color: var(--barva-pozadi);
white-space: nowrap;
margin: 10px;
font-weight: bold;
}
.galerie_nahled:hover, .podgalerie_nahled:hover {
background-color: var(--svetla-oranzova);
filter: drop-shadow(0px 5px 5px rgba(0, 0, 0, 0.4));
color: var(--tmava-oranzova);
}
.vystredeno{ /* helper */
display: inline-block;
height: 100%;
vertical-align: middle;
}
.galerie_nahled img {
vertical-align: middle;
max-height: 180px;
max-width: 180px;
}
.galerie_nahled div {
position: absolute;
bottom: 0px;
width: 100%;
text-align: center;
}
.podgalerie_nahled img {
margin-top: 20px;
margin-bottom: 15px;
max-height: 125px;
max-width: 167px;
}
.podgalerie_nahled .nazev_galerie {
position: absolute;
width: 100%;
top: 160px;
}
.podgalerie_nahled.mam-org-only, .podgalerie_nahled.mam-resitel-only {
margin: 10px;
padding: 0;
}
/* Odkazy na předchozí a následující podgalerii */
.galerie_predchozi_nasledujici {
overflow: auto;
margin: 10px auto 10px auto;
}
.galerie_predchozi_nasledujici .predchozi {
float: left;
}
.galerie_predchozi_nasledujici .nasledujici {
float: right;
}
/* posune kotvu obrázku v galerii o oranžový pruh dolu, aby se pod ním obrázek neschovával */
/* https://stackoverflow.com/questions/10732690/offsetting-an-html-anchor-to-adjust-for-fixed-header */
.kotva_obrazku {
position: absolute;
width: 0;
height: 55px; /* viz #title */
margin-top: -55px; /* viz #title */
}
@media(max-width: 860px) {
.kotva_obrazku {
height: 3em; /* #FIXME nemám páru, jak zjistit výšku toho elementu */
margin-top: -3em; /* #FIXME */
}
}
/* plus a minus tlacitka */
.mam-org-only-galerie {
background: var(--orgovska-svetla-fialova);
padding: 10px;
margin: 10px 10px 10px -20px;
border: #333 2px dashed;
float: left;
}
.mam-org-only-galerie a{
padding: 3px 5px;
margin: 5px;
border-radius: 20px;
background-color: var(--tmava-oranzova);;
color: var(--barva-pozadi);
float: left;
}

View file

@ -1,4 +1,4 @@
{% extends "base.html" %} {% extends "galerie/base.html" %}
{% block nadpis1a %} {% block nadpis1a %}
@ -46,6 +46,7 @@
{% block content %} {% block content %}
<div class="{% if obrazek.galerie.zobrazit == 1 or obrazek.galerie.zobrazit == 2 %}mam-org-only{% endif %}{% if obrazek.galerie.zobrazit == 3 %}mam-resitel-only{% endif %}">
<h2> <h2>
{% for g in cesta %} {% for g in cesta %}
@ -83,7 +84,7 @@
{# Popisek fotky #} {# Popisek fotky #}
<div class="popis"> <div class="popis">
{% if preview %} {% if upravy_popisku %}
<form action=".#nahoru" method="post" id="komentarform"> <form action=".#nahoru" method="post" id="komentarform">
{% csrf_token %} {% csrf_token %}
<table> <table>
@ -133,4 +134,6 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div>
{% endblock %} {% endblock %}

View file

@ -1,4 +1,4 @@
{% extends "base.html" %} {% extends "galerie/base.html" %}
{% block nadpis1a %} {% block nadpis1a %}
Galerie {{galerie.nazev}} Galerie {{galerie.nazev}}
@ -6,8 +6,11 @@ Galerie {{galerie.nazev}}
{% block content %} {% block content %}
{% if galerie.zobrazit > 0 %} {# FIXME: použít konstanty… #}
{% if galerie.zobrazit == 1 or galerie.zobrazit == 2 %}
<div class="mam-org-only"> <div class="mam-org-only">
{% elif galerie.zobrazit == 3 %}
<div class="mam-resitel-only">
{% endif %} {% endif %}
<h2> <h2>
@ -45,37 +48,43 @@ Galerie {{galerie.nazev}}
{% if podgalerie %} {% if podgalerie %}
{% with 22 as max_delka_nazvu %} {% with 22 as max_delka_nazvu %}
<div class="galerie_nahledy"> <div class="galerie_nahledy">
{% for galerie in podgalerie %} {% for pgalerie in podgalerie %}
<a href="../{{galerie.pk}}" <a href="../{{pgalerie.pk}}"
{% if galerie.nazev|length > max_delka_nazvu %} {% if pgalerie.nazev|length > max_delka_nazvu %}
title="{{ galerie.nazev }}" title="{{ pgalerie.nazev }}"
{% endif %}
class="podgalerie_nahled">
{% if galerie.titulni_obrazek %}
{% with galerie.titulni_obrazek.obrazek_maly as obrazek %}
<img src="{{ obrazek.url }}"
/>
{% endwith %}
{% endif %} {% endif %}
<div class="nazev_galerie"> class="podgalerie_nahled {% if pgalerie.zobrazit == 1 or pgalerie.zobrazit == 2 %}mam-org-only{% endif %}{% if pgalerie.zobrazit == 3 %}mam-resitel-only{% endif %}">
{{ galerie|truncatechars:max_delka_nazvu }} {% if pgalerie.titulni_obrazek %}
{% with pgalerie.titulni_obrazek.obrazek_maly as obrazek %}
<img src="{{ obrazek.url }}"
/>
{% endwith %}
{% endif %}
<div class="nazev_galerie">
{{ pgalerie|truncatechars:max_delka_nazvu }}
</div>
</a>
{% if galerie.zobrazit == 1 or galerie.zobrazit == 2 %}
{% if user.je_org %}
<div class="mam-org-only-galerie">
({{pgalerie.poradi}})
<span class="plus"><a href="plus/{{pgalerie.pk}}/">+</a></span>
<span class="minus"><a href="minus/{{pgalerie.pk}}/">-</a></span>
</div> </div>
</a> {% endif %}
{% if user.je_org and galerie.zobrazit > 0 %} {% endif %}
<div class="mam-org-only-galerie">
({{galerie.poradi}})
<span class="plus"><a href="plus/{{galerie.pk}}/">+</a></span>
<span class="minus"><a href="minus/{{galerie.pk}}/">-</a></span>
</div>
{% endif %}
{% endfor %} {% endfor %}
</div> </div>
{% endwith %} {% endwith %}
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if user.je_org and galerie.zobrazit > 0 %} {% if user.je_org %}
<div class="mam-org-only"> <div class="mam-org-only">
<a href="./new">Vytvořit novou podgalerii </a> {% if galerie.zobrazit == 1 or galerie.zobrazit == 2 %}
<a href="./new">Vytvořit novou podgalerii</a>, <a href="{% url 'admin:galerie_galerie_change' galerie.pk %}">upravit galerii v adminu</a>
{% else %}
Jestli chceš změnit pořadí podgalerií nebo přidat novou, nastav zobrazení jen pro orgy v <a href="{% url 'admin:galerie_galerie_change' galerie.pk %}">adminu</a>.
{% endif %}
</div> </div>
{% endif %} {% endif %}
@ -120,8 +129,10 @@ Galerie {{galerie.nazev}}
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if galerie.zobrazit > 0 %} {% if galerie.zobrazit == 1 or galerie.zobrazit == 2 %}
</div> {# mam-org-only #} </div> {# mam-org-only #}
{% elif galerie.zobrazit == 2 %}
</div> {# mam-resitel-only #}
{% endif %} {% endif %}
{% endblock content %} {% endblock content %}

View file

@ -1,4 +1,4 @@
{% extends "base.html" %} {% extends "galerie/base.html" %}
{% block content %} {% block content %}

View file

@ -0,0 +1,6 @@
{% extends "base.html" %}
{% load static %}
{% block custom_css %}
<link href="{% static 'css/galerie.css' %}?version=2" rel="stylesheet">
{% endblock %}

6
galerie/utils.py Normal file
View file

@ -0,0 +1,6 @@
from galerie.models import Galerie
def top_galerie(g: Galerie) -> Galerie:
while g.galerie_up is not None:
g = g.galerie_up
return g

View file

@ -1,23 +1,41 @@
import random import random
from django.http import HttpResponse, Http404 from django.http import HttpResponse, Http404, HttpRequest
from django.shortcuts import render, HttpResponseRedirect, get_object_or_404 from django.shortcuts import render, HttpResponseRedirect, get_object_or_404
from django.template import RequestContext from django.template import RequestContext
from datetime import datetime from datetime import datetime
from galerie.models import Obrazek, Galerie from galerie.utils import top_galerie
from personalni.utils import resitel_uzivatele
from galerie.models import Obrazek, Galerie, VZDY, ORG, UCASTNIK, NIKDY
from soustredeni.models import Soustredeni from soustredeni.models import Soustredeni
from galerie.forms import KomentarForm, NewGalerieForm from galerie.forms import KomentarForm, NewGalerieForm
def zobrazit(galerie, request): import logging
preview = False logger = logging.getLogger(__name__)
if galerie.zobrazit >= 1:
if request.user.je_org: def galerie_ke_zobrazeni(soustredeni: Soustredeni | None, request: HttpRequest) -> tuple[int]:
preview = True; if request.user.is_superuser: return (VZDY, ORG, UCASTNIK, NIKDY)
if request.user.je_org: return (VZDY, ORG, UCASTNIK)
if request.user.is_anonymous: return (VZDY,)
if soustredeni is None: return (VZDY,)
if (resitel := resitel_uzivatele(request.user)) is not None:
if resitel.soustredeni_set.contains(soustredeni):
return (VZDY, UCASTNIK)
else: else:
raise Http404 return (VZDY,)
return preview logger.warning("Nepodařilo se zjistit, jaké galerie se mají zobrazit!")
return (VZDY,)
def zobrazit(galerie: Galerie, request: HttpRequest) -> bool:
soustredeni = top_galerie(galerie).soustredeni
return galerie.zobrazit in galerie_ke_zobrazeni(soustredeni, request)
def dovolit_upravy_popisku(galerie: Galerie, request: HttpRequest) -> bool:
# FIXME: Dočasné: úpravy jen když je to v org-only stavu. (Odpovídá předchozímu chování)
return request.user.je_org and galerie.zobrazit in (ORG, NIKDY)
def cesta_od_korene(g): def cesta_od_korene(g):
@ -32,19 +50,19 @@ def cesta_od_korene(g):
def nahled(request, pk, soustredeni): def nahled(request, pk, soustredeni):
"""Zobrazeni nahledu vsech fotek ve skupine.""" """Zobrazeni nahledu vsech fotek ve skupine."""
galerie = get_object_or_404(Galerie, pk=pk) galerie = get_object_or_404(Galerie, pk=pk)
soustredeni = top_galerie(galerie).soustredeni
# FIXME: přepsat model a použít přímo dolů…
podgalerie = Galerie.objects.filter(galerie_up = galerie).order_by('poradi') podgalerie = Galerie.objects.filter(galerie_up = galerie).order_by('poradi')
if not request.user.je_org: podgalerie = podgalerie.filter(zobrazit__in=galerie_ke_zobrazeni(soustredeni, request))
podgalerie = podgalerie.filter(zobrazit__lt=1)
obrazky = Obrazek.objects.filter(galerie = galerie).order_by('poradi', 'nazev') obrazky = galerie.obrazek_set.all().order_by('poradi', 'nazev')
preview = zobrazit(galerie, request) ma_se_zobrazit = zobrazit(galerie, request)
if not ma_se_zobrazit: raise Http404("Galerie sice existuje, ale my se tváříme, že ne :-D")
sourozenci = [] sourozenci = []
if galerie.galerie_up: if galerie.galerie_up:
sourozenci = galerie.galerie_up.galerie_set.all().order_by('poradi') sourozenci = galerie.galerie_up.galerie_set.filter(zobrazit__in=galerie_ke_zobrazeni(soustredeni, request)).order_by('poradi')
if not request.user.je_org:
sourozenci = sourozenci.filter(zobrazit__lt=1)
predchozi = None predchozi = None
nasledujici = None nasledujici = None
@ -63,7 +81,6 @@ def nahled(request, pk, soustredeni):
{'galerie' : galerie, {'galerie' : galerie,
'podgalerie' : podgalerie, 'podgalerie' : podgalerie,
'obrazky' : obrazky, 'obrazky' : obrazky,
'preview' : preview,
'cesta': cesta, 'cesta': cesta,
'sourozenci': sourozenci, 'sourozenci': sourozenci,
'predchozi': predchozi, 'predchozi': predchozi,
@ -79,9 +96,41 @@ def detail(request, pk, fotka, soustredeni):
NAHLEDU = 1 NAHLEDU = 1
galerie = get_object_or_404(Galerie, pk=pk) galerie = get_object_or_404(Galerie, pk=pk)
preview = zobrazit(galerie, request) soustredeni = top_galerie(galerie).soustredeni
ma_se_zobrazit = zobrazit(galerie, request)
if not ma_se_zobrazit: raise Http404("Obrázek neukážu!")
obrazek = get_object_or_404(Obrazek, pk=fotka) obrazek = get_object_or_404(Obrazek, pk=fotka)
# Pořadí není povinné. FIXME: `nazev` je zavádějící… Ale tohle je kanonické pořadí obrázků v galerii…
obrazky = galerie.obrazek_set.all().order_by('poradi', 'nazev') obrazky = galerie.obrazek_set.all().order_by('poradi', 'nazev')
obrazky = list(obrazky)
index_obrazku = obrazky.index(obrazek)
# Podle mě se nemůže stát, že by volání výš selhalo, kdyžtak shodí web. (původně to byl explicitně ošetřený stav dávající 404)
predchozi_obrazky = list(reversed(obrazky[:index_obrazku]))
nasledujici_obrazky = obrazky[index_obrazku+1:]
# Může jich být hodně…
predchozi_obrazky = predchozi_obrazky[:NAHLEDU]
nasledujici_obrazky = nasledujici_obrazky[:NAHLEDU]
# Předchozí obrázky chceme v normálním pořadí
predchozi_obrazky = list(reversed(predchozi_obrazky))
if galerie.galerie_up is not None:
sousedni_galerie = Galerie.objects.filter(galerie_up=galerie.galerie_up, zobrazit__in=galerie_ke_zobrazeni(soustredeni, request)).order_by('poradi')
sousedni_galerie = list(sousedni_galerie)
# Teoreticky se můžeme dívat na galerie.poradi, ale jednak už tenhle pattern stejně je výš a druhak je galerií málo, takže pomalost nevadí.
index_galerie = sousedni_galerie.index(galerie)
predchozi_galerie = sousedni_galerie[index_galerie-1] if index_galerie > 0 else None
nasledujici_galerie = sousedni_galerie[index_galerie+1] if index_galerie < len(sousedni_galerie) - 1 else None
else:
predchozi_galerie = None
nasledujici_galerie = None
# Pokud je obrázků dost, tak další galerii nepotřebujeme
# (jo, mohli jsme si ušetřit práci, ale takhle je kód imho přehlednější a za pár ušetřených dotazů do DB to nestojí)
if len(predchozi_obrazky) >= NAHLEDU:
predchozi_galerie = None
if len(nasledujici_obrazky) >= NAHLEDU:
nasledujici_galerie = None
# vytvoreni a obslouzeni formulare # vytvoreni a obslouzeni formulare
if request.method == 'POST': if request.method == 'POST':
@ -91,49 +140,6 @@ def detail(request, pk, fotka, soustredeni):
obrazek.save() obrazek.save()
else: else:
form = KomentarForm({'komentar': obrazek.popis}) form = KomentarForm({'komentar': obrazek.popis})
# Poradi aktualniho obrazku v galerii/stitku.
for i in range(len(obrazky)):
if obrazky[i] == obrazek:
poradi = i
break
else:
# Obrazek neni v galerii/stitku.
raise Http404
# Nacteni okolnich obrazku a galerii
# TODO vyjmout zjisteni predchozich a nasledujicich galerii
# a udelat z toho funkci, ktera se pouzije u nahledu
predchozi_galerie = None
nasledujici_galerie = None
obrazky_dalsi = obrazky[poradi+1:poradi+NAHLEDU+1]
if (poradi+1) > NAHLEDU:
obrazky_predchozi = obrazky[poradi-NAHLEDU:poradi]
else:
obrazky_predchozi = obrazky[0:poradi]
if galerie.poradi > 1:
predchozi_galerie = Galerie.objects.\
filter(galerie_up=galerie.galerie_up).\
filter(poradi=(galerie.poradi-1))
if predchozi_galerie:
predchozi_galerie = predchozi_galerie[0]
else:
predchozi_galerie = None
if (poradi+1) == len(obrazky): # Tohle je poslední obrázek
if (galerie.poradi is not None
and galerie.galerie_up is not None):
nasledujici_galerie = Galerie.objects.\
filter(galerie_up=galerie.galerie_up).\
filter(poradi=(galerie.poradi+1))
else:
nasledujici_galerie = None
if nasledujici_galerie:
nasledujici_galerie = nasledujici_galerie[0]
else:
nasledujici_galerie = None
# Preskalovani obrazku do vybraneho prostoru. # Preskalovani obrazku do vybraneho prostoru.
vyska = obrazek.obrazek_stredni.height vyska = obrazek.obrazek_stredni.height
@ -152,9 +158,9 @@ def detail(request, pk, fotka, soustredeni):
'obrazek' : obrazek, 'obrazek' : obrazek,
'vyska' : vyska, 'vyska' : vyska,
'sirka' : sirka, 'sirka' : sirka,
'obrazky_predchozi' : obrazky_predchozi, 'obrazky_predchozi' : predchozi_obrazky,
'obrazky_dalsi' : obrazky_dalsi, 'obrazky_dalsi' : nasledujici_obrazky,
'preview' : preview, 'upravy_popisku' : dovolit_upravy_popisku(galerie, request),
'form' : form, 'form' : form,
'cesta': cesta_od_korene(galerie), 'cesta': cesta_od_korene(galerie),
}) })
@ -180,7 +186,7 @@ def new_galerie(request, galerie, soustredeni):
gal = Galerie() gal = Galerie()
gal.nazev = form.cleaned_data['nazev'] gal.nazev = form.cleaned_data['nazev']
#gal.popis = form.cleaned_data['popis'] # popis nepouzivame #gal.popis = form.cleaned_data['popis'] # popis nepouzivame
gal.zobrazit = 1 # galerie je v procesu vytvareni gal.zobrazit = ORG # galerie je v procesu vytvareni
''' pokud je to podgalerie pridej nadrazenou galerii ''' pokud je to podgalerie pridej nadrazenou galerii
a nadrazene soustredeni nechej volne, a nadrazene soustredeni nechej volne,
pokud je to hlavni galerie, tak nadrazena galerie neexistuje, pokud je to hlavni galerie, tak nadrazena galerie neexistuje,

View file

@ -3,3 +3,4 @@ from django.apps import AppConfig
class HeaderFotkyConfig(AppConfig): class HeaderFotkyConfig(AppConfig):
name = 'header_fotky' name = 'header_fotky'
verbose_name = 'Fotky v záhlaví'

View file

@ -1,3 +1,8 @@
"""
Context processory lze přidat do djanga v :mod:`~mamweb.settings` a dělají to,
že do contextu (tj. to, z čeho se např. berou proměnné v templatech) libovolné
stránky přidají další věci.
"""
from datetime import datetime, date from datetime import datetime, date
import random import random

View file

@ -1,12 +1,10 @@
from django.conf import settings
from django.contrib import admin from django.contrib import admin
from reversion.admin import VersionAdmin from reversion.admin import VersionAdmin
from korektury.models import KorekturovanePDF from korektury.models import KorekturovanePDF, Oprava, KorekturaTag
from django.core.mail import EmailMessage from django.core.mail import EmailMessage
from django.urls import reverse from django.urls import reverse
class KorekturovanePDFAdmin(VersionAdmin): class KorekturovanePDFAdmin(VersionAdmin):
""" """
nastaví čas vložení (:attr:`~koretkury.models.KorekturovanePDF.cas`) a počet nastaví čas vložení (:attr:`~koretkury.models.KorekturovanePDF.cas`) a počet
@ -27,12 +25,13 @@ class KorekturovanePDFAdmin(VersionAdmin):
fieldsets = [ fieldsets = [
(None, (None,
{'fields': {'fields':
['pdf', 'cas', 'org', 'stran', 'nazev', 'komentar', 'poslat_mail']}), ['pdf', 'cas', 'stran', 'nazev', 'orgove', 'komentar', 'poslat_mail']}),
# (u'PDF', {'fields': ['pdf']}), # (u'PDF', {'fields': ['pdf']}),
] ]
list_display = ['nazev', 'cas', 'stran', 'org'] list_display = ['nazev', 'cas', 'stran']
list_filter = [] list_filter = []
search_fields = [] search_fields = []
autocomplete_fields = ['orgove']
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
""" """
@ -41,8 +40,8 @@ class KorekturovanePDFAdmin(VersionAdmin):
super().save_model(request, obj, form, change) super().save_model(request, obj, form, change)
if not change and obj.poslat_mail: # Je nový a má se poslat mail if not change and obj.poslat_mail: # Je nový a má se poslat mail
odkaz = request.build_absolute_uri(reverse('korektury', kwargs={'pdf': obj.id})) odkaz = request.build_absolute_uri(reverse('korektury', kwargs={'pdf': obj.id}))
odesilatel = settings.KOREKTURY_NOVE_PDF_EMAIL odesilatel = 'korekturovatko-nove-pdf@mam.mff.cuni.cz'
prijemce = settings.KONFERA_ORGOVE_EMAIL prijemce = 'org@mam.mff.cuni.cz'
predmet = f'Nové korektury: {obj.nazev}' predmet = f'Nové korektury: {obj.nazev}'
text = f'''\ text = f'''\
V korekturovátku se objevil nový soubor: {obj.nazev} V korekturovátku se objevil nový soubor: {obj.nazev}
@ -63,3 +62,11 @@ Korekturovátko
).send() ).send()
admin.site.register(KorekturovanePDF, KorekturovanePDFAdmin) admin.site.register(KorekturovanePDF, KorekturovanePDFAdmin)
class OpravaAdmin(admin.ModelAdmin):
model = Oprava
filter_horizontal = ("informovani_orgove", "tagy",)
admin.site.register(Oprava, OpravaAdmin)
admin.site.register(KorekturaTag)

7
korektury/api/apps.py Normal file
View file

@ -0,0 +1,7 @@
from django.apps import AppConfig
class ApiConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'korektury.api'
label = 'korektury_api' # Protože jedno api už máme.

11
korektury/api/urls.py Normal file
View file

@ -0,0 +1,11 @@
from django.urls import path
from personalni.utils import org_required
from . import views
urlpatterns = [
path('<int:pdf_id>/stav', org_required(views.korektury_stav_view), name='korektury_api_pdf_stav'),
path('oprava/stav', org_required(views.oprava_stav_view), name='korektury_api_oprava_stav'),
path('<int:pdf_id>/opravy_a_komentare', org_required(views.opravy_a_komentare_view), name='korektury_api_opravy_a_komentare'),
path('oprava/smaz', org_required(views.oprava_smaz_view), name='korektury_api_oprava_smaz'),
path('komentar/smaz', org_required(views.komentar_smaz_view), name='korektury_api_komentar_smaz'),
]

134
korektury/api/views.py Normal file
View file

@ -0,0 +1,134 @@
from http import HTTPStatus
from django.http import JsonResponse, HttpResponse
from django.shortcuts import get_object_or_404
from django.utils.html import linebreaks
from rest_framework import serializers
from korektury.utils import send_email_notification_komentar
from korektury.models import Oprava, KorekturovanePDF, Komentar, KorekturaTag
from personalni.models import Organizator
def korektury_stav_view(request, pdf_id: int, **kwargs):
q = request.POST
pdf = get_object_or_404(KorekturovanePDF, id=pdf_id)
status = q.get('state')
if status is not None:
assert status in KorekturovanePDF.STATUS.values
pdf.status = status
pdf.save()
return JsonResponse({'status': pdf.status})
def oprava_stav_view(request, **kwargs):
q = request.POST
op_id_str = q.get('id')
assert op_id_str is not None
op_id = int(op_id_str)
op = get_object_or_404(Oprava, id=op_id)
status = q.get('action')
if status is not None:
assert status in Oprava.STATUS.values
op.status = status
op.save()
return JsonResponse({'status': op.status})
def oprava_smaz_view(request, **kwargs):
q = request.POST
op_id_str = q.get('oprava_id')
assert op_id_str is not None
op_id = int(op_id_str)
oprava = get_object_or_404(Oprava, id=op_id)
oprava.delete()
return HttpResponse(status=HTTPStatus.NO_CONTENT)
def komentar_smaz_view(request, **kwargs):
q = request.POST
kom_id_str = q.get('komentar_id')
assert kom_id_str is not None
kom_id = int(kom_id_str)
komentar = get_object_or_404(Komentar, id=kom_id)
komentar.delete()
return HttpResponse(status=HTTPStatus.NO_CONTENT)
class KomentarSerializer(serializers.ModelSerializer):
class Meta:
model = Komentar
fields = '__all__'
def to_representation(self, instance):
ret = super().to_representation(instance)
ret["autor"] = str(instance.autor)
ret["text"] = linebreaks(ret["text"], autoescape=True) # Autora není třeba escapovat, ten se vkládá jako text.
return ret
class KorekturaTagSerializer(serializers.ModelSerializer):
class Meta:
model = KorekturaTag
fields = '__all__'
class OpravaSerializer(serializers.ModelSerializer):
class Meta:
model = Oprava
fields = '__all__'
def to_representation(self, instance):
ret = super().to_representation(instance)
ret["komentare"] = [KomentarSerializer(komentar).data for komentar in instance.komentar_set.all()]
ret["tagy"] = [KorekturaTagSerializer(tag).data for tag in instance.tagy.all()]
return ret
# komentar_set = serializers.ListField(child=KomentarSerializer())
def opravy_a_komentare_view(request, pdf_id: int, **kwargs):
if request.method == 'POST':
q = request.POST
x = int(q.get('x'))
y = int(q.get('y'))
img_id = int(q.get('img_id'))
oprava_id = int(q.get('oprava_id'))
komentar_id = int(q.get('komentar_id'))
text = q.get('text')
# prirazeni autora podle prihlaseni
autor_user = request.user
# pokud existuje ucet (user), ale neni to organizator = 403
autor = Organizator.objects.filter(osoba__user=autor_user).first()
if komentar_id != -1:
komentar = get_object_or_404(Komentar, id=komentar_id)
if komentar.text != text:
komentar.text = text
komentar.autor = autor
komentar.save()
oprava = komentar.oprava
else:
if oprava_id != -1:
oprava = get_object_or_404(Oprava, id=oprava_id)
else:
pdf = get_object_or_404(KorekturovanePDF, id=pdf_id)
oprava = Oprava.objects.create(
pdf=pdf,
strana=img_id,
x=x,
y=y,
)
Komentar.objects.create(oprava=oprava, autor=autor, text=text)
send_email_notification_komentar(oprava, autor, request)
tagy_raw = q.get('tagy')
if tagy_raw is not None:
oprava.tagy.clear()
if tagy_raw != "":
tagy = list(map(int, tagy_raw.split(",")))
oprava.tagy.add(*KorekturaTag.objects.filter(id__in=tagy))
opravy = Oprava.objects.filter(pdf=pdf_id).all()
# Serializovat list je prý security vulnerability, tedy je přidán slovník pro bezpečnost
return JsonResponse({"context": [OpravaSerializer(oprava).data for oprava in opravy]})

View file

@ -1,14 +0,0 @@
from django import forms
class OpravaForm(forms.Form):
""" formulář k přidání opravy (:class:`korektury.models.Oprava`) """
text = forms.CharField(max_length=256)
autor = forms.CharField(max_length=20)
x = forms.IntegerField()
y = forms.IntegerField()
scroll = forms.CharField(max_length=256)
pdf = forms.CharField(max_length=256)
img_id = forms.CharField(max_length=256)
id = forms.CharField(max_length=256)
action = forms.CharField(max_length=256)

View file

@ -1,39 +0,0 @@
# Generated by Django 2.2.28 on 2023-07-31 17:54
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('korektury', '0020_lepsi_popis_nazvu_PDF_v_adminu'),
('personalni', '0002_initial'),
]
run_before = [
('seminar', '0116_smazani_personalniho'),
]
operations = [
migrations.SeparateDatabaseAndState(
state_operations=[
migrations.AlterField(
model_name='komentar',
name='autor',
field=models.ForeignKey(blank=True, help_text='Autor komentáře', null=True, on_delete=django.db.models.deletion.SET_NULL, to='personalni.Organizator'),
),
migrations.AlterField(
model_name='korekturovanepdf',
name='org',
field=models.ForeignKey(blank=True, default=None, help_text='Zodpovědný organizátor za obsah', null=True, on_delete=django.db.models.deletion.SET_NULL, to='personalni.Organizator'),
),
migrations.AlterField(
model_name='oprava',
name='autor',
field=models.ForeignKey(blank=True, help_text='Autor opravy', null=True, on_delete=django.db.models.deletion.SET_NULL, to='personalni.Organizator'),
),
],
database_operations=[],
),
]

View file

@ -0,0 +1,13 @@
# Generated by Django 4.2.8 on 2024-03-12 20:24
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('korektury', '0020_lepsi_popis_nazvu_PDF_v_adminu'),
]
operations = [
]

View file

@ -0,0 +1,30 @@
# Generated by Django 4.2.11 on 2024-03-19 21:35
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('personalni', '0003_initial'),
('korektury', '0021_auto_20240312_2124'),
]
operations = [
migrations.AlterField(
model_name='komentar',
name='autor',
field=models.ForeignKey(blank=True, help_text='Autor komentáře', null=True, on_delete=django.db.models.deletion.SET_NULL, to='personalni.organizator'),
),
migrations.AlterField(
model_name='korekturovanepdf',
name='org',
field=models.ForeignKey(blank=True, default=None, help_text='Zodpovědný organizátor za obsah', null=True, on_delete=django.db.models.deletion.SET_NULL, to='personalni.organizator'),
),
migrations.AlterField(
model_name='oprava',
name='autor',
field=models.ForeignKey(blank=True, help_text='Autor opravy', null=True, on_delete=django.db.models.deletion.SET_NULL, to='personalni.organizator'),
),
]

View file

@ -0,0 +1,14 @@
# Generated by Django 4.2.11 on 2024-03-26 21:25
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('korektury', '0022_alter_komentar_autor_alter_korekturovanepdf_org_and_more'),
('personalni', '0005_personalni_post_migrate'),
]
operations = [
]

View file

@ -0,0 +1,41 @@
# Generated by Django 4.2.13 on 2024-06-11 23:53
from django.db import migrations, models
def pridej_orgy(apps, schema_editor):
PDF = apps.get_model('korektury', 'KorekturovanePDF')
for pdf in PDF.objects.all(): # Tohle by asi mělo jít udělat pomocí update, ale moc práce a rychlé hledání taky nepomohlo.
if pdf.org is not None: pdf.orgove.add(pdf.org)
pdf.save() # ig?
def vyber_orga(apps, schema_editor):
PDF = apps.get_model('korektury', 'KorekturovanePDF')
for pdf in PDF.objects.all():
orgove = pdf.orgove.all()
if len(orgove) > 1:
raise migrations.exceptions.IrreversibleError(f'PDF {pdf.id} má víc než jednoho zodpovědného orga, nejde odmigrovat na verzi, která umí jen jednoho.')
if len(orgove) == 0:
pdf.org = None
else:
pdf.org = orgove[0]
pdf.save()
class Migration(migrations.Migration):
dependencies = [
('personalni', '0011_osloveni_vsechny_choices'),
('korektury', '0023_personalni_post_migrate'),
]
operations = [
migrations.AddField(
model_name='korekturovanepdf',
name='orgove',
field=models.ManyToManyField(blank=True, default=None, help_text='Zodpovědní organizátoři za obsah (chodí jim maily o nových korekturách)', to='personalni.organizator'),
),
migrations.RunPython(pridej_orgy, vyber_orga),
migrations.RemoveField(
model_name='korekturovanepdf',
name='org',
),
]

View file

@ -0,0 +1,45 @@
# Generated by Django 4.2.16 on 2024-12-12 10:25
from django.db import migrations
import datetime
from django.utils import timezone
def oprava2komentar(apps, schema_editor):
Oprava = apps.get_model('korektury', 'Oprava')
Komentar = apps.get_model('korektury', 'Komentar')
for o in Oprava.objects.all():
Komentar.objects.create(oprava=o, text=o.text, autor=o.autor, cas=timezone.make_aware(datetime.datetime.fromtimestamp(0)))
def komentar2oprava(apps, schema_editor):
Oprava = apps.get_model('korektury', 'Oprava')
Komentar = apps.get_model('korektury', 'Komentar')
for o in Oprava.objects.all():
k = Komentar.objects.filter(oprava=o).first()
o.text = k.text
o.autor = k.autor
o.save()
k.delete()
class Migration(migrations.Migration):
dependencies = [
('korektury', '0024_vic_orgu_k_pdf'),
]
operations = [
migrations.RunPython(oprava2komentar, komentar2oprava),
migrations.RemoveField(
model_name='oprava',
name='autor',
),
migrations.RemoveField(
model_name='oprava',
name='text',
),
]

View file

@ -0,0 +1,29 @@
# Generated by Django 4.2.16 on 2025-02-11 14:28
from django.db import migrations, models
def pridani_orgu(apps, _schema_editor):
Komentar = apps.get_model('korektury','Komentar')
for komentar in Komentar.objects.all():
org = komentar.autor
if org is not None:
# Tohle jde asi udělat lépe než .all(…). Ale nejhorší na tom je, že .add(…) funguje jinak tady v migracích.
if org not in komentar.oprava.informovani_orgove.all():
komentar.oprava.informovani_orgove.add(org)
class Migration(migrations.Migration):
dependencies = [
('personalni', '0019_rename_upozorneni_resitel_upozornovat_na_opravy_reseni'),
('korektury', '0025_remove_oprava_autor_remove_oprava_text'),
]
operations = [
migrations.AddField(
model_name='oprava',
name='informovani_orgove',
field=models.ManyToManyField(blank=True, default=None, help_text='Orgové informovaní při přidání komentáře ke korektuře', related_name='informovan_o_opravach', to='personalni.organizator', verbose_name='Informovaní organizátoři'),
),
migrations.RunPython(pridani_orgu, migrations.RunPython.noop),
]

View file

@ -0,0 +1,27 @@
# Generated by Django 4.2.16 on 2025-02-11 16:07
import colorfield.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('korektury', '0026_oprava_informovani_orgove'),
]
operations = [
migrations.CreateModel(
name='KorekturaTag',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('nazev', models.CharField(help_text='Název daného tagu, <20 znaků', max_length=20, verbose_name='název tagu')),
('barva', colorfield.fields.ColorField(default='#FFFFFF', image_field=None, max_length=25, samples=None, verbose_name='barva daného tagu')),
],
),
migrations.AddField(
model_name='oprava',
name='tagy',
field=models.ManyToManyField(blank=True, default=None, to='korektury.korekturatag'),
),
]

View file

@ -0,0 +1,22 @@
# Generated by Django 4.2.16 on 2025-03-19 18:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('korektury', '0027_korekturatag_oprava_tagy'),
]
operations = [
migrations.AlterModelOptions(
name='korekturovanepdf',
options={'ordering': ['-cas'], 'verbose_name': 'PDF ke korekturování', 'verbose_name_plural': 'PDFka ke korekturování'},
),
migrations.AlterField(
model_name='korekturovanepdf',
name='nazev',
field=models.CharField(help_text='Název (např. „42.6 | analýza v4“ nebo „propagace | letáček v0“) korekturovaného PDF, v seznamu se seskupují podle textu do první mezery, tedy zde „42.6“ respektive „propagace“.', max_length=50, verbose_name='název PDF'),
),
]

View file

@ -1,5 +1,9 @@
import os import os
from colorfield.fields import ColorField
from django.db import models from django.db import models
from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.conf import settings from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
@ -26,42 +30,38 @@ def generate_filename(self, filename):
clean) clean)
return os.path.join(settings.KOREKTURY_PDF_DIR, fname) return os.path.join(settings.KOREKTURY_PDF_DIR, fname)
#@reversion.register(ignore_duplicates=True) #@reversion.register(ignore_duplicates=True)
class KorekturovanePDF(models.Model): class KorekturovanePDF(models.Model):
class Meta: class Meta:
ordering = ['-cas'] ordering = ['-cas']
db_table = 'korekturovane_cislo' db_table = 'korekturovane_cislo'
verbose_name = u'PDF k opravám' verbose_name = 'PDF ke korekturování'
verbose_name_plural = u'PDF k opravám' verbose_name_plural = 'PDFka ke korekturování'
#Interní ID #Interní ID
id = models.AutoField(primary_key = True) id = models.AutoField(primary_key = True)
cas = models.DateTimeField(u'čas vložení PDF',default=timezone.now,help_text = 'Čas vložení PDF') cas = models.DateTimeField(u'čas vložení PDF',default=timezone.now,help_text = 'Čas vložení PDF')
nazev = models.CharField(u'název PDF',blank = False,max_length=50, help_text='Název (např. `22.1 | analyza v4` nebo `propagace | letacek v0`) korekturovaného PDF') nazev = models.CharField(u'název PDF',blank = False,max_length=50, help_text='Název (např. „42.6 | analýza v4“ nebo „propagace | letáček v0“) korekturovaného PDF, v seznamu se seskupují podle textu do první mezery, tedy zde „42.6“ respektive „propagace“.')
komentar = models.TextField(u'komentář k PDF',blank = True, help_text='Komentář ke korekturovanému PDF (např. na co se zaměřit)') komentar = models.TextField(u'komentář k PDF',blank = True, help_text='Komentář ke korekturovanému PDF (např. na co se zaměřit)')
pdf = models.FileField(u'PDF', upload_to = generate_filename) pdf = models.FileField(u'PDF', upload_to = generate_filename)
org = models.ForeignKey(Organizator, blank=True, orgove = models.ManyToManyField(Organizator, blank=True,
help_text='Zodpovědný organizátor za obsah', help_text='Zodpovědní organizátoři za obsah (chodí jim maily o nových korekturách)',
null=True, default=None, on_delete=models.SET_NULL) default=None)
stran = models.IntegerField(u'počet stran', help_text='Počet stran PDF', stran = models.IntegerField(u'počet stran', help_text='Počet stran PDF',
default=0) default=0)
STATUS_PRIDAVANI = 'pridavani'
STATUS_ZANASENI = 'zanaseni' class STATUS(models.TextChoices):
STATUS_ZASTARALE = 'zastarale' PRIDAVANI = 'pridavani', 'Přidávání korektur'
STATUS_CHOICES = ( ZANASENI = 'zanaseni', 'Korektury jsou zanášeny'
(STATUS_PRIDAVANI, u'Přidávání korektur'), ZASTARALE = 'zastarale', 'Stará verze, nekorigovat'
(STATUS_ZANASENI, u'Korektury jsou zanášeny'),
(STATUS_ZASTARALE, u'Stará verze, nekorigovat'), status = models.CharField(u'stav PDF',max_length=16, choices=STATUS.choices, blank=False, default = STATUS.PRIDAVANI)
)
status = models.CharField(u'stav PDF',max_length=16, choices=STATUS_CHOICES, blank=False,
default = STATUS_PRIDAVANI)
poslat_mail = models.BooleanField('Poslat mail o novém PDF', default=True, poslat_mail = models.BooleanField('Poslat mail o novém PDF', default=True,
help_text='Určuje, zda se má o nově nahraném PDF poslat e-mail do mam-org. Při upravování existujícího souboru už nemá žádný vliv.', help_text='Určuje, zda se má o nově nahraném PDF poslat e-mail do mam-org. Při upravování existujícího souboru už nemá žádný vliv.',
@ -129,6 +129,17 @@ class KorekturovanePDF(models.Model):
return nazev_split[0] # + " " + nazev_split[2] return nazev_split[0] # + " " + nazev_split[2]
except IndexError: except IndexError:
return self.nazev return self.nazev
def get_absolute_url(self):
return reverse('korektury', kwargs={'pdf': self.id})
class KorekturaTag(models.Model):
nazev = models.CharField("název tagu", blank = False, max_length=20, help_text="Název daného tagu, <20 znaků")
barva = ColorField("barva daného tagu", default="#FFFFFF")
def __str__(self):
return self.nazev
@reversion.register(ignore_duplicates=True) @reversion.register(ignore_duplicates=True)
@ -149,32 +160,22 @@ class Oprava(models.Model):
x = models.IntegerField(u'x-ová souřadnice bugu') x = models.IntegerField(u'x-ová souřadnice bugu')
y = models.IntegerField(u'y-ová souřadnice bugu') y = models.IntegerField(u'y-ová souřadnice bugu')
STATUS_K_OPRAVE = 'k_oprave' class STATUS(models.TextChoices):
STATUS_OPRAVENO = 'opraveno' K_OPRAVE = 'k_oprave', 'K opravě'
STATUS_NENI_CHYBA = 'neni_chyba' OPRAVENO = 'opraveno', 'Opraveno'
STATUS_K_ZANESENI = 'k_zaneseni' NENI_CHYBA = 'neni_chyba', 'Není chyba'
STATUS_CHOICES = ( K_ZANESENI = 'k_zaneseni', 'K zanesení do TeXu'
(STATUS_K_OPRAVE, u'K opravě'),
(STATUS_OPRAVENO, u'Opraveno'),
(STATUS_NENI_CHYBA, u'Není chyba'),
(STATUS_K_ZANESENI, u'K zanesení do TeXu'),
)
status = models.CharField(u'stav opravy',max_length=16, choices=STATUS_CHOICES, blank=False,
default = STATUS_K_OPRAVE)
autor = models.ForeignKey(Organizator, blank = True, status = models.CharField(u'stav opravy',max_length=16, choices=STATUS.choices, blank=False, default = STATUS.K_OPRAVE)
help_text='Autor opravy',
null = True, on_delete=models.SET_NULL)
text = models.TextField(u'text opravy',blank = True, help_text='Text opravy')
# def __init__(self,dictionary): informovani_orgove = models.ManyToManyField(
# for k,v in dictionary.items(): Organizator, blank=True, default=None,
# setattr(self,k,v) verbose_name='Informovaní organizátoři',
help_text="Orgové informovaní při přidání komentáře ke korektuře",
def __str__(self): related_name='informovan_o_opravach',
return '{} od {}: {}'.format(self.status,self.autor,self.text) )
tagy = models.ManyToManyField(KorekturaTag, blank=True, default=None,)
@reversion.register(ignore_duplicates=True) @reversion.register(ignore_duplicates=True)
@ -200,5 +201,7 @@ class Komentar(models.Model):
def __str__(self): def __str__(self):
return '{} od {}: {}'.format(self.cas,self.autor,self.text) return '{} od {}: {}'.format(self.cas,self.autor,self.text)
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
if self.autor is not None:
self.oprava.informovani_orgove.add(self.autor)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 697 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 717 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 270 B

After

Width:  |  Height:  |  Size: 307 B

View file

@ -0,0 +1,10 @@
<svg id="icon-reload" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 120 120" xml:space="preserve">
<g>
<path d="M60,95.5c-19.575,0-35.5-15.926-35.5-35.5c0-19.575,15.925-35.5,35.5-35.5c13.62,0,25.467,7.714,31.418,19h22.627
C106.984,20.347,85.462,3.5,60,3.5C28.796,3.5,3.5,28.796,3.5,60c0,31.203,25.296,56.5,56.5,56.5
c16.264,0,30.911-6.882,41.221-17.88L85.889,84.255C79.406,91.168,70.201,95.5,60,95.5z"/>
</g>
<line fill="none" x1="120" y1="0" x2="120" y2="45.336"/>
<line fill="none" x1="91.418" y1="43.5" x2="114.045" y2="43.5"/>
<polygon points="120,21.832 119.992,68.842 74.827,55.811 "/>
</svg>

After

Width:  |  Height:  |  Size: 712 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 702 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 617 B

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -1,136 +1,220 @@
body, .textzanaseni { display:none; }
.adding{ .textzastarale { display:none; }
background: #f3f3f3; #prekomentar, #prekorektura, #prepointer { display: none; }
color: black;
body {
&[data-stav_pdf="pridavani"] {
background: #f3f3f3;
}
&[data-stav_pdf="zanaseni"] {
background: yellow;
.textzanaseni { display: unset; }
}
&[data-stav_pdf="zastarale"] {
background: red;
.textzastarale { display: unset; }
}
} }
.comitting
{ #sbal-korektury, #rozbal-korektury {
background: yellow; float: right;
} margin-left: 4pt;
.deprecated {
background: red;
} }
img{background:white;} img{background:white;}
/* Barvy korektur */ /* Barvy korektur */
.k_oprave { [data-stav_korektury="k_oprave"] {
--rgb: 255, 0, 0; --rgb: 255, 0, 0;
[value="k_oprave"] { display: none }
.komentovat_disabled { display: none }
} }
.opraveno { [data-stav_korektury="opraveno"] {
--rgb: 0, 0, 255; --rgb: 0, 0, 255;
[value="opraveno"] { display: none }
.komentovat { display: none }
} }
.neni_chyba { [data-stav_korektury="neni_chyba"] {
--rgb: 128, 128, 128; --rgb: 128, 128, 128;
[value="neni_chyba"] { display: none }
.komentovat { display: none }
} }
.k_zaneseni { [data-stav_korektury="k_zaneseni"] {
--rgb: 0, 255, 0; --rgb: 0, 255, 0;
[value="k_zaneseni"] { display: none }
.komentovat { display: none }
} }
.pointer-hi, /* Skrývání korektur */
[data-korektura_sbalena="true"] {
.korektura-telo { display: none; }
.korektura-tlacitka { display: none; }
.sbal-rozbal-img { transform: rotate(180deg); }
}
/* Skrývání komentářů */
[data-komentar_sbalen="true"] {
.sbal-rozbal-img { transform: rotate(180deg); }
.uprav-komentar { display: none; }
.komtext { display: none; }
}
/* Čára od textu k místu korektury */
.pointer{ .pointer{
position:absolute; position:absolute;
/*border-bottom-left-radius: 10px; */ border-bottom-left-radius: 10px;
border-left: 2px solid yellow; border-left: 1px solid rgb(var(--rgb),var(--alpha));
border-bottom: 2px solid yellow; border-bottom: 1px solid rgb(var(--rgb),var(--alpha));
border-color: rgb(var(--rgb),var(--alpha)); pointer-events: none;
--alpha: 0.35;
/* Zvýraznění čáry při najetí na korekturu */
&[data-hover="true"] {
border-width: 3px;
--alpha: 1;
}
} }
.pointer { /* Korektura samotná */
border-width: 1px; .korektura {
--alpha: 0.35;
}
.pointer-hi {
border-width: 3px;
--alpha: 1;
}
.box:hover{
border-width:3px;
margin: 0px;
}
.box {
margin: 1px; margin: 1px;
background-color: white; background-color: white;
width:300px; width: 300px;
/*position:absolute;*/
padding: 3px; padding: 3px;
border: 2px solid black; border: 2px solid rgb(var(--rgb));
border-radius: 10px; border-radius: 10px;
border-color: rgb(var(--rgb)); position: absolute;
&:hover {
border-width:3px;
margin: 0;
}
button, img {
border: 1px solid white;
background-color:transparent;
margin:0;
padding: 1px;
&:hover {
border: 1px solid black;
}
}
button img { pointer-events: none; }
.hlavicka-komentare {
overflow: auto;
}
.autor {
font-weight: bold;
float: left;
margin-top: 3px;
}
.float-right{
float:right;
}
} }
form { form {
display:inline; display:inline;
} }
.float-right{ /* Zobrazované PDF */
float:right;
}
.imgdiv { .imgdiv {
position:relative; position:relative;
left:0px; left:0;
top:0px; top:0;
} }
#commform-div {
display: none; /* Přidávání korektury / úprava komentáře */
#korekturovaci-formular-div {
position: absolute; position: absolute;
background-color: white; background-color: white;
border: 1px solid;
padding: 3px; padding: 3px;
/*
width: 310;
height: 220;
*/
z-index: 10; z-index: 10;
border: 4px solid red; border: 4px solid red;
border-radius: 10px; border-radius: 10px;
background-color: white;
opacity: 80%; opacity: 80%;
} }
.close-button{
background-color: yellow; .korektury-tag {
border-radius: 5px;
margin: 2px;
padding: 2px;
&[data-vybran="false"] { background: unset !important; }
/*&[data-vybran="true"] { border-color: unset !important; }*/
}
/* Šipky na posouvání korektur */
#korektury-sipky {
position: fixed;
bottom: 5px;
left: 5px;
opacity: 50%;
button, img {
border: 1px solid white;
background-color:transparent;
margin:0;
padding: 1px;
border-radius: 5px;
&:hover {
border: 1px solid black;
}
}
button img { pointer-events: none; }
#predchozi-korektura, #dalsi-korektura {
background-color: #EEEEEE;
}
#predchozi-korektura-k-oprave, #dalsi-korektura-k-oprave {
background-color: #FF0000;
}
#predchozi-korektura-k-zaneseni, #dalsi-korektura-k-zaneseni {
background-color: #00FF00;
}
/* Tlačítko na aktualizaci */
#korektury-aktualizace {
background-color: #e84e10;
}
} }
.box button, /**** ROZLIŠENÍ MEZI LOKÁLNÍM, TESTOVACÍM A PRODUKČNÍM WEBEM ****/
.box img, body.localweb, body.testweb, body.suprodweb {
.box-done button, &:before, &:after {
.box-done img, content: "";
.box-ready button, position: fixed;
.box-ready img, width: 20px;
.box-wontfix button, height: 100%;
.box-wontfix img{ top: 0;
border: 1px solid white; z-index: -1000;
background-color:transparent; }
margin:0;
padding: 1px; &:before { left: 0; }
} &:after { right: 0; }
.box button:hover,
.box img:hover,
.box-done img:hover,
.box-done button:hover,
.box-ready img:hover,
.box-ready button:hover,
.box-wontfix img:hover,
.box-wontfix button:hover{
border: 1px solid black;
}
.comment hr {
height: 0px;
}
.corr-header {
overflow: auto;
}
.author {
font-weight: bold;
float: left;
margin-top: 3px;
} }
body.localweb { &:before, &:after { background: greenyellow; } }
body.testweb { &:before, &:after { background: darkorange; } }
body.suprodweb { &:before, &:after { background: red; } }
/****************************************************************/

View file

@ -1,283 +0,0 @@
function place_comments_one_div(img_id, comments)
{
var img = document.getElementById(img_id);
if( img == null ) {
return;
}
var par = img.parentNode;
var w = img.clientWidth;
var h = img.clientHeight;
var w_skip = 10;
var h_skip = 5;
var pointer_min_h = 30;
var bott_max = 0;
var comments_sorted = comments.sort(function (a,b) {
return a[2] - b[2];
//pokus o hezci kladeni poiteru, ale nic moc
if( a[3] < b[3] ) {
return (a[2] + pointer_min_h)- b[2];
} else {
return (a[2] - pointer_min_h)- b[2];
}
});
//console.log("w:" + w);
for (c in comments_sorted) {
var id = comments_sorted[c][0];
var x = comments_sorted[c][1];
var y = comments_sorted[c][2];
var el = document.getElementById(id);
var elp = document.getElementById(id + "-pointer");
if( el == null || elp == null ) {
continue;
}
par.appendChild(elp);
par.appendChild(el);
var delta_y = (y > bott_max) ? 0: bott_max - y + h_skip;
elp.style.left = x;
elp.style.top = y ;
elp.style.width = w - x + w_skip;
elp.style.height = pointer_min_h + delta_y;
elp.img_id = img_id;
el.img_id = img_id;
el.style.position = 'absolute';
el.style.left = w + w_skip;
el.style.top = y + delta_y;
var bott = el.offsetTop + el.offsetHeight;
bott_max = ( bott_max > bott ) ? bott_max : bott;
//console.log( "par.w:" + par.style.width);
}
if( par.offsetHeight < bott_max ) {
//par.style.height = bott_max;
//alert("preteklo to:"+ par.offsetHeight +",mx:" + bott_max );
par.style.height = bott_max;
}
}
function place_comments() {
for (var i=0; i < comments.length-1; i++) {
place_comments_one_div(comments[i][0], comments[i][1])
}
}
// ctrl-enter submits form
function textarea_onkey(ev)
{
//console.log("ev:" + ev.keyCode + "," + ev.ctrlKey);
if( (ev.keyCode == 13 || ev.keyCode == 10 ) && ev.ctrlKey ) {
var form = document.getElementById('commform');
if( form ) {
save_scroll(form);
//form.action ='';
form.submit();
}
return true;
}
return false;
}
//hide comment form
function close_commform() {
var formdiv = document.getElementById('commform-div');
if( formdiv == null ) {
alert("form null");
return true;
}
formdiv.style.display = 'none';
return false;
}
// show comment form, when clicked to image
function img_click(element, ev) {
var body_class = document.body.className;
switch(body_class){
case "comitting":
if (!confirm("Právě jsou zanášeny korektury, opravdu chcete přidat novou?"))
return;
break;
case "deprecated":
if (!confirm("Toto PDF je již zastaralé, opravdu chcete vytvořit korekturu?"))
return;
break;
}
var dx, dy;
var par = element.parentNode;
if( ev.pageX != null ) {
dx = ev.pageX - par.offsetLeft;
dy = ev.pageY - par.offsetTop;
} else { //IE
dx = ev.offsetX;
dy = ev.offsetY;
}
var img_id = element.id;
if( element.img_id != null ) {
// click was to '-pointer'
img_id = element.img_id;
}
return show_form(img_id, dx, dy, '', '', '', '');
}
// hide or show text of correction
function toggle_visibility(oid){
var buttondiv = document.getElementById(oid+'-buttons')
var text = document.getElementById(oid+'-body');
if (text.style.display == 'none'){
text.style.display = 'block';
buttondiv.style.display = 'inline-block';
}else {
text.style.display = 'none';
buttondiv.style.display = 'none';
}
for (var i=0;i<comments.length-1;i++){
place_comments_one_div(comments[i][0], comments[i][1])
}
}
// show comment form, when 'edit' or 'comment' button pressed
function box_edit(oid, action)
{
var divpointer = document.getElementById(oid + '-pointer');
var text;
if (action == 'update') {
var text_el = document.getElementById(oid + '-text');
text = text_el.textContent; // FIXME původně tu bylo innerHTML.unescapeHTML()
} else {
text = '';
}
var dx = parseInt(divpointer.style.left);
var dy = parseInt(divpointer.style.top);
var divbox = document.getElementById(oid);
//alert('not yet 2:' + text + text_el); // + divpointer.style.top "x" + divpo );
id = oid.substring(2);
return show_form(divbox.img_id, dx, dy, id, text, action);
}
// show comment form when 'update-comment' button pressed
function update_comment(oid,ktid)
{
var divpointer = document.getElementById(oid + '-pointer');
var dx = parseInt(divpointer.style.left);
var dy = parseInt(divpointer.style.top);
var divbox = document.getElementById(oid);
var text = document.getElementById(ktid).textContent; // FIXME původně tu bylo innerHTML.unescapeHTML()
return show_form(divbox.img_id, dx, dy, ktid.substring(2), text, 'update-comment');
}
//fill up comment form and show him
function show_form(img_id, dx, dy, id, text, action) {
var form = document.getElementById('commform');
var formdiv = document.getElementById('commform-div');
var textarea = document.getElementById('commform-text');
var inputX = document.getElementById('commform-x');
var inputY = document.getElementById('commform-y');
var inputImgId = document.getElementById('commform-img-id');
var inputId = document.getElementById('commform-id');
var inputAction = document.getElementById('commform-action');
var img = document.getElementById(img_id);
if( formdiv == null || textarea == null ) {
alert("form null");
return 1;
}
//form.action = "#" + img_id;
// set hidden values
inputX.value = dx;
inputY.value = dy;
inputImgId.value = img_id;
inputId.value = id;
inputAction.value = action;
textarea.value = text;
//textarea.value = "dxy:"+ dx + "x" + dy + "\n" + 'id:' + img_id;
// show form
formdiv.style.display = 'block';
formdiv.style.left = dx;
formdiv.style.top = dy;
img.parentNode.appendChild(formdiv);
textarea.focus();
return true;
}
function box_onmouseover(box)
{
var id = box.id;
var pointer = document.getElementById(box.id + '-pointer');
pointer.classList.remove('pointer');
pointer.classList.add('pointer-hi');
}
function box_onmouseout(box)
{
var id = box.id;
var pointer = document.getElementById(box.id + '-pointer');
pointer.classList.remove('pointer-hi');
pointer.classList.add('pointer');
}
function save_scroll(form)
{
//alert('save_scroll:' + document.body.scrollTop);
form.scroll.value = document.body.scrollTop;
//alert('save_scroll:' + form.scroll.value);
return true;
}
function toggle_corrections(aclass)
{
var stylesheets = document.styleSheets;
var ssheet = null;
for (var i=0;i<stylesheets.length; i++){
if (stylesheets[i].title === "opraf-css"){
ssheet = stylesheets[i];
break;
}
}
if (! ssheet){
return;
}
for (var i=0;i<ssheet.cssRules.length;i++){
var rule = ssheet.cssRules[i];
if (rule.selectorText === '.'+aclass){
if (rule.style.display === ""){
rule.style.display = "none";
} else {
rule.style.display = "";
}
}
}
place_comments();
}
String.prototype.unescapeHTML = function () {
return(
this.replace(/&amp;/g,'&').
replace(/&gt;/g,'>').
replace(/&lt;/g,'<').
replace(/&quot;/g,'"')
);
};

View file

@ -0,0 +1,66 @@
{# Část korekturovátka, která obsahuje všechno okolo korektur #}
{% include "korektury/korekturovatko/moduly/schovani_korektur.html" %}
{% include "korektury/korekturovatko/moduly/edit_komentar.html" %}
{% include "korektury/korekturovatko/moduly/stranky_pdfka.html" %}
{# {% for k in korektury %} {% include "korektury/korekturovatko/korektura.html" %} {% endfor %} #}
{% include "korektury/korekturovatko/moduly/korektura.html" %}
{% include "korektury/korekturovatko/moduly/komentar.html" %}
{% include "korektury/korekturovatko/moduly/dalsi_korektura.html" %}
<script>
/**
* Fetchne korektury a komentáře a na základě toho aktualizuje všechno
* (korektury, komentáře, zásluhy, počty korektur v daných stavech, umístění korektur)
* @param {RequestInit} data FormData a jiné náležitosti (method: POST) posílané při přidání/úpravě korektury/komentáře
* @param {Boolean} catchError jestli padat hlasitě (pokud se aktualizuje automaticky a spadne to např. na nepřítomnost sítě, pak není třeba informovat uživatele)
* @param {(() => *)?} pri_uspechu akce, která se má provést při úspěchu (speciálně zavřít formulář)
*/
function aktualizuj_vse(data={}, catchError=true, pri_uspechu=null) { // FIXME není mi jasné, zda v {} nemá být `cache: "no-store"`, aby prohlížeč necachoval GET.
fetch('{% url "korektury_api_opravy_a_komentare" korekturovanepdf.id %}', data)
.then(response => {
if (!response.ok && catchError) {alert('Něco se nepovedlo:' + response.statusText);}
else response.json().then(data => {
for (const korektura_data of data["context"]) {
const korektura = Korektura.aktualizuj_nebo_vytvor(korektura_data);
for (const komentar_data of korektura_data["komentare"]) {
Komentar.aktualizuj_nebo_vytvor(komentar_data, korektura);
}
}
aktualizuj_pocty_stavu();
aktualizuj_pocty_zasluh();
umisti_korektury();
if (pri_uspechu) pri_uspechu();
});
})
.catch(error => {if (catchError) alert('Něco se nepovedlo:' + error);});
}
window.addEventListener("load", _ => {
aktualizuj_vse({}, true, () => {
if (location.hash !== "") { // Po rozházení korektur sescrollujeme na kotvu v URL
const h = location.hash.substring(1);
location.hash = "HACK";
location.hash = h;
}
});
});
// FIXME není mi jasné, zda v {} nemá být `cache: "no-store"`, aby prohlížeč necachoval GET.
setInterval(() => aktualizuj_vse({}, false), 120000); // Každý dvě minuty fetchni korektury
</script>
{# Formulář, který mouhou použít tlačítka bez svého formuláře k vytvoření POST requestu, viz CSRF_FORM níže #}
<form id='CSRF_form' style='display: none'>{% csrf_token %}</form>
<script>
/**
* Formulář, který mouhou použít tlačítka bez svého formuláře k vytvoření POST requestu
* @type {HTMLFormElement}
*/
const CSRF_FORM = document.getElementById('CSRF_form');
</script>

View file

@ -0,0 +1,64 @@
{# Okolí samotného hlavni_cast_korekturovatka.html, tedy „povinné HTML věci“, informace o korekturovaném PDF a starání se o stav PDF #}
{% load static %}
<html lang='cs'>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" title="opraf-css" type="text/css" media="screen, projection" href="{% static "korektury/opraf.css"%}?version=3" />
<link href="{% static 'css/rozliseni.css' %}?version=3" rel="stylesheet">
<title>Korektury {{korekturovanepdf.nazev}}</title>
</head>
<body class="{{ LOCAL_TEST_PROD }}web" data-stav_pdf="{{ korekturovanepdf.status }}">
<h1>Korektury {{korekturovanepdf.nazev}}</h1>
<h2 class="textzanaseni"> Probíhá zanášení korektur, zvažte, zda chcete přidávat nové </h2>
<h2 class="textzastarale"> Toto PDF je již zastaralé, nepřidávejte nové korektury </h2>
<i>{{korekturovanepdf.komentar}}</i>
<br>
<i>Klikni na chybu, napiš komentář</i> |
<a href="{{korekturovanepdf.pdf.url}}">stáhnout PDF (bez korektur)</a> |
<a href="../">seznam souborů</a> |
<a href="/admin/korektury/korekturovanepdf/">Spravovat PDF</a> |
<a href="../help">nápověda</a> |
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|
<a href="/">hlavní stránka</a> |
<a href="https://mam.mff.cuni.cz/wiki">wiki</a> |
<hr />
{% include "korektury/korekturovatko/hlavni_cast_korekturovatka.html" %}
{% include "korektury/korekturovatko/zmena_stavu_pdf.html" %}
<hr/>
<p>
Děkujeme opravovatelům: <span id="pocty_autoru"></span></p>
<hr>
<script>
/**
* HTML prvek, kam se zapíší (pomocí .innerHTML) počty korektur jednotlivých autorů
* @type {HTMLElement}
*/
const span_s_pocty_autoru = document.getElementById("pocty_autoru")
/** Aktualizuje, kolik který autor má komentářů u daného korekturovaného PDF. */
function aktualizuj_pocty_zasluh() {
const pocty_autoru = {};
for (let komentar of Object.values(komentare)) {
if (!(komentar.autor in pocty_autoru)) pocty_autoru[komentar.autor] = 0;
pocty_autoru[komentar.autor] += 1;
}
const setrizene = [];
for (const keyval of Object.entries(pocty_autoru)) setrizene.push(keyval);
setrizene.sort(function(a, b) {return a[1] - b[1];});
let ans = "";
for (let [autor, pocet] of setrizene) ans += `, ${autor} (${pocet})`;
span_s_pocty_autoru.innerHTML = ans.substring(2);
}
</script>
</body>
</html>

View file

@ -0,0 +1,77 @@
{# Template starající se o tlačítka v levém dolním rohu, především skákající na další/předchozí korekturu. #}
{% load static %}
<div id="korektury-sipky">
<button type='button' id="predchozi-korektura" title='Předchozí korektura'>
<img src='{% static "korektury/imgs/hide.png" %}' alt='⬆'/>
</button>
<button type='button' id="predchozi-korektura-k-oprave" title='Předchozí korektura k opravě'>
<img src='{% static "korektury/imgs/hide.png" %}' alt='⬆'/>
</button>
<button type='button' id="predchozi-korektura-k-zaneseni" title='Předchozí korektura k zaneseni'>
<img src='{% static "korektury/imgs/hide.png" %}' alt='⬆'/>
</button>
<br>
<button type='button' id="dalsi-korektura" title='Další korektura'>
<img src='{% static "korektury/imgs/hide.png" %}' alt='⬇' style="transform: rotate(180deg);"/>
</button>
<button type='button' id="dalsi-korektura-k-oprave" title='Další korektura k opravě'>
<img src='{% static "korektury/imgs/hide.png" %}' alt='⬇' style="transform: rotate(180deg);"/>
</button>
<button type='button' id="dalsi-korektura-k-zaneseni" title='Další korektura k zaneseni'>
<img src='{% static "korektury/imgs/hide.png" %}' alt='⬇' style="transform: rotate(180deg);"/>
</button>
<button type='button' id='korektury-aktualizace'
title='Aktualizuj korektury
Nemusíš mačkat, pokud ti stačí, že se korektury aktualizují samy každé 2 minuty a při každém přidání korektury/komentáře.'
>
<img src='{% static "korektury/imgs/reload.svg" %}' alt='↻' style="width: 15px"/>
</button>
</div>
<script>
document.getElementById('predchozi-korektura').addEventListener('click', _ => { dalsi_nebo_predchozi_korektura(false) });
document.getElementById('dalsi-korektura').addEventListener('click', _ => { dalsi_nebo_predchozi_korektura(true) });
document.getElementById('predchozi-korektura-k-oprave').addEventListener('click', _ => { dalsi_nebo_predchozi_korektura(false, "k_oprave") });
document.getElementById('dalsi-korektura-k-oprave').addEventListener('click', _ => { dalsi_nebo_predchozi_korektura(true, "k_oprave") });
document.getElementById('predchozi-korektura-k-zaneseni').addEventListener('click', _ => { dalsi_nebo_predchozi_korektura(false, "k_zaneseni") });
document.getElementById('dalsi-korektura-k-zaneseni').addEventListener('click', _ => { dalsi_nebo_predchozi_korektura(true, "k_zaneseni") });
// FIXME není mi jasné, zda v {} nemá být `cache: "no-store"`, aby prohlížeč necachoval GET.
document.getElementById("korektury-aktualizace").addEventListener("click", _ => aktualizuj_vse({}, false));
/**
* Sescrolluje na další nebo předchozí (vůči hornímu okraji okna) korekturu (v daném stavu).
* V případě neexistence takové korektury vyhodí alert.
* @param {boolean} dalsi reprezentuje, zda chceme další nebo předchozí korekturu
* @param {?string} stav pokud je nenullový, tak ignoruje korektury v jiném stavu
*/
function dalsi_nebo_predchozi_korektura(dalsi=true, stav=null) {
let predchozi = null;
for (const strana of setrizene_strany) {
// strana.setrid_korektury(); // Nemělo by být potřeba, protože se volá vždy, když se renderují korektury.
for (const korektura of strana.korektury) {
if (stav == null || korektura.stav === stav) {
const y = korektura.htmlElement.getBoundingClientRect().y;
if (y >= -1) {
if (dalsi) {
if (y > 1) {
korektura.htmlElement.scrollIntoView();
return;
}
} else {
if (predchozi !== null) predchozi.htmlElement.scrollIntoView(); else alert("Výše už není žádná taková korektura.");
return;
}
}
predchozi = korektura;
}
}
}
if (!dalsi && predchozi !== null) {
predchozi.htmlElement.scrollIntoView();
return;
}
alert("Žádná další korektura.");
}
</script>

View file

@ -0,0 +1,166 @@
{# Template starající se o editační/přidávací formulář. #}
<div id="korekturovaci-formular-div" style="display: none">
<input size="24" name="au" value="{{user.osoba}}" readonly/>
<button type="button" id="korekturovaci-formular-odesli">Oprav!</button>
<button type="button" id="korekturovaci-formular-zavri">Zavřít</button>
<br/>
<textarea id="korekturovaci-formular-text" cols=40 rows=10 name="txt"></textarea>
<br/>
<div id="korekturovaci-formular-tagy-info">Úprava tagů celé korektury:</div>
<div id="korekturovaci-formular-tagy">
{% for tag in tagy %}
<button type="button" class="korektury-tag" value="{{tag.id}}" data-vybran="false" style="background: {{ tag.barva }}; border-color: {{ tag.barva }};">{{tag.nazev}}</button>
{% endfor %}
</div>
</div>
<script>
/** V podstatě singleton (viz korekturovaci_formular) starající se o editační/přidávací formulář. */
class _KorekturovaciFormular {
/**
* <div> obsahující celý formulář.
* @type {HTMLElement}
*/
div;
/**
* Políčko, kam uživatel vyplňuje text.
* @type {HTMLElement}
*/
text;
/**
* Tlačítko odeslat. Často ho chceme disablenout.
* @type {HTMLElement}
*/
odesilaci_button;
/**
* <div> obsahující všechny tagy, pomocí tagy.getElementsByTagName("button") umíme dělat operace nad všemi tagy.
* @type {HTMLElement}
*/
tagy;
/**
* Text upozorňující na to, že tagy nepřidáváme, ale editujeme. (Tj. chceme ho schovat, když vytváříme novou korekturu.)
* @type {HTMLElement}
*/
tagy_info;
/**
* zda při přidávání nové korektury mají být všechny tagy odvybrané, nebo mají kopírovat předchozí nastavení
* @type {boolean}
*/
pri_otevreni_odvyber_tagy;
constructor() {
this.div = document.getElementById('korekturovaci-formular-div');
this.text = document.getElementById('korekturovaci-formular-text');
this.odesilaci_button = document.getElementById('korekturovaci-formular-odesli');
const zaviraci_button = document.getElementById('korekturovaci-formular-zavri');
this.tagy = document.getElementById('korekturovaci-formular-tagy');
this.tagy_info = document.getElementById('korekturovaci-formular-tagy-info');
// ctrl-enter odešle formulář
this.text.addEventListener("keydown", ev => {
if (ev.code === "Enter" && ev.ctrlKey) this.odesli_formular();
});
zaviraci_button.addEventListener("click", _ => { this.schovej(); });
this.odesilaci_button.addEventListener("click", _ => { this.odesli_formular(); });
for (const tag of this.tagy.getElementsByTagName("button")) tag.addEventListener("click", event => { this.vyber_nebo_odvyber_tag(event); });
this.pri_otevreni_odvyber_tagy = true;
}
/**
* Přepne tag na vybraný/nevybraný (v závislosti na tom, zda byl nevybrán/vybrán)
* @param {MouseEvent} event vyvolaný kliknutím na daný tag (musí mít za event.target daný tag)
*/
vyber_nebo_odvyber_tag(event) {
const button = event.target;
button.dataset.vybran = String(button.dataset.vybran === "false");
}
/** Nastaví všechny tagy na nevybrané. */
odvyber_tagy() { for (const tag of this.tagy.getElementsByTagName("button")) tag.dataset.vybran = "false"; }
/** Schová (zavře) korekturovací formulář */
schovej() { this.div.style.display = 'none'; }
/**
* Zobrazí/otevře korekturovací formulář (bez toho, aby v něm cokoliv měnil).
* @param {Strana} strana (na které straně se má zobrazit)
* @param {number} x
* @param {number} y
*/
_zobraz(strana, x, y) {
this.odesilaci_button.disabled = false;
this.div.style.display = 'block';
this.div.style.left = x;
this.div.style.top = y;
strana.htmlElement_div.appendChild(korekturovaci_formular.div);
this.text.focus();
}
/**
* Předvyplní správně korekturovací formulář a zobrazí/otevře ho
* @param {Strana} strana (na které straně se má zobrazit)
* @param {Number} x
* @param {Number} y
* @param {string} text (text k předvyplněný, místo null chceš psáť "")
* @param {Number} komentar_id (!= -1 znamená úprava komentáře, -1 znamená přidávání korektury/komentáře)
* @param {Number} korektura_id (v případě komentar_id != -1 znamená: -1 je nová korektura, ne-1 je nový komentář)
*/
zobraz(strana, x, y, text, korektura_id=-1, komentar_id=-1) {
if (this.div.style.display !== 'none' && this.text.value !== "" && !confirm("Zavřít předchozí okénko přidávání korektury / editace komentáře?")) return;
// set hidden values
this.x = x;
this.y = y;
this.strana = strana;
this.korektura_id = korektura_id;
this.komentar_id = komentar_id;
this.text.value = text;
// show form
if (korektura_id === -1 && komentar_id === -1) {
if (this.pri_otevreni_odvyber_tagy) this.odvyber_tagy();
this.tagy_info.style.display = 'none';
} else {
const korektura = korektury[korektura_id];
this.tagy_info.style.display = 'unset';
for (const tag of this.tagy.getElementsByTagName("button"))
tag.dataset.vybran = String(korektura.tagy.has(parseInt(tag.value)));
}
this._zobraz(strana, x, y);
}
/** Shrábne data a pošle daný požadavek, čímž kromě vyřízení dané věci aktualizuje korektury+komentáře. */
odesli_formular() {
this.odesilaci_button.disabled = true;
const data = new FormData(CSRF_FORM);
data.append('x', this.x);
data.append('y', this.y);
data.append('img_id', this.strana.id);
data.append('oprava_id', this.korektura_id);
data.append('komentar_id', this.komentar_id);
const tagy = [];
for (const tag of this.tagy.getElementsByTagName("button")) {
if (tag.dataset.vybran !== "false") tagy.push(tag.value);
}
data.append('tagy', String(tagy));
data.append('text', this.text.value);
aktualizuj_vse({method: 'POST', body: data}, true, () => {this.schovej(); this.odesilaci_button.disabled = false;});
}
}
/**
* Objekt starající se o editační/přidávací formulář (jeho předvyplňování, zobrazování a posílání).
* @type {_KorekturovaciFormular}
*/
const korekturovaci_formular = new _KorekturovaciFormular();
</script>

View file

@ -0,0 +1,168 @@
{# Template starající se o jeden každý komentář u korektury. #}
{% load static %}
<div class='comment' id='prekomentar' {# id='k{{k.id}}' #}>
<div class='hlavicka-komentare'>
<div class='autor'>{# {{k.autor}} #}</div>
<div class='float-right'>
<button type='button' style='display: none' class="smaz-komentar" title='Smaž komentář'>
<img src='{% static "korektury/imgs/delete.png" %}' alt='del'/>
</button>
<button type='button' class="uprav-komentar" title='Uprav komentář'>
<img src='{% static "korektury/imgs/edit.png" %}' alt='edit'/>
</button>
<button type='button' class='sbal-rozbal' title='Skrýt/Zobrazit'>
<img class='sbal-rozbal-img' src='{% static "korektury/imgs/hide.png" %}' alt='⬆'/>
</button>
</div>
</div>
<div class='komtext'>{# {{k.text|linebreaks}} #}</div>
<hr>
</div>
<script>
/**
* Prototyp komentáře, ze kterého se vygeneruje každý komentář (resp. jeho HTML reprezentace) v dokumentu
* @type {HTMLElement}
*/
const prekomentar = document.getElementById('prekomentar');
/**
* Mapování ID |-> komentář
* @type {Object.<Number, Komentar>}
*/
const komentare = {};
/** Třída reprezentující jeden komentář (a starající se o vytvoření a updatování jeho HTML reprezentace) */
class Komentar {
/**
* Z dat aktualizuje (v případě, že korektura s daným ID existuje) nebo vytvoří Komentar
* @param {Object.<string, ?>} komentar_data „Slovník“ obsahující data daného komentáře
* @param {Korektura} korektura ke které se komentář má připojit
*/
static aktualizuj_nebo_vytvor(komentar_data, korektura) {
const id = komentar_data['id'];
if (id in komentare) komentare[id].aktualizuj(komentar_data);
else new Komentar(komentar_data, korektura);
}
/**
* <div> se jménem autora komentáře
* @type {HTMLElement}
*/
#autor;
/**
* <div> obsahující text komentáře
* @type {HTMLElement}
*/
#text;
/**
* <div> reprezentující celý komentář
* @type {HTMLElement}
*/
htmlElement;
/** @type {Number} */
id;
/** @type{Korektura} */
korektura;
/** @type{string} */
autor;
/** @type {boolean} */
sbalen = false;
/**
* Vytvoří HTML reprezentaci, připojí komentář pod korekturu, nastaví event-listenery, uloží si data
* @param {Object.<string, ?>} komentar_data „Slovník“ obsahující data daného komentáře
* @param {Korektura} korektura korektura ke které se komentář má připojit
*/
constructor(komentar_data, korektura) {
this.htmlElement = prekomentar.cloneNode(true);
this.#autor = this.htmlElement.getElementsByClassName('autor')[0];
this.#text = this.htmlElement.getElementsByClassName('komtext')[0];
this.id = komentar_data['id'];
this.htmlElement.id = 'k' + this.id;
this.korektura = korektura;
this.korektura.pridej_htmlElement_komentare(this.htmlElement);
this.aktualizuj(komentar_data);
this.htmlElement.getElementsByClassName('sbal-rozbal')[0].addEventListener('click', _ => this.#sbal_nebo_rozbal());
this.htmlElement.getElementsByClassName('uprav-komentar')[0].addEventListener('click', _ => this.#uprav_komentar());
this.htmlElement.getElementsByClassName('smaz-komentar')[0].addEventListener('click', _ => this.#smaz_komentar());
komentare[this.id] = this;
}
/**
* Aktualizuje/nastaví JS data i HTML reprezentaci komentáře
* @param {Object.<string, ?>} komentar_data „Slovník“ obsahující data daného komentáře
*/
aktualizuj(komentar_data) {
this.set_autor(komentar_data['autor']);
this.set_text(komentar_data['text']);
};
/**
* Aktualizuje/nastaví JS data i HTML reprezentaci autora komentáře
* @param {String} autor
*/
set_autor(autor) {
this.#autor.textContent=autor;
this.autor = autor;
};
/**
* @param {String} text
*/
set_text(text) {
this.#text.innerHTML=text;
};
/** Sbalí/rozbalí (podle toho, zda byl rozbalený/sbalený) komentář, ale nezmění pozice korektur (je třeba později zavolat umisti_korektury()) */
sbal_nebo_rozbal() {
this.sbalen = !this.sbalen;
this.htmlElement.dataset.komentar_sbalen = String(this.sbalen);
}
/** Doplněk sbal_nebo_rozbal, který i přeskládá korektury. */
#sbal_nebo_rozbal(){
this.sbal_nebo_rozbal();
umisti_korektury();
}
/** Ukáže formulář na editaci komentáře (když je zmáčknuto „uprav-komentar“) */
#uprav_komentar() {
return korekturovaci_formular.zobraz(this.korektura.strana, this.korektura.x, this.korektura.y, this.#text.textContent, this.korektura.id, this.id);
}
/** Smaže komentář (když je zmáčknuto „smaz-komentar“) */
#smaz_komentar() {
if (confirm('Opravdu smazat komentář?')) {
const data = new FormData(CSRF_FORM);
data.append('komentar_id', this.id);
fetch('{% url "korektury_api_komentar_smaz" %}', {method: 'POST', body: data})
.then(response => {
if (!response.ok) {alert('Něco se nepovedlo:' + response.statusText);}
this.smaz_pouze_na_strance();
umisti_korektury();
})
.catch(error => {alert('Něco se nepovedlo:' + error);});
}
}
/** Smaže div komentáře (ne databázový záznam!), používá se, když je smazán komentář nebo jeho nadřazená korektura */
smaz_pouze_na_strance() {
delete komentare[this.id];
this.htmlElement.remove();
}
}
</script>

View file

@ -0,0 +1,271 @@
{% load static %}
<div id='prepointer' {# id='kor{{k.id}}-pointer' #}
class='pointer'
data-hover='false'
{# data-stav_korektury='{{k.status}}' #}
></div>
<div id='prekorektura' {# name='kor{{k.id}}' id='kor{{k.id}}' #}
class='korektura'
{# data-stav_korektury='{{k.status}}' #}
data-korektura_sbalena='false'
>
<div class="korektura-tagy">
{# {% for tag in k.tagy %} <span style="background:{{ tag.barva }}>{{ tag.text }}<span/> #}
</div>
<div class='korektura-telo'>
{# {% for k in k.komentare %} {% include "korektury/korekturovatko/komentar.html" %} {% endfor %} #}
</div>
<div class='hlavicka-komentare'>
<span class='float-right'>
<span class='korektura-tlacitka'>
<button type='button' style='display: none' class="smaz-korekturu" title='Smaž korekturu'>
<img src='{% static "korektury/imgs/delete.png" %}' alt='🗑️'/>
</button>
<button type='button' class='action' value='k_oprave' title='Označ jako neopravené'>
<img src='{% static "korektury/imgs/undo.png" %}' alt='↪'/>
</button>
<button type='button' class='action' value='opraveno' title='Označ jako opravené'>
<img src='{% static "korektury/imgs/check.png" %}' alt='✔️'/>
</button>
<button type='button' class='action' value='neni_chyba' title='Označ, že se nebude měnit'>
<img src='{% static "korektury/imgs/cross.png" %}' alt='❌'/>
</button>
<button type='button' class='action' value='k_zaneseni' title='Označ jako připraveno k zanesení'>
<img src='{% static "korektury/imgs/tex.png" %}' alt='TeX'/>
</button>
<a href='{% url "admin:korektury_oprava_change" -1 %}' class='edit' title='Uprav korekturu jako takovou.' style="text-decoration: none;"> {# FIXME Udělat z toho tlačítko? #}
<img src='{% static "korektury/imgs/edit.png" %}' alt='✏️' style="opacity: 0.5;"/> {# FIXME Odlišit jinak než pomocí opacity? #}
</a>
<button type='button' class='komentovat_disabled' title='Korekturu nelze komentovat, protože už je uzavřená' disabled=''>
<img src='{% static "korektury/imgs/comment-gr.png" %}' alt='💭'/>
</button>
<button type='button' class='komentovat' title='Komentovat'>
<img src='{% static "korektury/imgs/comment.png" %}' alt='💭'/>
</button>
</span>
<button type='button' class='sbal-rozbal' title='Skrýt/Zobrazit'>
<img class='sbal-rozbal-img' src='{% static "korektury/imgs/hide.png" %}' alt='⬆'/>
</button>
</span>
</div>
</div>
<script>
/**
* Prototyp korektury, ze kterého se vygeneruje každý komentář (resp. jeho HTML reprezentace) v dokumentu
* @type {HTMLElement}
*/
const prekorektura = document.getElementById('prekorektura');
/**
* Prototyp pointeru (té lomené čáry od korektury)
* @type {HTMLElement}
*/
const prepointer = document.getElementById('prepointer');
/**
* Mapování ID |-> korektura
* @type {Object.<Number, Korektura>}
*/
const korektury = {};
/** Třída reprezentující jednu korekturu (a starající se o vytvoření a updatování její HTML reprezentace) */
class Korektura {
/**
* Z dat aktualizuje (v případě, že korektura s daným ID existuje) nebo vytvoří Korekturu
* @param {Object.<string, ?>} korektura_data „Slovník“ obsahující data dané korektury
* @returns {Korektura} vytvořená/aktualizovaná Korektura (pro použití při vytváření/aktualizaci komentářů)
*/
static aktualizuj_nebo_vytvor(korektura_data) {
const id = korektura_data['id'];
if (id in korektury) return korektury[id].aktualizuj(korektura_data);
else return new Korektura(korektura_data);
}
/**
* <div> obsahující <div>y komentářů
* @type {HTMLElement}
*/
#komentare;
/**
* <div> obsahující tagy
* @type {HTMLElement}
*/
#tagy;
/**
* <div> reprezentující celý korekturu
* @type {HTMLElement}
*/
htmlElement;
/**
* <div> reprezentující pointer (tu lomenou čáru od korektury)
* @type {HTMLElement}
*/
pointer;
/** @type {Number} */
id;
/** @type {Number} */
x;
/** @type {Number} */
y;
/** @type {Strana} */
strana;
/** @type {string} */
stav;
/** @type {boolean} */
sbalena = false;
/** @type Set<Number> */
tagy;
/**
* Vytvoří HTML reprezentaci, připojí korekturu pod stranu (ale neumístí ji), nastaví event-listenery, uloží si data
* @param {Object.<string, ?>} korektura_data „Slovník“ obsahující data dané korektury
*/
constructor(korektura_data) {
this.htmlElement = prekorektura.cloneNode(true);
this.pointer = prepointer.cloneNode(true);
this.#komentare = this.htmlElement.getElementsByClassName('korektura-telo')[0];
this.#tagy = this.htmlElement.getElementsByClassName('korektura-tagy')[0];
this.id = korektura_data['id'];
this.htmlElement.id = 'kor' + this.id;
this.pointer.id = 'kor' + this.id + '-pointer';
this.x = korektura_data['x'];
this.y = korektura_data['y'];
this.aktualizuj(korektura_data);
this.htmlElement.getElementsByClassName('sbal-rozbal')[0].addEventListener('click', _ => this.#sbal_nebo_rozbal());
for (const button of this.htmlElement.getElementsByClassName('action'))
button.addEventListener('click', async event => this.#zmen_stav_korektury(event));
this.htmlElement.getElementsByClassName('komentovat')[0].addEventListener('click', _ => this.#komentuj())
this.htmlElement.getElementsByClassName('smaz-korekturu')[0].addEventListener('click', _ => this.#smaz_korekturu());
const odkaz_editace = this.htmlElement.getElementsByClassName('edit')[0];
odkaz_editace.href = odkaz_editace.href.replace("-1", this.id);
odkaz_editace.onclick = ev => { if (!confirm("Editace korektury je velmi pokročilá featura umožňující přesouvat korekturu nebo přidávat informované orgy, opravdu chceš pokračovat do adminu?")) ev.preventDefault(); };
this.htmlElement.addEventListener('mouseover', _ => this.pointer.dataset.hover = 'true');
this.htmlElement.addEventListener('mouseout', _ => this.pointer.dataset.hover = 'false');
const cislo_strany = korektura_data['strana'];
if (cislo_strany in strany) {
this.strana = strany[cislo_strany];
this.strana.korektury.push(this);
} else alert("Někdo korekturoval stranu, která neexistuje. Dejte vědět webařům :)");
korektury[this.id] = this;
}
/**
* Aktualizuje/nastaví JS data i HTML reprezentaci korektury
* @param {Object.<string, ?>} korektura_data „Slovník“ obsahující data dané korektury
* @returns {Korektura} pro jednodušší implementaci aktualizuj_nebo_vytvor vracíme this
*/
aktualizuj(korektura_data) {
this.set_stav(korektura_data['status']);
this.set_tagy(korektura_data["tagy"]);
return this;
};
/**
* Aktualizuje/nastaví JS data i HTML reprezentaci tagů korektury
* @param {Object.<string, ?>[]} tagy
*/
set_tagy(tagy) {
this.#tagy.innerHTML = "";
this.tagy = new Set();
for (const tag of tagy) {
this.tagy.add(tag["id"]);
const span = document.createElement("span");
span.innerHTML = tag["nazev"];
span.classList.add("korektury-tag");
span.style.backgroundColor = tag["barva"];
this.#tagy.appendChild(span);
}
}
/**
* Aktualizuje/nastaví JS data i HTML reprezentaci stavu korektury
* @param {String} stav
*/
set_stav(stav) {
this.stav = stav;
this.htmlElement.dataset.stav_korektury=stav;
this.pointer.dataset.stav_korektury=stav;
};
/**
* Přidá HTML reprezentaci komentáře pod tuto korekturu
* @param {HTMLElement} htmlElement přidávaný komentář (jako HTML prvek)
*/
pridej_htmlElement_komentare(htmlElement) { this.#komentare.appendChild(htmlElement); }
/** Sbalí/rozbalí (podle toho, zda byla rozbalená/sbalená) korekturu, ale nezmění pozice korektur (je třeba později zavolat umisti_korektury()) */
sbal_nebo_rozbal() {
this.sbalena = !this.sbalena;
this.htmlElement.dataset.korektura_sbalena = String(this.sbalena);
}
/** Doplněk sbal_nebo_rozbal, který i přeskládá korektury. */
#sbal_nebo_rozbal(){
this.sbal_nebo_rozbal();
umisti_korektury();
}
/** Ukaž komentovací formulář (když je zmáčknuto komentovat) */
#komentuj() { korekturovaci_formular.zobraz(this.strana, this.x, this.y, "", this.id); }
/**
* Změní stav (když je zmáčknuto tlačítko daného stavu)
* @param {MouseEvent} event který vyvolal danou změnu (event.target.value musí být chtěný stav)
*/
#zmen_stav_korektury(event) {
const data = new FormData(CSRF_FORM);
data.append('id', this.id);
data.append('action', event.target.value);
fetch('{% url "korektury_api_oprava_stav" %}', {method: 'POST', body: data})
.then(response => {
if (!response.ok) {alert('Něco se nepovedlo:' + response.statusText);}
else response.json().then(data => {
this.set_stav(data['status']);
aktualizuj_pocty_stavu();
});
})
.catch(error => {alert('Něco se nepovedlo:' + error);});
}
/** Smaže korekturu (když je zmáčknuto „smaz-korekturu“) */
#smaz_korekturu() {
if (confirm('Opravdu smazat korekturu?')) {
const data = new FormData(CSRF_FORM);
data.append('oprava_id', this.id);
fetch('{% url "korektury_api_oprava_smaz" %}', {method: 'POST', body: data})
.then(response => {
if (!response.ok) {alert('Něco se nepovedlo:' + response.statusText);}
this.#smaz_pouze_na_strance()
aktualizuj_pocty_stavu();
aktualizuj_pocty_zasluh();
umisti_korektury();
})
.catch(error => {alert('Něco se nepovedlo:' + error);});
}
}
/** Smaže div korektury (včetně všech komentářů; ne databázový záznam!) */
#smaz_pouze_na_strance() {
this.strana.korektury.splice(this.strana.korektury.indexOf(this), 1);
delete korektury[this.id];
for (const komentar of Object.values(komentare)) if (komentar.korektura === this) komentar.smaz_pouze_na_strance();
this.htmlElement.remove();
this.pointer.remove();
}
}
</script>

View file

@ -0,0 +1,86 @@
{# Template starající se o tlačítkovou lištu nahoře, tj. hlavně o hromadné schovávání korektur. #}
Zobrazit:
<input type="checkbox" id="k_oprave_checkbox" checked>
<label for="k_oprave_checkbox">K opravě (<span id="k_oprave_pocet"></span>)</label>
<input type="checkbox" id="opraveno_checkbox" checked>
<label for="opraveno_checkbox">Opraveno (<span id="opraveno_pocet"></span>)</label>
<input type="checkbox" id="neni_chyba_checkbox" checked>
<label for="neni_chyba_checkbox">Není chyba (<span id="neni_chyba_pocet"></span>)</label>
<input type="checkbox" id="k_zaneseni_checkbox" checked>
<label for="k_zaneseni_checkbox">K zanesení (<span id="k_zaneseni_pocet"></span>)</label>
<button type="button" id="sbal-korektury">Sbal korektury</button>
<button type="button" id="rozbal-korektury">Rozbal korektury</button>
<hr/>
<script>
document.getElementById('k_oprave_checkbox').addEventListener('change', () => skryj_nebo_zobraz_korektury('k_oprave'));
document.getElementById('opraveno_checkbox').addEventListener('change', () => skryj_nebo_zobraz_korektury('opraveno'));
document.getElementById('neni_chyba_checkbox').addEventListener('change', () => skryj_nebo_zobraz_korektury('neni_chyba'));
document.getElementById('k_zaneseni_checkbox').addEventListener('change', () => skryj_nebo_zobraz_korektury('k_zaneseni'));
document.getElementById("sbal-korektury").addEventListener("click", () => {
for (const korektura of Object.values(korektury))
if (!korektura.sbalena) korektura.sbal_nebo_rozbal();
umisti_korektury();
})
document.getElementById("rozbal-korektury").addEventListener("click", () => {
for (const korektura of Object.values(korektury))
if (korektura.sbalena) korektura.sbal_nebo_rozbal();
umisti_korektury();
})
/**
* Změní CSS tak, aby se korektury příslušného stavu nezobrazovali/zobrazovali (v závislosti na tom, jestli byly zobrazené/nezobrazené)
* @param {string} aclass stav korektur, které mají být skryty/zobrazeny
*/
function skryj_nebo_zobraz_korektury(aclass)
{
const stylesheets = document.styleSheets;
let ssheet = null;
for (let i=0; i<stylesheets.length; i++){
if (stylesheets[i].title === "opraf-css"){
ssheet = stylesheets[i];
break;
}
}
if (! ssheet){
return;
}
for (let i=0; i<ssheet.cssRules.length; i++){
const rule = ssheet.cssRules[i];
if (rule.selectorText === '[data-stav_korektury="'+aclass+'"]'){
if (rule.style.display === ""){
rule.style.display = "none";
} else {
rule.style.display = "";
}
}
}
umisti_korektury();
}
/**
* Mapování stav korektur |-> span, kde se píše, kolik je korektur toho stavu.
* Používané v následující funcki
* @type {Object.<string, HTMLElement>}
*/
const spany_s_pocty_stavu_korektur = {
'k_oprave': document.getElementById('k_oprave_pocet'),
'opraveno': document.getElementById('opraveno_pocet'),
'neni_chyba': document.getElementById('neni_chyba_pocet'),
'k_zaneseni': document.getElementById('k_zaneseni_pocet'),
}
/** Aktualizuje počty korektur jednotlivých stavů */
function aktualizuj_pocty_stavu() {
const pocty_stavu_korektur = {};
for (const stav_korektury of Object.keys(spany_s_pocty_stavu_korektur)) pocty_stavu_korektur[stav_korektury] = 0;
for (const korektura of Object.values(korektury)) {
if (!(korektura.stav in pocty_stavu_korektur)) pocty_stavu_korektur[korektura.stav] = 0;
pocty_stavu_korektur[korektura.stav] += 1;
}
for (let [stav, pocet] of Object.entries(pocty_stavu_korektur)) spany_s_pocty_stavu_korektur[stav].innerText = pocet;
}
</script>

View file

@ -0,0 +1,148 @@
{# Template starající se o zobrazení PDF stran a o umístění korektur na ně. (O samotné korektury se stará `./korektura.html`.) #}
{% for i in indexy_stran %}
<div class='imgdiv'>
<img
id='img-{{i}}'
width='1021' height='1448'
src='/media/korektury/img/{{korekturovanepdf.get_prefix}}-{{i}}.png'
alt='Strana {{ i|add:1 }}'
class="strana"
/>
</div>
<hr/>
{% endfor %}
<script>
// Pro umisťování korektur
const HORIZONTALNI_MEZERA = 10;
const VERTIKALNI_MEZERA = 5;
const MINIMALNI_VYSKA_POINTERU = 30;
/**
* Mapování index_strany |-> strana
* @type {Object.<int, Strana>}
*/
const strany = {};
/** Třída spravující jednu stranu PDF a umisťující na ni příslušné korektury. */
class Strana {
/**
* <img> příslušící straně
* @type {HTMLElement}
*/
htmlElement_img;
/**
* <div> obalující stranu, do něj se umisťují korektury
* @type {HTMLElement}
*/
htmlElement_div;
/**
* Index strany (používá se při ukládání korektury (a načítání <img>))
* @type {Number}
*/
id;
/**
* Korektury na příslušné straně (BÚNO setříděné podle vertikálního umístění)
* @type {Korektura[]}
*/
korektury;
/**
* Uloží si data (včetně pointrů na správné části HTML DOMu) a nastaví event-listener
* @param {HTMLElement} htmlElement_img
*/
constructor(htmlElement_img) {
this.htmlElement_img = htmlElement_img;
this.htmlElement_div = this.htmlElement_img.parentNode;
this.id = parseInt(this.htmlElement_img.id.substring(4));
this.korektury = []
this.htmlElement_img.addEventListener('click', event => this.#korekturuj(event));
strany[this.id] = this;
}
/**
* Otevře korekturovací formulář pro přidání korektury v daném místě
* @param {MouseEvent} event
*/
#korekturuj(event) {
switch (document.body.dataset.stav_pdf) {
case 'zanaseni':
if (!confirm('Právě jsou zanášeny korektury, opravdu chcete přidat novou?')) return;
break;
case 'zastarale':
if (!confirm('Toto PDF je již zastaralé, opravdu chcete vytvořit korekturu?')) return;
break;
}
let dx, dy;
if (event.pageX != null) {
dx = event.pageX - this.htmlElement_div.offsetLeft;
dy = event.pageY - this.htmlElement_div.offsetTop;
} else { //IE a další
dx = event.offsetX;
dy = event.offsetY;
}
korekturovaci_formular.zobraz(this, dx, dy, '');
console.log("Pro přesun korektur: strana = " + this.id + ", x = " + dx + ", y = " + dy);
}
/** Setřídí seznam korektur příslušný dané straně */
setrid_korektury() { this.korektury.sort((a, b) => a.y - b.y); }
/** Zobrazí korektury a jejich pointry (a umístí je správně pod sebe) na dané straně */
umisti_korektury() {
this.setrid_korektury()
const w = this.htmlElement_img.clientWidth;
let spodek_posledni_korektury = 0;
for (const korektura of this.korektury) {
const x = korektura.x;
const y = korektura.y;
const pointer = korektura.pointer;
this.htmlElement_div.appendChild(pointer);
this.htmlElement_div.appendChild(korektura.htmlElement);
const delta_y = (y > spodek_posledni_korektury) ? 0: spodek_posledni_korektury - y + VERTIKALNI_MEZERA;
pointer.style.left = x;
pointer.style.top = y;
pointer.style.width = w - x + HORIZONTALNI_MEZERA;
pointer.style.height = MINIMALNI_VYSKA_POINTERU + delta_y;
korektura.htmlElement.style.left = w + HORIZONTALNI_MEZERA;
korektura.htmlElement.style.top = y + delta_y;
spodek_posledni_korektury = Math.max(
spodek_posledni_korektury,
korektura.htmlElement.offsetTop + korektura.htmlElement.offsetHeight + VERTIKALNI_MEZERA
); // FIXME nemám páru, proč +VERTIKALNI_MEZERA funguje, ale opravuje to bug, že nově vytvořené korektury za sebou neměly mezeru
}
this.htmlElement_div.style.height = "unset";
if (this.htmlElement_div.offsetHeight < spodek_posledni_korektury)
this.htmlElement_div.style.height = spodek_posledni_korektury;
}
}
// Vytvoření objektu Strana pro každou stranu
for (const strana_img of document.getElementsByClassName('strana'))
new Strana(strana_img);
/**
* Seznam stran setřízený podle toho, jak jdou po sobě (aby se dali korektury prohledávat od první na HTML stránce po poslední)
* @type {Strana[]}
*/
const setrizene_strany = Object.values(strany);
setrizene_strany.sort((a, b) => a.htmlElement_img.offsetTop - b.htmlElement_img.offsetTop);
/** Zobrazí korektury a jejich pointry (a umístí je správně pod sebe) na všech stranách */
function umisti_korektury() { for (const strana of Object.values(strany)) strana.umisti_korektury(); }
</script>

View file

@ -0,0 +1,49 @@
{# Template starající se o formulář na změnu stavu PDF (včetně jeho odeslání) #}
<b>Změnit stav PDF:</b>
<br>
<form method="post" id="PDFSTAV_FORM">
{% csrf_token %}
<input type="radio" name="state" value="{{ korekturovanepdf.STATUS.PRIDAVANI }}" {% if korekturovanepdf.status == korekturovanepdf.STATUS.PRIDAVANI %} checked {% endif %}>Přidávání korektur
<br>
<input type="radio" name="state" value="{{ korekturovanepdf.STATUS.ZANASENI }}" {% if korekturovanepdf.status == korekturovanepdf.STATUS.ZANASENI %} checked {% endif %}>Zanášení korektur
<br>
<input type="radio" name="state" value="{{ korekturovanepdf.STATUS.ZASTARALE }}" {% if korekturovanepdf.status == korekturovanepdf.STATUS.ZASTARALE %} checked {% endif %}>Zastaralé, nekorigovat
<br>
<input type='submit' value='Změnit stav PDF'/>
</form>
<script>
/**
* Formulář měnící stav korekturovaného PDF
* @type {HTMLFormElement}
*/
const pdfstav_form = document.getElementById('PDFSTAV_FORM');
/**
* Fetchne stav korekturovaného PDF a změní ho na dané stránce.
* FIXME: nemění, který radio-button je vybrán.
* @param {RequestInit} data FormData a jiné náležitosti (method: POST) posílané při změně stavu korekturovaného PDF
* @param {Boolean} catchError jestli padat hlasitě (pokud se aktualizuje automaticky a spadne to např. na nepřítomnost sítě, pak není třeba informovat uživatele)
*/
function fetchStav(data, catchError=true) {
fetch("{% url 'korektury_api_pdf_stav' korekturovanepdf.id %}", data
)
.then(response => {
if (!response.ok) { if (catchError) alert("Něco se nepovedlo:" + response.statusText);}
else response.json().then(data => document.body.dataset.stav_pdf = data["status"]);
})
.catch(error => {if (catchError) alert("Něco se nepovedlo:" + error);});
}
pdfstav_form.addEventListener('submit', async event => {
event.preventDefault();
const data = new FormData(pdfstav_form);
fetchStav({method: "POST", body: data});
});
// FIXME není mi jasné, zda v {} nemá být `cache: "no-store"`, aby prohlížeč necachoval get.
setInterval(() => fetchStav({}, false), 120000); // Každý dvě minuty fetchni stav
</script>

View file

@ -1,244 +0,0 @@
{% load static %}
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" title="opraf-css" type="text/css" media="screen, projection" href="{% static "korektury/opraf.css"%}" />
<link href="{% static 'css/rozliseni.css' %}?version=1" rel="stylesheet">
<script src="{% static "korektury/opraf.js"%}"></script>
<title>Korektury {{pdf.nazev}}</title>
</head>
<body class="{{ LOCAL_TEST_PROD }}web{% if pdf.status == 'zanaseni'%} comitting{% elif pdf.status == 'zastarale' %} deprecated{% endif %}" onload='place_comments()'>
<h1>Korektury {{pdf.nazev}}</h1>
{% if pdf.status == 'zanaseni' %} <h2> Probíhá zanášení korektur, zvažte, zda chcete přidávat nové </h2> {% endif %}
{% if pdf.status == 'zastarale' %} <h2> Toto PDF je již zastaralé, nepřidávejte nové korektury </h2> {% endif %}
<i>{{pdf.komentar}}</i>
<br>
<i>Klikni na chybu, napiš komentář</i> |
<a href="{{pdf.pdf.url}}">stáhnout PDF (bez korektur)</a> |
<a href="../">seznam souborů</a> |
<a href="/admin/korektury/korekturovanepdf/">Spravovat PDF</a> |
<a href="../help">nápověda</a> |
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|
<a href="/">hlavní stránka</a> |
<a href="https://mam.mff.cuni.cz/wiki">wiki</a> |
<hr />
Zobrazit:
<input type="checkbox"
id="k_oprave_checkbox"
name="k_oprave_checkbox"
onchange="toggle_corrections('k_oprave')" checked>
<label for="k_oprave_checkbox">K opravě ({{k_oprave_cnt}})</label>
<input type="checkbox"
id="opraveno_checkbox"
name="opraveno_checkbox"
onchange="toggle_corrections('opraveno')" checked>
<label for="opraveno_checkbox">Opraveno ({{opraveno_cnt}})</label>
<input type="checkbox"
id="neni_chyba_checkbox"
name="neni_chyba_checkbox"
onchange="toggle_corrections('neni_chyba')" checked>
<label for="neni_chyba_checkbox">Není chyba ({{neni_chyba_cnt}})</label>
<input type="checkbox"
id="k_zaneseni_checkbox"
name="k_zaneseni_checkbox"
onchange="toggle_corrections('k_zaneseni')" checked>
<label for="k_zaneseni_checkbox">K zanesení ({{k_zaneseni_cnt}})</label>
<hr/>
<div id="commform-div">
<!-- Pridat korekturu / komentar !-->
<form action='' onsubmit='save_scroll(this)' id="commform" method="POST">
{% csrf_token %}
<input size="24" name="au" value="{{user.first_name}} {{user.last_name}}" readonly/>
<input type=submit value="Oprav!"/>
<button type="button" onclick="close_commform()">Zavřít</button>
<br/>
<textarea onkeypress="textarea_onkey(event);" id="commform-text" cols=40 rows=10 name="txt"></textarea>
<br/>
<input type="hidden" size="3" name="pdf" value='{{pdf.id}}'/>
<input type="hidden" size="3" id="commform-x" name="x"/>
<input type="hidden" size="3" id="commform-y" name="y"/>
<input type="hidden" size="3" id="commform-img-id" name="img-id"/>
<input type="hidden" size="3" id="commform-id" name="id"/>
<input type="hidden" size="3" id="commform-action" name="action"/>
<input type="hidden" size="3" id="commform-action" name="scroll"/>
</form>
<!-- /Pridat korekturu / komentar !-->
</div>
{% for i in img_indexes %}
<div class='imgdiv'>
<img width='1021' height='1448'
onclick='img_click(this,event)' id='img-{{i}}'
src='/media/korektury/img/{{img_prefix}}-{{i}}.png'/>
</div>
<hr/>
{% endfor %}
<h4>Změnit stav PDF:</h4>
<i>Aktuální: {{pdf.status}}</i>
<br>
<!-- Zmenit stav PDF !-->
<form method="post">
{% csrf_token %}
<input type='hidden' name='action' value='set-state'/>
<input type='hidden' name='pdf' value='{{pdf.id}}'/>
<input type="radio" name="state" value="adding" {% if pdf.status == 'pridavani' %} checked {% endif %}>Přidávání korektur
<br>
<input type="radio" name="state" value="comitting" {% if pdf.status == 'zanaseni' %} checked {% endif %}>Zanášení korektur
<br>
<input type="radio" name="state" value="deprecated" {% if pdf.status == 'zastarale' %} checked {% endif %}>Zastaralé, nekorigovat
<br>
<input type='submit' value='Změnit stav PDF'/>
</form>
<!-- /Zmenit stav PDF !-->
<hr/>
<p>
Děkujeme opravovatelům:
{% for z in zasluhy %}
{{z.autor}} ({{z.pocet}}){% if not forloop.last %},{% endif %}
{% endfor %}</p>
<hr>
{% for o in opravy %}
<div onclick='img_click(this,event)'
id='op{{o.id}}-pointer'
class='pointer {{o.status}}'>
</div>
<div name='op{{o.id}}' id='op{{o.id}}'
class='box {{o.status}}'
onmouseover='box_onmouseover(this)'
onmouseout='box_onmouseout(this)'>
<div class='corr-header'>
<span class='author' id='op{{o.id}}-autor'>{{o.autor}}</span>
<span class='float-right'>
<span id='op{{o.id}}-buttons'>
<!-- Existujici korektura !-->
<form action='' onsubmit='save_scroll(this)' method='POST'>
{% csrf_token %}
<input type='hidden' name="au" value="{{o.autor}}"/>
<input type='hidden' name='pdf' value='{{pdf.id}}'>
<input type='hidden' name='id' value='{{o.id}}'>
<input type='hidden' name='scroll'>
{% if o.komentare %}
<button name='action' value='del' type='button'
title="Opravu nelze smazat &ndash; už ji někdo okomentoval">
<img src="{% static "korektury/imgs/delete-gr.png"%}"/>
</button>
{% else %}
<button type='submit' name='action' value='del' title='Smaž opravu'>
<img src="{% static "korektury/imgs/delete.png"%}"/>
</button>
{% endif %}
{% if o.status != 'k_oprave' %}
<button type='submit' name='action' value='undone' title='Označ jako neopravené'>
<img src="{% static "korektury/imgs/undo.png"%}"/>
</button>
{% endif %}
{% if o.status != 'opraveno' %}
<button type='submit' name='action' value='done' title='Označ jako opravené'>
<img src="{% static "korektury/imgs/check.png"%}"/>
</button>
{% endif %}
{% if o.status != 'neni_chyba' %}
<button type='submit' name='action' value='wontfix' title='Označ, že se nebude měnit'>
<img src="{% static "korektury/imgs/cross.png" %}"/>
</button>
{% endif %}
{% if o.status != 'k_zaneseni' %}
<button type='submit' name='action' value='ready' title='Označ jako připraveno k zanesení'>
<img src="{% static "korektury/imgs/tex.png" %}"/>
</button>
{% endif %}
</form>
<!-- /Existujici korektura !-->
{% if o.komentare %}
<button type='button' title="Korekturu nelze upravit &ndash; už ji někdo okomentoval">
<img src="{% static "korektury/imgs/edit-gr.png" %}"/>
</button>
{% else %}
<button type='button' onclick='box_edit("op{{o.id}}","update");' title='Oprav opravu'>
<img src="{% static "korektury/imgs/edit.png" %}"/>
</button>
{% endif %}
{% if o.status == 'opraveno' or o.status == 'neni_chyba' %}
<button type='button' title='Korekturu nelze komentovat, protože už je uzavřená'>
<img src="{% static "korektury/imgs/comment-gr.png" %}"/>
</button>
{% else %}
<button type='button' onclick='box_edit("op{{o.id}}", "comment");' title='Komentovat'>
<img src="{% static "korektury/imgs/comment.png" %}"/>
</button>
{% endif %}
</span>
<button type='button' onclick='toggle_visibility("op{{o.id}}");' title='Skrýt/Zobrazit'>
<img src="{% static "korektury/imgs/hide.png" %}"/>
</button>
</span>
</div>
<div class='corr-body' id='op{{o.id}}-body'>
<div id='op{{o.id}}-text'>{{o.text|linebreaks}}</div>
{% for k in o.komentare %}
<hr>
<div class='comment' id='k{{k.id}}'>
<div class='corr-header'>
<div class='author'>{{k.autor}}</div>
<div class="float-right">
<!-- Komentar !-->
<form action='' onsubmit='save_scroll(this)' method='POST'>
{% csrf_token %}
<input type='hidden' name='pdf' value='{{pdf.id}}'>
<input type='hidden' name='id' value='{{k.id}}'>
<input type='hidden' name='scroll'>
{% if forloop.last %}
<button type='submit' name='action' value='del-comment' title='Smaž komentář'
onclick='return confirm("Opravdu smazat komentář?")'>
<img src="{% static "korektury/imgs/delete.png" %}"/>
</button>
{% else %}
<button name='action' value='del-comment' type='button'
title="Komentář nelze smazat &ndash; existuje novější">
<img src="{% static "korektury/imgs/delete-gr.png"%}"/>
</button>
{% endif %}
</form>
<!-- /Komentar !-->
{% if forloop.last %}
<button type='button' onclick="update_comment('op{{o.id}}','kt{{k.id}}');" title='Uprav komentář'>
<img src="{% static "korektury/imgs/edit.png"%}"/>
</button>
{% else %}
<button type='button' title="Komentář nelze upravit &ndash; existuje novější">
<img src="{% static "korektury/imgs/edit-gr.png" %}"/>
</button>
{% endif %}
</div>
</div>
<div id='kt{{k.id}}'>{{k.text|linebreaks}}</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
<script>
var comments = [
{% for s in opravy_strany %}
["img-{{s.strana}}", [{% for o in s.op_id %}["op{{o.id}}",{{o.x}},{{o.y}}],{% endfor %}[]]],
{% endfor %}
[]]
{% if scroll %}
window.scrollTo(0,{{scroll}});
{% endif %}
</script>
</body>
</html>

View file

@ -1,3 +1,6 @@
"""
Soubor sloužící ke generování testdat.
"""
import logging import logging
import os import os
from shutil import copyfile, rmtree from shutil import copyfile, rmtree
@ -32,24 +35,27 @@ def create_test_pdf(rnd, organizatori):
# TODO silent ghostscript (vypisuje odstavec za každou stránku…) # TODO silent ghostscript (vypisuje odstavec za každou stránku…)
KorekturovanePDF.objects.create( korekturovane_pdf = KorekturovanePDF.objects.create(
nazev='B', komentar='Neuronové sítě', org=rnd.choice(organizatori), pdf=gen_filename(filename='B.pdf') nazev='B', komentar='Neuronové sítě', 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.orgove.set((rnd.choice(organizatori),))
korekturovane_pdf = KorekturovanePDF.objects.create( 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' status='zanaseni'
) )
KorekturovanePDF.objects.create( 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'),
status='zastarale' 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 # TODO najít správné chyby, které vyhazují různé systémy při neexistenci ImageMagick, nebo knihoven
logger.error(str(e)) logger.error(str(e))
logger.error( logger.error(

View file

@ -1,10 +1,14 @@
from django.urls import path from django.urls import path
from django.urls import include
from personalni.utils import org_required from personalni.utils import org_required
from . import views from . import views
urlpatterns = [ urlpatterns = [
path('', org_required(views.KorekturySeskupeneListView.as_view()), name='korektury_list'), path('korektury/', org_required(views.KorekturySeskupeneListView.as_view()), name='korektury_list'),
path('neseskupene/', org_required(views.KorekturyAktualniListView.as_view()), name='korektury_neseskupene_list'), path('korektury/neseskupene/', org_required(views.KorekturyAktualniListView.as_view()), name='korektury_neseskupene_list'),
path('zastarale/', org_required(views.KorekturyZastaraleListView.as_view()), name='korektury_stare_list'), path('korektury/zastarale/', org_required(views.KorekturyZastaraleListView.as_view()), name='korektury_stare_list'),
path('<int:pdf>/', org_required(views.KorekturyView.as_view()), name='korektury'), path('korektury/<int:pdf>/', org_required(views.KorekturyView.as_view()), name='korektury'),
path('korektury/api/', include('korektury.api.urls')),
] ]

53
korektury/utils.py Normal file
View file

@ -0,0 +1,53 @@
from django.core.mail import EmailMessage
from django.http import HttpRequest
from django.urls import reverse
from korektury.models import Komentar, Oprava
from personalni.models import Organizator
def send_email_notification_komentar(oprava: Oprava, autor: Organizator, request: HttpRequest):
''' Rozesle e-mail pri pridani komentare / opravy,
ktery obsahuje text vlakna opravy.
'''
# parametry e-mailu
#odkaz = "https://mam.mff.cuni.cz/korektury/{}/".format(oprava.pdf.pk)
odkaz = request.build_absolute_uri(reverse('korektury', kwargs={'pdf': oprava.pdf.pk}))
odkaz = f"{odkaz}#kor{oprava.id}-pointer"
from_email = 'korekturovatko@mam.mff.cuni.cz'
subject = 'Nová korektura od {} v {}'.format(autor, oprava.pdf.nazev)
texty = []
for kom in Komentar.objects.filter(oprava=oprava):
texty.append((kom.autor.osoba.plne_jmeno(),kom.text))
optext = "\n\n\n".join([": ".join(t) for t in texty])
text = u"Text komentáře:\n\n{}\n\n=== Konec textu komentáře ===\n\
\nodkaz do korekturovátka: {}\n\
\nVaše korekturovátko\n".format(optext, odkaz)
# Prijemci e-mailu
emails = set()
# nalezeni e-mailu na autory komentaru
for org in oprava.informovani_orgove.all():
email_komentujiciho = org.osoba.email
if email_komentujiciho:
emails.add(email_komentujiciho)
# zodpovedni orgove
for org in oprava.pdf.orgove.all():
email_zobpovedny = org.osoba.email
if email_zobpovedny:
emails.add(email_zobpovedny)
# odstran e-mail autora opravy
email = autor.osoba.email
if email:
emails.discard(email)
EmailMessage(
subject=subject,
body=text,
from_email=from_email,
to=list(emails),
).send()

View file

@ -1,26 +1,15 @@
from django.shortcuts import get_object_or_404, render
from django.views import generic from django.views import generic
from django.utils.translation import ugettext as _
from django.conf import settings
from django.http import HttpResponseForbidden
from django.core.mail import EmailMessage
from django.db.models import Count,Q from django.db.models import Count,Q
from .models import Oprava,Komentar,KorekturovanePDF, Organizator from .models import Oprava, KorekturovanePDF, KorekturaTag
from .forms import OpravaForm
import subprocess
import shutil
import os
class KorekturyListView(generic.ListView): class KorekturyListView(generic.ListView):
model = KorekturovanePDF model = KorekturovanePDF
# Nefunguje, filtry se vubec nepouziji
queryset = KorekturovanePDF.objects.annotate( queryset = KorekturovanePDF.objects.annotate(
k_oprave_cnt=Count('oprava',distinct=True,filter=Q(oprava__status='k_oprave')), k_oprave_cnt=Count('oprava',distinct=True,filter=Q(oprava__status=Oprava.STATUS.K_OPRAVE)),
opraveno_cnt=Count('oprava',distinct=True,filter=Q(oprava__status='opraveno')), opraveno_cnt=Count('oprava',distinct=True,filter=Q(oprava__status=Oprava.STATUS.OPRAVENO)),
neni_chyba_cnt=Count('oprava',distinct=True,filter=Q(oprava__status='neni_chyba')), neni_chyba_cnt=Count('oprava',distinct=True,filter=Q(oprava__status=Oprava.STATUS.NENI_CHYBA)),
k_zaneseni_cnt=Count('oprava',distinct=True,filter=Q(oprava__status='k_zaneseni')), k_zaneseni_cnt=Count('oprava',distinct=True,filter=Q(oprava__status=Oprava.STATUS.K_ZANESENI)),
) )
template_name = 'korektury/seznam.html' template_name = 'korektury/seznam.html'
@ -57,191 +46,14 @@ class KorekturySeskupeneListView(KorekturyAktualniListView):
return reversed(sorted(qs, key=lambda it: it.cislo_a_tema)) return reversed(sorted(qs, key=lambda it: it.cislo_a_tema))
### Korektury ### Korektury
class KorekturyView(generic.TemplateView): class KorekturyView(generic.DetailView):
model = Oprava model = KorekturovanePDF
template_name = 'korektury/opraf.html' pk_url_kwarg = "pdf"
form_class = OpravaForm template_name = 'korektury/korekturovatko/html_obal.html'
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
q = request.POST
scroll = q.get('scroll')
# prirazeni autora podle prihlaseni
autor_user = request.user
# pokud existuje ucet (user), ale neni to organizator = 403
autor = Organizator.objects.filter(osoba__user=autor_user).first()
if not autor:
return HttpResponseForbidden()
if not scroll:
scroll = 0
action = q.get('action')
if (action == ''): # Přidej
x = int(q.get('x'))
y = int(q.get('y'))
text = q.get('txt')
strana = int(q.get('img-id')[4:])
pdf = KorekturovanePDF.objects.get(id=q.get('pdf'))
op = Oprava(x=x,y=y, autor=autor, text=text, strana=strana,pdf = pdf)
op.save()
self.send_email_notification_komentar(op,autor)
elif (action == 'del'):
id = int(q.get('id'))
op = Oprava.objects.get(id=id)
op.delete()
elif (action == 'update'):
id = int(q.get('id'))
op = Oprava.objects.get(id=id)
text = q.get('txt')
op.autor = autor
op.text = text
op.save()
elif (action == 'undone'):
id = int(q.get('id'))
op = Oprava.objects.get(id=id)
op.status = op.STATUS_K_OPRAVE
op.save()
elif (action == 'done'):
id = int(q.get('id'))
op = Oprava.objects.get(id=id)
op.status = op.STATUS_OPRAVENO
op.save()
elif (action == 'ready'):
id = int(q.get('id'))
op = Oprava.objects.get(id=id)
op.status = op.STATUS_K_ZANESENI
op.save()
elif (action == 'wontfix'):
id = int(q.get('id'))
op = Oprava.objects.get(id=id)
op.status = op.STATUS_NENI_CHYBA
op.save()
elif (action == 'comment'):
id = int(q.get('id'))
op = Oprava.objects.get(id=id)
text = q.get('txt')
kom = Komentar(oprava=op,autor=autor,text=text)
kom.save()
self.send_email_notification_komentar(op,autor)
elif (action == 'update-comment'):
id = int(q.get('id'))
kom = Komentar.objects.get(id=id)
text = q.get('txt')
kom.text = text
kom.autor = autor
kom.save()
elif (action == 'del-comment'):
id = int(q.get('id'))
kom = Komentar.objects.get(id=id)
kom.delete()
elif (action == 'set-state'):
pdf = KorekturovanePDF.objects.get(id=q.get('pdf'))
if (q.get('state') == 'adding'):
pdf.status = pdf.STATUS_PRIDAVANI
elif (q.get('state') == 'comitting'):
pdf.status = pdf.STATUS_ZANASENI
elif (q.get('state') == 'deprecated'):
pdf.status = pdf.STATUS_ZASTARALE
pdf.save()
context = self.get_context_data()
context['scroll'] = scroll
context['autor'] = autor
return render(request, 'korektury/opraf.html',context)
def send_email_notification_komentar(self, oprava, autor):
''' Rozesle e-mail pri pridani komentare / opravy,
ktery obsahuje text vlakna opravy.
'''
# parametry e-mailu
#odkaz = "https://mam.mff.cuni.cz/korektury/{}/".format(oprava.pdf.pk)
from django.urls import reverse
odkaz = self.request.build_absolute_uri(reverse('korektury', kwargs={'pdf': oprava.pdf.pk}))
odkaz = f"{odkaz}#op{oprava.id}-pointer"
from_email = settings.KOREKTURY_EMAIL
subject = 'Nová korektura od {} v {}'.format(autor, oprava.pdf.nazev)
texty = [(oprava.autor.osoba.plne_jmeno(),oprava.text)]
for kom in Komentar.objects.filter(oprava=oprava):
texty.append((kom.autor.osoba.plne_jmeno(),kom.text))
optext = "\n\n\n".join([": ".join(t) for t in texty])
text = u"Text komentáře:\n\n{}\n\n=== Konec textu komentáře ===\n\
\nodkaz do korekturovátka: {}\n\
\nVaše korekturovátko\n".format(optext, odkaz)
# Prijemci e-mailu
emails = set()
# e-mail autora korektury
email = oprava.autor.osoba.email
if email:
emails.add(email)
# nalezeni e-mailu na autory komentaru
for komentar in oprava.komentar_set.all():
email_komentujiciho = komentar.autor.osoba.email
if email_komentujiciho:
emails.add(email_komentujiciho)
# zodpovedny org
if oprava.pdf.org:
email_zobpovedny = oprava.pdf.org.osoba.email
if email_zobpovedny:
emails.add(email_zobpovedny)
# odstran e-mail autora opravy
email = autor.osoba.email
if email:
emails.discard(email)
EmailMessage(
subject=subject,
body=text,
from_email=from_email,
to=list(emails),
).send()
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
pdf = get_object_or_404(KorekturovanePDF, id=self.kwargs['pdf']) context['indexy_stran'] = range(self.object.stran)
context['pdf'] = pdf context['tagy'] = KorekturaTag.objects.all()
context['img_prefix'] = pdf.get_prefix()
context['img_path'] = settings.KOREKTURY_IMG_DIR
context['img_indexes'] = range(pdf.stran)
context['form_oprava'] = OpravaForm()
opravy = Oprava.objects.filter(pdf=self.kwargs['pdf'])
zasluhy = {}
for o in opravy:
if o.autor in zasluhy:
zasluhy[o.autor]+=1
else:
zasluhy[o.autor]=1
o.komentare = o.komentar_set.all()
for k in o.komentare:
if k.autor in zasluhy:
zasluhy[k.autor] += 1
else:
zasluhy[k.autor] = 1
zasluhy = [
{'autor': jmeno, 'pocet': pocet}
for (jmeno, pocet) in zasluhy.items()
]
zasluhy.sort(key=lambda z: z['pocet'], reverse=True)
strany = set(o.strana for o in opravy)
opravy_na_stranu = [{'strana': s, 'op_id': opravy.filter(strana=s)} for s in strany]
context['opravy_strany'] = opravy_na_stranu
context['k_oprave_cnt'] = opravy.filter(status='k_oprave').count()
context['opraveno_cnt'] = opravy.filter(status='opraveno').count()
context['neni_chyba_cnt'] = opravy.filter(status='neni_chyba').count()
context['k_zaneseni_cnt'] = opravy.filter(status='k_zaneseni').count()
context['opravy'] = opravy
context['zasluhy'] = zasluhy
return context return context
def form_valid(self,form):
return super().form_valid(form)

View file

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

View file

@ -95,7 +95,7 @@ function safe_checkout_branch {
echo >&2 "Změna v $SCRIPT, prosím pullni manuálně" echo >&2 "Změna v $SCRIPT, prosím pullni manuálně"
exit 1 exit 1
fi fi
git checkout "$BRANCH" git checkout "$BRANCH" --
git pull git pull
git clean -f git clean -f
} }

View file

@ -4,7 +4,7 @@ set -exuo pipefail
. make/lib.sh . make/lib.sh
scp vue_frontend/webpack-stats.json "$GIMLI_LOGIN:$TESTWEB/vue_frontend/" 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" " ssh "$GIMLI_LOGIN" "
set -euxo pipefail set -euxo pipefail
cd $TESTWEB cd $TESTWEB

View file

@ -5,5 +5,4 @@ set -exuo pipefail
ensure_web_installed ensure_web_installed
./manage.py graph_models seminar | dot -Tpdf > schema_seminar.pdf
./manage.py graph_models -a -g | dot -Tpdf > schema_all.pdf ./manage.py graph_models -a -g | dot -Tpdf > schema_all.pdf

View file

@ -7,17 +7,18 @@ import locale
from django.contrib import admin from django.contrib import admin
from django.contrib.admin import AdminSite from django.contrib.admin import AdminSite
from django.contrib.flatpages.models import FlatPage from django.contrib.flatpages.models import FlatPage
import logging
# Note: we are renaming the original Admin and Form as we import them! # Note: we are renaming the original Admin and Form as we import them!
from django.contrib.flatpages.admin import FlatPageAdmin as FlatPageAdminOld from django.contrib.flatpages.admin import FlatPageAdmin as FlatPageAdminOld
from django.contrib.flatpages.admin import FlatpageForm as FlatpageFormOld from django.contrib.flatpages.admin import FlatpageForm as FlatpageFormOld
from django import forms from django import forms
from ckeditor_uploader.widgets import CKEditorUploadingWidget from django_ckeditor_5.widgets import CKEditor5Widget
class FlatpageForm(FlatpageFormOld): class FlatpageForm(FlatpageFormOld):
content = forms.CharField(widget=CKEditorUploadingWidget()) content = forms.CharField(widget=CKEditor5Widget())
class Meta: class Meta:
model = FlatPage # this is not automatically inherited from FlatpageFormOld model = FlatPage # this is not automatically inherited from FlatpageFormOld
exclude = [] exclude = []
@ -35,19 +36,34 @@ locale.setlocale(locale.LC_COLLATE, 'cs_CZ.UTF-8')
# https://books.agiliq.com/projects/django-admin-cookbook/en/latest/set_ordering.html # https://books.agiliq.com/projects/django-admin-cookbook/en/latest/set_ordering.html
# FIXME zpraseno pomocí toho, že Python umí bez problému přepisovat funkce # FIXME zpraseno pomocí toho, že Python umí bez problému přepisovat funkce
def get_app_list(self, request): def get_app_list(self, request, app_label=None):
""" """
Return a sorted list of all the installed apps that have been Return a sorted list of all the installed apps that have been
registered in this site. registered in this site.
""" """
app_dict = self._build_app_dict(request) app_dict = self._build_app_dict(request, label=app_label)
# Sort the apps alphabetically. aplikace_nahore = [
app_list = sorted(app_dict.values(), key=lambda x: locale.strxfrm('!') if (x['name'] == "Seminar") else locale.strxfrm(x['name'].lower())) 'tvorba',
'personalni',
'novinky',
'korektury',
'various',
'prednasky',
'soustredeni',
]
# Odhlášený admin má prázdný app_dict :-/
app_list = [app_dict[label] for label in aplikace_nahore if label in app_dict] + [app_dict[label] for label in app_dict if label not in aplikace_nahore]
# Sort the models alphabetically within each app. # Sort the models alphabetically within each app.
for app in app_list: try: # na macu nefunguje locale.strxfrm :-/ proto je tu try except block
app['models'].sort(key=lambda x: locale.strxfrm('žž' + x['name'].lower()) if (x['name'].endswith("(Node)")) else locale.strxfrm(x['name'].lower())) for app in app_list:
app['models'].sort(key=lambda x: locale.strxfrm(x['name'].lower()))
except OSError as e:
# locale.strxfrm nefunguje na macu... :-/ -> neprovede se řazení
logger = logging.getLogger(__name__)
logger.error(e)
return app_list return app_list

View file

@ -1,56 +1,71 @@
"""
Django settings for mamweb project.
For more information on this file, see
https://docs.djangoproject.com/en/1.7/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.7/ref/settings/
"""
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os import os
import traceback import traceback
BASE_DIR = os.path.dirname(os.path.dirname(__file__)) BASE_DIR = os.path.dirname(os.path.dirname(__file__))
# Application definition # Application definition
SITE_ID = 1 SITE_ID = 1
ROOT_URLCONF = 'mamweb.urls' ROOT_URLCONF = 'mamweb.urls'
WSGI_APPLICATION = 'mamweb.wsgi.application' WSGI_APPLICATION = 'mamweb.wsgi.application'
# Lokalizace APPEND_SLASH = True
# Internationalization
# https://docs.djangoproject.com/en/1.7/topics/i18n/
LANGUAGE_CODE = 'cs' LANGUAGE_CODE = 'cs'
TIME_ZONE = 'Europe/Prague' TIME_ZONE = 'Europe/Prague'
USE_L10N = True # S přechodem k djangu>=4 lze smazat (localized formatting) USE_I18N = True
USE_TZ = True # S přechodem k djangu>=5 lze smazat (timezone aware datetimes) USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.7/howto/static-files/
# Statické soubory (CSS, JavaScript, obrázky) a další média
STATIC_URL = '/static/' STATIC_URL = '/static/'
MEDIA_URL = '/media/' MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
STATIC_ROOT = os.path.join(BASE_DIR, 'static') STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATICFILES_FINDERS = ( STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.AppDirectoriesFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.FileSystemFinder',
) )
# URL pro přihlášení (default je account/login) # Where redirect for login required services
LOGIN_URL = 'login' LOGIN_URL = 'login'
LOGIN_REDIRECT_URL = 'profil' LOGIN_REDIRECT_URL = 'profil'
# Odhlášení po zavření prohlížeče # Odhlášení po zavření prohlížeče
# (pozor nefunguje na firefox se znovuotevíráním oken po startu firefoxu) # (pozor nefunguje na firefox se znovuotevíráním oken po startu firefoxu)
# default je False a SESSION_COOKIE_AGE = 3600*24*14 = 2 týdny # default je False a SESSION_COOKIE_AGE = 3600*24*14 = 2 týdny
SESSION_EXPIRE_AT_BROWSER_CLOSE = True SESSION_EXPIRE_AT_BROWSER_CLOSE = True
DOBA_ODHLASENI_PRI_ZASKRTNUTI_NEODHLASOVAT = 365 * 24 * 3600 # rok DOBA_ODHLASENI_PRI_ZASKRTNUTI_NEODHLASOVAT = 365 * 24 * 3600 # rok
# View pro chybu s CSRF tokenem (např. se sušenkami)
CSRF_FAILURE_VIEW = 'various.views.csrf.csrf_error'
# SECURITY WARNING: keep the secret key used in production secret! # Modules configuration
# Create file 'django.secret' in every install (it is not kept in git) FORM_RENDERER = "django.forms.renderers.DjangoDivFormRenderer"
try:
with open(os.path.join(os.path.dirname(__file__), '..', 'django.secret')) as f:
SECRET_KEY = f.readline().strip()
except:
SECRET_KEY = '12345zmr_k53a*@f4q_+ji^o@!pgpef*5&8c7zzdqwkdlkj'
# Přidávání dalších součástí (do) djangovské mašinérie
AUTHENTICATION_BACKENDS = ( AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', 'django.contrib.auth.backends.ModelBackend',
) )
MIDDLEWARE = ( MIDDLEWARE = (
# 'reversion.middleware.RevisionMiddleware', # 'reversion.middleware.RevisionMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
@ -59,6 +74,7 @@ MIDDLEWARE = (
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware', 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
) )
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'BACKEND': 'django.template.backends.django.DjangoTemplates',
@ -67,17 +83,22 @@ TEMPLATES = [
'OPTIONS': { 'OPTIONS': {
'context_processors': ( 'context_processors': (
'django.contrib.auth.context_processors.auth', 'django.contrib.auth.context_processors.auth',
'django.template.context_processors.request', 'django.template.context_processors.request',
'django.contrib.messages.context_processors.messages', 'django.contrib.messages.context_processors.messages',
'sekizai.context_processors.sekizai',
'header_fotky.context_processors.vzhled', 'header_fotky.context_processors.vzhled',
'various.context_processors.rozliseni', 'various.context_processors.rozliseni',
'various.context_processors.april', 'various.context_processors.april',
'various.context_processors.halloween',
) )
}, },
}, },
] ]
INSTALLED_APPS = ( INSTALLED_APPS = (
# Basic # Basic
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'django.contrib.sessions', 'django.contrib.sessions',
@ -87,19 +108,14 @@ INSTALLED_APPS = (
'django.contrib.auth', 'django.contrib.auth',
# Utilities # Utilities
'sekizai',
'reversion', 'reversion',
'django_countries', 'django_countries',
'solo', 'solo',
'ckeditor', 'django_ckeditor_5',
'ckeditor_uploader',
'taggit', 'taggit',
'dal', 'dal',
'dal_select2', 'dal_select2',
'crispy_forms',
'django_comments',
'django.contrib.flatpages', 'django.contrib.flatpages',
'django.contrib.humanize', 'django.contrib.humanize',
@ -113,82 +129,158 @@ INSTALLED_APPS = (
'rest_framework', 'rest_framework',
'rest_framework.authtoken', 'rest_framework.authtoken',
'colorfield',
# MaMweb # MaMweb
'mamweb', 'mamweb',
'seminar', 'seminar',
'tvorba',
'galerie', 'galerie',
'korektury', 'korektury',
'korektury.api',
'prednasky', 'prednasky',
'header_fotky', 'header_fotky',
'various', 'various',
'various.autentizace', 'various.autentizace',
'api', 'api',
'aesop',
'odevzdavatko', 'odevzdavatko',
'vysledkovky', 'vysledkovky',
'personalni', 'personalni',
'soustredeni', 'soustredeni',
'tvorba',
'treenode', 'treenode',
'vyroci', 'vyroci',
'sifrovacka',
'novinky',
# Admin upravy: # Admin upravy:
# 'material', # 'material',
# 'material.admin', # 'material.admin',
# 'admin_tools', # 'admin_tools',
# 'admin_tools.theming', # 'admin_tools.theming',
# 'admin_tools.menu', # 'admin_tools.menu',
# 'admin_tools.dashboard', # 'admin_tools.dashboard',
'django.contrib.admin', 'django.contrib.admin',
# Nechat na konci (INSTALLED_APPS je uspořádané): # Nechat na konci (INSTALLED_APPS je uspořádané):
'django_cleanup.apps.CleanupConfig', # Uklízí media/ 'django_cleanup.apps.CleanupConfig', # Uklízí media/
) )
DEBUG_TOOLBAR_CONFIG = {
'SHOW_COLLAPSED': True,
}
# MaM-specifické složky SUMMERNOTE_CONFIG = {
SEMINAR_RESENI_DIR = os.path.join('reseni') 'iframe': False,
SEMINAR_KONFERY_DIR = os.path.join('konfery') 'airMode': False,
KOREKTURY_PDF_DIR = os.path.join('korektury', 'pdf') 'attachment_require_authentication': True,
KOREKTURY_IMG_DIR = os.path.join('korektury', 'img') 'width': '80%',
CISLO_IMG_DIR = os.path.join('cislo', 'img') # 'height': '30em',
'toolbar': [
['style', ['style']],
['font', ['bold', 'italic', 'superscript', 'subscript', 'clear']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
['insert', ['link', 'picture', 'hr']],
['view', ['fullscreen', 'codeview']],
['help', ['help']],
]
}
CKEDITOR_5_CUSTOM_CSS = "css/ckeditor5_fix.css"
# MaM-specifické konstanty # customColorPalette = [
ROCNIK_ZRUSENI_TEMAT = 25 # {
ROCNIK_INFLACE_BODU = 25 # 'color': 'hsl(4, 90%, 58%)',
ROCNIK_INFLACE_TITULU = 26 # 'label': 'Red',
# # },
NOVE_CISLO_EMAIL = 'zadani@mam.mff.cuni.cz' # {
NOVE_RESENI_EMAIL = 'submitovatko@mam.mff.cuni.cz' # 'color': 'hsl(340, 82%, 52%)',
KOREKTURY_NOVE_PDF_EMAIL = 'korekturovatko-nove-pdf@mam.mff.cuni.cz' # 'label': 'Pink',
KOREKTURY_EMAIL = 'korekturovatko@mam.mff.cuni.cz' # },
REGISTRACE_EMAIL = 'registrace@mam.mff.cuni.cz' # {
PASSWD_RESET_EMAIL = 'login@mam.mff.cuni.cz' # 'color': 'hsl(291, 64%, 42%)',
# # 'label': 'Purple',
KONFERA_ORGOVE_EMAIL = 'org@mam.mff.cuni.cz' # },
# {
# 'color': 'hsl(262, 52%, 47%)',
# CKEditor = WYSIWYG html editor # 'label': 'Deep Purple',
CKEDITOR_UPLOAD_PATH = "uploads/" # },
CKEDITOR_IMAGE_BACKEND = 'pillow' # {
# CKEDITOR_JQUERY_URL = '//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js' # 'color': 'hsl(231, 48%, 48%)',
CKEDITOR_CONFIGS = { # 'label': 'Indigo',
# },
# {
# 'color': 'hsl(207, 90%, 54%)',
# 'label': 'Blue',
# },
# ]
CKEDITOR_5_FILE_STORAGE = "various.storage.UploadStorage"
CKEDITOR_5_CONFIGS = {
'default': { 'default': {
'entities': False, 'language': 'cs',
'toolbar': [ 'blockToolbar': [
['Source', 'ShowBlocks', '-', 'Maximize'], 'paragraph', 'heading1', 'heading2', 'heading3',
['Bold', 'Italic', 'Subscript', 'Superscript', '-', 'RemoveFormat'], '|',
['NumberedList', 'BulletedList', '-', 'Blockquote', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock'], 'bulletedList', 'numberedList',
['Link', 'Unlink', 'Anchor', '-', 'Image', 'Table', 'HorizontalRule'], '|',
['Format'], 'blockQuote',
], ],
# 'toolbar': 'full', 'toolbar': ['sourceEditing', '|', 'heading', '|',
'height': '40em', # 'outdent', 'indent', '|',
'width': '100%', 'bold', 'italic', 'link', 'underline', 'strikethrough',
'toolbarStartupExpanded': False, 'code',
'allowedContent': True, # '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 # Webpack loader
@ -207,21 +299,31 @@ WEBPACK_LOADER = {
# Dajngo REST Framework # Dajngo REST Framework
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 100 'PAGE_SIZE': 100
} }
# Logování # SECURITY WARNING: keep the secret key used in production secret!
# Create file 'django.secret' in every install (it is not kept in git)
try:
with open(os.path.join(os.path.dirname(__file__), '..', 'django.secret')) as f:
SECRET_KEY = f.readline().strip()
except:
SECRET_KEY = '12345zmr_k53a*@f4q_+ji^o@!pgpef*5&8c7zzdqwkdlkj'
# Logging
LOGGING = { LOGGING = {
'version': 1, 'version': 1,
'disable_existing_loggers': False, 'disable_existing_loggers': False,
'formatters': { 'formatters': {
'verbose': { 'verbose': {
'format': 'format': '%(levelname)s %(asctime)s %(module)s (logger %(name)s): %(message)s'
'%(levelname)s %(asctime)s %(module)s (logger %(name)s): %(message)s'
}, },
}, },
@ -252,18 +354,18 @@ LOGGING = {
'filters': ['Http404AsInfo'], 'filters': ['Http404AsInfo'],
}, },
'seminar.prihlaska.form': { 'personalni.prihlaska.form':{
'handlers': ['console', 'registration_logfile'], 'handlers': ['console','registration_logfile'],
'level': 'INFO' 'level': 'INFO'
}, },
'seminar.prihlaska.problem': { 'personalni.prihlaska.problem':{
'handlers': ['console', 'mail_registration', 'registration_error_log'], 'handlers': ['console','mail_registration','registration_error_log'],
'level': 'INFO' 'level': 'INFO'
}, },
# Catch-all logger # Catch-all logger
'': { '': {
'handlers': ['console'], 'handlers': ['console'], # Add 'mail_admins' in prod and test
'level': 'DEBUG', 'level': 'DEBUG',
'filters': ['StripSensitiveFormData'], 'filters': ['StripSensitiveFormData'],
}, },
@ -273,7 +375,7 @@ LOGGING = {
'handlers': { 'handlers': {
'console': { 'console': {
'level': 'WARNING', 'level': 'WARNING', ## Set to 'DEBUG' in local
'class': 'logging.StreamHandler', 'class': 'logging.StreamHandler',
'formatter': 'verbose', 'formatter': 'verbose',
}, },
@ -289,24 +391,37 @@ LOGGING = {
'class': 'django.utils.log.AdminEmailHandler', 'class': 'django.utils.log.AdminEmailHandler',
'formatter': 'verbose', 'formatter': 'verbose',
}, },
'registration_logfile': { 'registration_logfile':{
'level': 'INFO', 'level': 'INFO',
'class': 'logging.FileHandler', 'class': 'logging.FileHandler',
# filename declared in specific configuration files # filename declared in specific configuration files
'formatter': 'verbose',
},
'registration_error_log': {
'level': 'INFO',
'class': 'logging.FileHandler',
# filename declared in specific configuration files
'formatter': 'verbose', 'formatter': 'verbose',
}, },
'registration_error_log':{
'level': 'INFO',
'class': 'logging.FileHandler',
# filename declared in specific configuration files
'formatter': 'verbose',
},
}, },
} }
# Permissions for uploads
FILE_UPLOAD_PERMISSIONS = 0o0644
# Logování neexistujících proměnných v templatech # MaM specific
SEMINAR_RESENI_DIR = os.path.join('reseni')
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')
# Logování chyb
class InvalidTemplateVariable(str): class InvalidTemplateVariable(str):
def __mod__(self, variable): def __mod__(self, variable):
import logging import logging
@ -318,38 +433,5 @@ class InvalidTemplateVariable(str):
return '' return ''
TEMPLATES[0]['OPTIONS']['string_if_invalid'] = InvalidTemplateVariable('%s') TEMPLATES[0]['OPTIONS']['string_if_invalid'] = InvalidTemplateVariable('%s')
# Django 3.2 vyžaduje explicitní nastavení autoklíče, zatím nechápu proč # Django 3.2 vyžaduje explicitní nastavení autoklíče, zatím nechápu proč
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
# TODO odstranit? (Je to default.)
# Whether to append trailing slashes to URLs.
APPEND_SLASH = True
# TODO odstranit? (Je to default.)
# Permissions for uploads
FILE_UPLOAD_PERMISSIONS = 0o0644
# TODO odstranit? (Je to default.)
# Automatická lokalizace
USE_I18N = True
# TODO odstranit? (Nevím o tom, že bychom ho někde používali.)
# Summernote = WYSIWYG editor (pro admin?)
SUMMERNOTE_CONFIG = {
'iframe': False,
'airMode': False,
'attachment_require_authentication': True,
'width': '80%',
# 'height': '30em',
'toolbar': [
['style', ['style']],
['font', ['bold', 'italic', 'superscript', 'subscript', 'clear']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
['insert', ['link', 'picture', 'hr']],
['view', ['fullscreen', 'codeview']],
['help', ['help']],
]
}

View file

@ -1,22 +0,0 @@
from .settings_common import *
DEBUG = True
TEMPLATES[0]['OPTIONS']['debug'] = True
MIDDLEWARE += (
'debug_toolbar.middleware.DebugToolbarMiddleware',
)
INSTALLED_APPS += (
'debug_toolbar', # Takovéto užitečné (pro debug) na html stránce vpravo
'django_extensions', # Kolekce zajímavých ./manage.py commandů
)
DEBUG_TOOLBAR_CONFIG = {
'SHOW_COLLAPSED': True,
}
# Nechceme nikomu omylem poslat e-mail
# Když někdo spustí omylem tohle nastavení, prostě to při poslání mailu spadne
# V settings_local a settings_test přepíšeme...
EMAIL_BACKEND = None

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