Merge branch 'develop' of gimli.ms.mff.cuni.cz:/akce/mam/git/mamweb into develop
This commit is contained in:
commit
ea3b1a7791
36 changed files with 485 additions and 50 deletions
20
Makefile
20
Makefile
|
@ -102,10 +102,10 @@ deploy_prod: venv_check
|
||||||
sync_prod_flatpages: venv_check
|
sync_prod_flatpages: venv_check
|
||||||
@echo Downloading current version of flatpages from mamweb-prod.
|
@echo Downloading current version of flatpages from mamweb-prod.
|
||||||
ssh mam-web@gimli.ms.mff.cuni.cz \
|
ssh mam-web@gimli.ms.mff.cuni.cz \
|
||||||
"cd /akce/mam/www/mamweb-prod; . env/bin/activate; ./manage.py dumpdata flatpages --indent=2 > flat.json"
|
"cd /akce/mam/www/mamweb-prod; . env/bin/activate; ./manage.py dumpdata flatpages --indent=2 > flat.json; ./fix_json.py flat.json flat_fixed.json"
|
||||||
rsync -ave ssh mam-web@gimli.ms.mff.cuni.cz:/akce/mam/www/mamweb-prod/flat.json ./flat.json
|
rsync -ave ssh mam-web@gimli.ms.mff.cuni.cz:/akce/mam/www/mamweb-prod/flat_fixed.json data/flat.json
|
||||||
@echo "Applying downloaded flatpages."
|
@echo "Applying downloaded flatpages."
|
||||||
./manage.py loaddata flat.json
|
./manage.py loaddata data/flat.json
|
||||||
@echo "Done."
|
@echo "Done."
|
||||||
|
|
||||||
# Sync test media directory with production
|
# Sync test media directory with production
|
||||||
|
@ -114,24 +114,16 @@ sync_test_media:
|
||||||
@if [ `readlink -f .` != "/aux/akce/mam/www/mamweb-test" ]; then echo "Only possible in /akce/mam/www/mamweb-test"; exit 1; fi
|
@if [ `readlink -f .` != "/aux/akce/mam/www/mamweb-test" ]; then echo "Only possible in /akce/mam/www/mamweb-test"; exit 1; fi
|
||||||
rsync -av --delete /akce/mam/www/mamweb-prod/media/ ./media
|
rsync -av --delete /akce/mam/www/mamweb-prod/media/ ./media
|
||||||
|
|
||||||
# Sync test database with production database
|
# Sync (with drop) test database with production database
|
||||||
sync_test_db:
|
|
||||||
@if [ ${USER} != "mam-web" ]; then echo "Only possible by user mam-web"; exit 1; fi
|
|
||||||
pg_dump mam_test > dump-test-`date +"%Y%m%d_%H%M"`.sql
|
|
||||||
pg_dump -Fc mam_prod > dump-prod.sql
|
|
||||||
pg_restore -c --if-exists -d mam_test dump-prod.sql
|
|
||||||
rm dump-prod.sql
|
|
||||||
@echo Done.
|
|
||||||
|
|
||||||
# Aggresive variant: destroy original mam_test db with 'DROP OWNED BY "mam-web";'
|
|
||||||
sync_test_db_aggressive:
|
sync_test_db_aggressive:
|
||||||
@if [ ${USER} != "mam-web" ]; then echo "Only possible by user mam-web"; exit 1; fi
|
@if [ ${USER} != "mam-web" ]; then echo "Only possible by user mam-web"; exit 1; fi
|
||||||
pg_dump mam_test > dump-test-`date +"%Y%m%d_%H%M"`.sql
|
pg_dump mam_test > dump-test-`date +"%Y%m%d_%H%M"`.sql
|
||||||
pg_dump -Fc mam_prod > dump-prod.sql
|
pg_dump -Fc mam_prod > dump-prod.sql
|
||||||
@# I am not sure which shell is used, so I am calling bash to make sure
|
@# I am not sure which shell is used, so I am calling bash to make sure
|
||||||
bash -c "psql mam_test <<< 'DROP OWNED BY \"mam-web\";'"
|
psql mam_test -c 'DROP OWNED BY "mam-web";'
|
||||||
pg_restore -c --if-exists -d mam_test dump-prod.sql
|
pg_restore -c --if-exists -d mam_test dump-prod.sql
|
||||||
rm dump-prod.sql
|
rm dump-prod.sql
|
||||||
|
psql mam_test -c "UPDATE django_site SET name='MaMweb (test)', domain='mam-test.ks.matfyz.cz' WHERE id=1"
|
||||||
@echo Done.
|
@echo Done.
|
||||||
|
|
||||||
# Sync test with production
|
# Sync test with production
|
||||||
|
|
|
@ -15,4 +15,4 @@ cd "$tmp"
|
||||||
wget --spider -o "$logfile" -r -p -X '/soustredeni/*/fotogalerie/' "$@" || true # wget nejspíš skončí s chybou, že něco nestáhl…
|
wget --spider -o "$logfile" -r -p -X '/soustredeni/*/fotogalerie/' "$@" || true # wget nejspíš skončí s chybou, že něco nestáhl…
|
||||||
|
|
||||||
echo "Result: (a last few lines of the file $logfile)"
|
echo "Result: (a last few lines of the file $logfile)"
|
||||||
sed -ne '/^Found [0-9]* broken links/,$ p' "$logfile"
|
sed -ne '/^Found [0-9]* broken link/,$ p' "$logfile"
|
||||||
|
|
|
@ -91,7 +91,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fields": {
|
"fields": {
|
||||||
"content": "<h2>Podzimní soustředění</h2>\r\n\r\n<p>se uskuteční 16. - 24. října 2021.</p>",
|
"content": "<h2>Jarní soustředění</h2>\r\n\r\n<p>se uskuteční 2. – 10. dubna 2022.</p>",
|
||||||
"enable_comments": false,
|
"enable_comments": false,
|
||||||
"registration_required": false,
|
"registration_required": false,
|
||||||
"sites": [
|
"sites": [
|
||||||
|
@ -136,7 +136,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fields": {
|
"fields": {
|
||||||
"content": "<h2>Zážitkové akce</h2>\r\n\r\n<h6>Letní a Zimní Škola Matematiky a Fyziky</h6>\r\n\r\n<p style=\"text-align: justify;\">ŠMFko je zážitková akce určená středoškolákům se zájmem o další sebevzdělání. Krom populárně naučných přednášek se na ŠMFku proběhneš venku, vyřádíš se ve sněhu, užiješ si veselý vnitřní program a taky se pobavíš během společného šarádění, lenošení nebo hraní na kytaru.</p>\r\n\r\n<h6>InterSoB</h6>\r\n\r\n<p style=\"text-align: justify;\">InterSoB je zábavná a poučná jednodenní soutěž středoškolských studentů, při které máte možnost podívat se netradičním způsobem do zákulisí Masarykovy univerzity, vyzkoušet si své schopnosti v mnoha různých oblastech, udělat si s kamarády zajímavý výlet po Brně a v neposlední řadě také poměřit svoje síly s dalšími týmy.</p>\r\n\r\n<h2>Další semináře</h2>\r\n\r\n<h6>Korespondenční Seminář z Programování</h6>\r\n\r\n<p style=\"text-align: justify;\">KSP je seminář určený pro studenty středních a základních škol, kteří mají zájem naučit se něco z oblasti algoritmů, logických úloh, programování a informatiky vůbec. Na své si však přijdou i příznivci matematiky (a vlastně libovolného přemýšlení), ježto oba obory mají mnoho společného.</p>\r\n\r\n<h6>FYzikální KOrespondenční Seminář</h6>\r\n\r\n<p style=\"text-align: justify;\">FYKOS pro vás představuje možnost si zajímavým způsobem rozšířit chápání fyziky a proniknout do dalších, dosud nepoznaných, oblastí této vědy. Cílem FYKOSu je rozvíjet fyzikální myšlení, protože člověk, který se umí nad (nejen fyzikálními) problémy zamyslet a cítí touhu dobrat se k nějakému řešení, se uplatní všude, kde si schopností lidského mozku cení.</p>\r\n\r\n<h6>Matematický korespondenční seminář PraSe (PRAžský SEminář)</h6>\r\n\r\n<p style=\"text-align: justify;\">Řešením úloh tohoto semináře získáš mnoho matematických znalostí a naučíš přesněji a srozumitelněji formulovat své myšlenky a závěry. Seminář je dobrou přípravou pro účast v nejrůznějších matematických soutěžích i pro další studium matematiky, ale schopnost logického myšlení, kterou si můžeš procvičit, se ti v životě bude hodit, i když se v něm třeba právě matematice věnovat nehodláš.</p>\r\n\r\n<h2>Pro mladší sourozence</h2>\r\n\r\n<h6>Pikomat</h6>\r\n\r\n<p style=\"text-align: justify;\">Pikomat je matematický korespondenční seminář určený žákům šestých až devátých tříd základních škol a studentům odpovídajících ročníků víceletých gymnázií. Spočívá v řešení několika úloh propojených příběhem. Na jaře se koná soustředění pro nejlepší řešitele, v létě pak tábor pro všechny zájemce.</p>\r\n\r\n<h6>Výfuk (VÝpočty Fyzikálních UKolů)</h6>\r\n\r\n<p style=\"text-align: justify;\">Výfuk je samostatný korespondenční seminář Matfyzu, který spadá pod Katedru didaktiky fyziky. Během školního roku kromě šesti sérií semináře organizátoři připravují i podzimní a jarní setkání, letní tábor a Náboj junior.</p>\r\n\r\n<h1>Databáze mimoškolních aktivit</h1>\r\n\r\n<p style=\"text-align: justify;\">Je-li ti výčet aktivit výše málo nebo tě žádná z nich nezaujala, doporučujeme navštívit web organizace <a href=\"https://prostredoskolaky.cz\">ProStředoškoláky</a>, jež zde připravila rozsáhlou databázi mimoškolních aktivit a akcí. Krom toho organizace pořádá soutěž <strong>Středoškolák roku</strong>, ve které každý rok oceňuje nejaktivnější středoškoláky. Věnuješ-li se tedy mimoškolně něčemu ve větším měřítku, neváhej se do soutěže přihlásit.</p>",
|
"content": "<h2>Zážitkové akce</h2>\r\n\r\n<a href=\"https://smf.mff.cuni.cz/\"><h6>Letní a Zimní Škola Matematiky a Fyziky</h6></a>\r\n\r\n<p style=\"text-align: justify;\">ŠMFko je zážitková akce určená středoškolákům se zájmem o další sebevzdělání. Krom populárně naučných přednášek se na ŠMFku proběhneš venku, vyřádíš se ve sněhu, užiješ si veselý vnitřní program a taky se pobavíš během společného šarádění, lenošení nebo hraní na kytaru.</p>\r\n\r\n<a href=\"https://intersob.math.muni.cz/\"><h6>InterSoB</h6></a>\r\n\r\n<p style=\"text-align: justify;\">InterSoB je zábavná a poučná jednodenní soutěž středoškolských studentů, při které máte možnost podívat se netradičním způsobem do zákulisí Masarykovy univerzity, vyzkoušet si své schopnosti v mnoha různých oblastech, udělat si s kamarády zajímavý výlet po Brně a v neposlední řadě také poměřit svoje síly s dalšími týmy.</p>\r\n\r\n<h2>Další semináře</h2>\r\n\r\n<a href=\"https://ksp.mff.cuni.cz/\"><h6>Korespondenční Seminář z Programování</h6></a>\r\n\r\n<p style=\"text-align: justify;\">KSP je seminář určený pro studenty středních a základních škol, kteří mají zájem naučit se něco z oblasti algoritmů, logických úloh, programování a informatiky vůbec. Na své si však přijdou i příznivci matematiky (a vlastně libovolného přemýšlení), ježto oba obory mají mnoho společného.</p>\r\n\r\n<a href=\"https://fykos.cz/\"><h6>FYzikální KOrespondenční Seminář</h6></a>\r\n\r\n<p style=\"text-align: justify;\">FYKOS pro vás představuje možnost si zajímavým způsobem rozšířit chápání fyziky a proniknout do dalších, dosud nepoznaných, oblastí této vědy. Cílem FYKOSu je rozvíjet fyzikální myšlení, protože člověk, který se umí nad (nejen fyzikálními) problémy zamyslet a cítí touhu dobrat se k nějakému řešení, se uplatní všude, kde si schopností lidského mozku cení.</p>\r\n\r\n\r\n<a href=\"https://prase.cz/\"><h6>Matematický korespondenční seminář PraSe (PRAžský SEminář)</h6></a>\r\n\r\n<p style=\"text-align: justify;\">Řešením úloh tohoto semináře získáš mnoho matematických znalostí a naučíš přesněji a srozumitelněji formulovat své myšlenky a závěry. Seminář je dobrou přípravou pro účast v nejrůznějších matematických soutěžích i pro další studium matematiky, ale schopnost logického myšlení, kterou si můžeš procvičit, se ti v životě bude hodit, i když se v něm třeba právě matematice věnovat nehodláš.</p>\r\n\r\n<h2>Pro mladší sourozence</h2>\r\n\r\n\r\n<a href=\"https://pikomat.mff.cuni.cz/\"><h6>Pikomat</h6></a>\r\n\r\n<p style=\"text-align: justify;\">Pikomat je matematický korespondenční seminář určený žákům šestých až devátých tříd základních škol a studentům odpovídajících ročníků víceletých gymnázií. Spočívá v řešení několika úloh propojených příběhem. Na jaře se koná soustředění pro nejlepší řešitele, v létě pak tábor pro všechny zájemce.</p>\r\n\r\n\r\n<a href=\"https://vyfuk.mff.cuni.cz/\"><h6>Výfuk (VÝpočty Fyzikálních UKolů)</h6></a>\r\n\r\n<p style=\"text-align: justify;\">Výfuk je samostatný korespondenční seminář Matfyzu, který spadá pod Katedru didaktiky fyziky. Během školního roku kromě šesti sérií semináře organizátoři připravují i podzimní a jarní setkání, letní tábor a Náboj junior.</p>\r\n\r\n<h1>Databáze mimoškolních aktivit</h1>\r\n\r\n<p style=\"text-align: justify;\">Je-li ti výčet aktivit výše málo nebo tě žádná z nich nezaujala, doporučujeme navštívit web organizace <a href=\"https://prostredoskolaky.cz\">ProStředoškoláky</a>, jež zde připravila rozsáhlou databázi mimoškolních aktivit a akcí. Krom toho organizace pořádá soutěž <strong>Středoškolák roku</strong>, ve které každý rok oceňuje nejaktivnější středoškoláky. Věnuješ-li se tedy mimoškolně něčemu ve větším měřítku, neváhej se do soutěže přihlásit.</p>",
|
||||||
"enable_comments": false,
|
"enable_comments": false,
|
||||||
"registration_required": false,
|
"registration_required": false,
|
||||||
"sites": [
|
"sites": [
|
||||||
|
@ -178,5 +178,20 @@
|
||||||
},
|
},
|
||||||
"model": "flatpages.flatpage",
|
"model": "flatpages.flatpage",
|
||||||
"pk": 29
|
"pk": 29
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"content": "<div class=\"content\">\r\n<h1>Nápověda ke korekturovátku</h1>\r\n\r\n<p>Korekturovátko slouží k přidávání korektur do PDF souborů. Umožňuje přidávat a komentovat korektury a označovat je jako k zanesení, zanesené nebo irelevantní. Rovněž umožňuje o PDF říci, že jsou právě zanášeny korektury nebo že je zastaralé.</p>\r\n\r\n<h2>Použití</h2>\r\n\r\n<p>Kliknu do PDF tam, kam chci zadat korekturu, napíši text a kliknu na Oprav! (nebo Ctrl-Enter). Korektura se zobrazí na pravé straně červeně. Pokud chci korekturu okomentovat, kliknu na ikonu <img src=\"http://127.0.0.1:8000/static/korektury/imgs/comment.png\" />, napíši komentář a kliknu na Oprav! (nebo Ctrl-Enter). Komentář se zobrazí pod původní korekturou.</p>\r\n\r\n<h2>Tlačítka u korektury</h2>\r\n\r\n<ul>\r\n\t<li><img src=\"http://127.0.0.1:8000/static/korektury/imgs/delete.png\" /> – smazat korekturu</li>\r\n\t<li><img src=\"http://127.0.0.1:8000/static/korektury/imgs/check.png\" /> – označt koreturu jako zanesenou</li>\r\n\t<li><img src=\"http://127.0.0.1:8000/static/korektury/imgs/cross.png\" /> – označit korekturu jako irelevantní (není to chyba, nebude zaneseno)</li>\r\n\t<li><img src=\"http://127.0.0.1:8000/static/korektury/imgs/tex.png\" /> – označt koreturu jako připravenou k zanesení</li>\r\n\t<li><img src=\"http://127.0.0.1:8000/static/korektury/imgs/edit.png\" /> – upravit text korektury</li>\r\n\t<li><img src=\"http://127.0.0.1:8000/static/korektury/imgs/comment.png\" /> – okomentovat korekturu</li>\r\n\t<li><img src=\"http://127.0.0.1:8000/static/korektury/imgs/hide.png\" /> – srolovat korekturu</li>\r\n</ul>\r\n\r\n<h2>Stavy</h2>\r\n\r\n<h3>Korektura</h3>\r\n\r\n<ul>\r\n\t<li>K vyřešení (červená) – bug report či návrh úpravy, probíhá diskuze, zatím nerozhodnuto</li>\r\n\t<li>Zanesená (modrá) – zanesená v TeXu</li>\r\n\t<li>Irelevantní (šedá) – není to chyba, nebude zanesena</li>\r\n\t<li>K zanesení (zelená) – rozhodnuto, čeká na zanesení do TeXu</li>\r\n</ul>\r\n\r\n<h3>PDF</h3>\r\n\r\n<ul>\r\n\t<li>Přidávání – probíhá přidávání korektur</li>\r\n\t<li>Zanášení (žluté pozadí) – probíhá zanášení korektur do TeXu</li>\r\n\t<li>Zastaralé (červené pozadí) – PDF je zastaralé, nepřidávat nové korektury</li>\r\n</ul>\r\n</div>",
|
||||||
|
"enable_comments": false,
|
||||||
|
"registration_required": false,
|
||||||
|
"sites": [
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"template_name": "",
|
||||||
|
"title": "Nápověda ke korekturovátku",
|
||||||
|
"url": "/korektury/help/"
|
||||||
|
},
|
||||||
|
"model": "flatpages.flatpage",
|
||||||
|
"pk": 30
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
173
docs/zapisy/2021-12-06-testovani_dokumentace_codereview.md
Normal file
173
docs/zapisy/2021-12-06-testovani_dokumentace_codereview.md
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
# Postup zkrášlení kódu M&Mího webu
|
||||||
|
|
||||||
|
## Obecně o webu
|
||||||
|
|
||||||
|
- Python, Django, spousta nějakých rozšíření, frontend HTML + CSS + trocha JS
|
||||||
|
- Velké břímě historie, kterou nejspíš nechceme zahodit
|
||||||
|
- Změny v M&M někdy dost zamotají potřebný kód (tituly)
|
||||||
|
- Občas je potřeba dělat opravy rychle
|
||||||
|
|
||||||
|
## Aktuální stav
|
||||||
|
|
||||||
|
- Zběsile zbastlený kód
|
||||||
|
- „Co je to ‚single responsibility principle‘?“ ☺
|
||||||
|
- Dost netriviální množství objektů a jejich vazeb
|
||||||
|
- Dost možná do velké míry inherentní složitost
|
||||||
|
- Webaři aktuálně relativně zběhlí v programování (ale je potřeba myslet na to, že to tak být nemusí)
|
||||||
|
- Webaři stárnou (Jethro, Kristý, Anet) a mizí (Pavel, Káťa), je potřeba web připravit na předání
|
||||||
|
- Kód je rozdělený mezi orgy a jeden „nerozumí“ tomu, co druhý napsal (musí to vyčíst z kódu, nezná souvislosti, …)
|
||||||
|
- Noví orgové se aktuálně musí ptát, což je jim nepříjemné a nutí je umět formulovat dotazy
|
||||||
|
|
||||||
|
### Invarianty
|
||||||
|
|
||||||
|
- Není to práce, ale zábava-ish → libovolný proces nesmí být (moc) na obtíž.
|
||||||
|
- I malé nepohodlí je potřeba vyvážit relativně velkým přínosem
|
||||||
|
- nebo dostatečně zřejmou vidinou budoucího pohodlí / minimalizace nepohodlí
|
||||||
|
- Nejsme programátoři, spíš jsme bastlíři kódu (kteří znají rozumnou podmnožinu syntaxe Pythonu)
|
||||||
|
- Zvlášť noví orgové
|
||||||
|
- Nechceme cílit na mega-profi kód, je to nedosažitelný cíl
|
||||||
|
- Nejspíš to do nějaké míry ubastlené bude pořád, ta míra závisí na zkušenosti aktuálních webařů
|
||||||
|
- Nástroje nás nesmí moc mást.
|
||||||
|
- Děláme to zadarmo jeden večer v týdnu
|
||||||
|
- Vývoj jde pomalu, často pomaleji než vývoj knihoven
|
||||||
|
- → kód se rozbíjí i sám
|
||||||
|
- Běží to na Gimlim
|
||||||
|
- Debian (old)stable → nemůžeme používat moc nové featury Pythonu
|
||||||
|
- Aktuálně Python 3.7.3
|
||||||
|
- Webaři jsou náhodně vzniklá skupina lidí.
|
||||||
|
- Různé nástroje, různé operační systémy
|
||||||
|
- Nechceme vynucovat konkrétní metody, multiplatformní nástroje asi požadovat můžeme
|
||||||
|
- Na serveru může běžet cokoliv, co tam jde rozběhnout
|
||||||
|
- Kód by neměl být moc složitý / matoucí / kompaktní (?)
|
||||||
|
- případně fakt hodně okomentovaný
|
||||||
|
|
||||||
|
## O co se snažíme
|
||||||
|
|
||||||
|
- Zpřístupnit vývoj novým webařům
|
||||||
|
- Umožnit chápání i cizího kódu co nejjednodušeji
|
||||||
|
- Nevzít si s sebou implementační tajemství ~~do hrobu~~ pryč z M&Mka
|
||||||
|
|
||||||
|
```graphviz
|
||||||
|
digraph "Závislosti věcí" {
|
||||||
|
ss -> sdil -> doku -> cs;
|
||||||
|
ss -> cit ->ref -> cs;
|
||||||
|
ref -> nrt -> tst -> cs;
|
||||||
|
sdil -> cr -> cs
|
||||||
|
ss -> nrt;
|
||||||
|
ss[label="Současný stav",shape=box];
|
||||||
|
cit[label="čitelný kód"];
|
||||||
|
cs[label="Cílový stav",shape=box];
|
||||||
|
ref[label="refactoring"];
|
||||||
|
nrt[label="nerozbít to"];
|
||||||
|
tst[label="testy"];
|
||||||
|
doku[label="dokumentace (vývojářská)"];
|
||||||
|
sdil[label="chápání kódu ostatních / stávajícího"];
|
||||||
|
cr[label="code review"];
|
||||||
|
nrt,sdil,cit[shape=hexagon];
|
||||||
|
tst,doku,cr[color=blue];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code review
|
||||||
|
|
||||||
|
Aktuálně: Pavel občas z rozmaru čte diffy; párkrát jsme zkoušeli [programovat v páru](https://mam.mff.cuni.cz/wiki/Web/Tipy/PairProgramming), je to relativně časově náročné.
|
||||||
|
|
||||||
|
- Nevynucovat
|
||||||
|
- Primární motivace je umožnit nějak vidět změny a případně k nim dávat komentáře, jak stylu „tohle mi není jasné“, tak i „tohle by chtělo přepsat“.
|
||||||
|
- Chceme hlavně vytvořit příležitost ke čtení cizího kódu a seznamování se s ním (i v zájmu zaučování nových webařů)
|
||||||
|
|
||||||
|
### Názory
|
||||||
|
|
||||||
|
- spíš post-hoc
|
||||||
|
- Možná code-review toho, co jde na produkci
|
||||||
|
- Chceme umět komentovat konkrétní řádky kódu
|
||||||
|
|
||||||
|
- Gitea/gitlab?
|
||||||
|
- Klikátko a barevný řádky a vyhledávání jsou fajn (gitlab, gitea)
|
||||||
|
- Jethro: je fajn umět skočit na definici
|
||||||
|
- Kombinace s CI
|
||||||
|
- Pokud bude gitea v něčem nedostatečná, tak hrozí, že bude potřeba migrovat znovu, což je nepraktické
|
||||||
|
- Gitlab je asi častější než gitea → je lepší názor si na to zvyknout
|
||||||
|
|
||||||
|
__Závěr:__ Zkusíme to hodit do GitLabu (nejspíš veřejného\*), zavedeme pull-requesty do `master` větve, náhodně se budeme přiřazovat a používat ho nějak intuitivně.
|
||||||
|
|
||||||
|
\*: Ve fakultním neumíme přidávat další uživatele, v KAMím zas možná není CI.
|
||||||
|
|
||||||
|
## Testy
|
||||||
|
|
||||||
|
Aktuálně: pár testů na dohromady řádově 4 funkce, drobný pokus o TDD, jenž narazil na úskalí reálného světa. Lokální spuštění testů trvá relativně dlouho, nejspíš kvůli spoustě migrací.
|
||||||
|
|
||||||
|
- Potřebujeme ověřit, že to funguje a že to _pořád_ funguje
|
||||||
|
|
||||||
|
- CI? Coverage?
|
||||||
|
|
||||||
|
### Názory
|
||||||
|
|
||||||
|
- PyTest je fajn
|
||||||
|
- Testovat frontend?
|
||||||
|
- Jethro: při nasazení by se mohly dělat screenshoty celých vybraných stránek přes Selenium (nebo obdobné) a hlásit rozdíly
|
||||||
|
|
||||||
|
__Závěr:__ Backend testujeme PyTestem (`./manage.py test`), u frontendu výhledově zkusíme ty vizuální diffy a pak se uvidí, CI podle webového klikátka na code review. Bylo by dobré mít rozumná testdata, ať se nemusí mockovat moc věcí.
|
||||||
|
|
||||||
|
## Formát kódu
|
||||||
|
|
||||||
|
Aktuálně: Jakýsi coding style zhruba existuje, není popsaný, šíří se lidovou slovesností.
|
||||||
|
|
||||||
|
- Nesmí být striktně vynucovaný
|
||||||
|
- Musel by být hodně nastavitelný
|
||||||
|
- Nechceme mít kód plný `#NOQA: WTF42`
|
||||||
|
- Nejspíš vždycky bude mít false positives (`seminar.utils.roman_numerals`) i false negatives (`seminar.models.tvorba.Cislo.posli_cislo_mailem`)
|
||||||
|
- Možná dobrý sluha, ale určitě špatný pán (also: špatná zkušenost ☺)
|
||||||
|
- __Důsledek:__ Hrozí, že těch falešných varování bude moc, čímž to ztratí smysl úplně
|
||||||
|
- Potenciálně by šlo aplikovat jen lokálně na změny?
|
||||||
|
|
||||||
|
### Názory
|
||||||
|
|
||||||
|
- P: nemyslím si, že zvládneme mít „průhledný kód“ (dostatečně konzistentní kód, aby ho člověk přestal vnímat a spíš viděl myšlenky).
|
||||||
|
|
||||||
|
- P: Kecadla do kódu trochu zavání větším peklem než užitkem (takže black a flake8 jsou ze hry); isort možná dává smysl; je otázka, jestli použít mypy, ale typové značky v kódu spíš chceme (zvlášť u věcí, které nejsou prostě django view – typicky utils.)
|
||||||
|
- J: Divné (=Pavlovo) groupování importů je spíš matoucí, spíš moc nepomáhalo
|
||||||
|
|
||||||
|
- P (doplněno zpětně): pydocstyle vynucuje PEP-257, který se možná tluče se sphinxem…
|
||||||
|
- P (též zpětně): Možná by se mohl dát použít pylint, tomu jde aspoň vysvětlit coding style a nerazí tupě PEP-8, ale nastavovat ho je asi větší porod, než jak moc pomůže…
|
||||||
|
|
||||||
|
__Závěr:__ Kecadla na formát spíš nechceme; isort by mohl být fajn, ale bylo by dobré mít rozdělené bloky „náš kód“, „standardní knihovna“ a „Django a další balíčky z PyPA“; u našeho kódu (utils, obecně ne-Django věci) chceme držet záměr (na úrovni dohody) psát signatury funkcí a v případě jejich přítomnosti se nechat varovat při porušení signatury.
|
||||||
|
|
||||||
|
- P (večerní prokrastinace): Bandit vypadá, že hlásí jen strašně obvious věci by default, nepřijde mi jako moc úžasné vylepšení. Do CI možná dobrý
|
||||||
|
|
||||||
|
## Dokumentace
|
||||||
|
|
||||||
|
Aktuálně: něco málo je na wiki (`/Web/`), občas má nějaká funkce docstring, obecně je toho málo
|
||||||
|
|
||||||
|
### Požadavky
|
||||||
|
|
||||||
|
- Jedno autoritativní místo a dá se najít
|
||||||
|
- Dostatečně „blízko“ kódu
|
||||||
|
- nastavit mindset na psaní dokumentace „rovnou“?
|
||||||
|
- Umožnit i obecný text, ne jen komentáře kódu (modulů, funkcí)
|
||||||
|
|
||||||
|
|
||||||
|
### Bonusy
|
||||||
|
|
||||||
|
- Zajišťování konzistence s uživatelskou dokumentací
|
||||||
|
- aktuálně wiki
|
||||||
|
- Podpora i dalších jazyků (Vue, Javascript, CSS, možná django-templates)
|
||||||
|
|
||||||
|
### Názory
|
||||||
|
|
||||||
|
- Jde to dělat sphinxem
|
||||||
|
- Stínovlas by mohl vědět v CZ.NICu se sphinx jede. Případně zkusit zjistit, co umí jejich Akademie
|
||||||
|
- Free-textová dokumentace (architektura ap.) má být nezávislá na dokumentaci konkrétního kódu, ale je fajn, když se to pak spojí dohromady, jde-li to.
|
||||||
|
- Lepší, když se obojí dá dělat stejným nástrojem
|
||||||
|
- Káťa má někdy pocit, že tráví spoustu času tím, že hledá který soubor vůbec upravit; to má v naší dokumentaci být.
|
||||||
|
|
||||||
|
__Závěr:__ Zkusíme použít sphinx (z nedostatku vlastních zkušeností a kvůli jeho popularitě), když se nám to nebude líbit, tak budeme řešit dál. Bylo by fajn najít nějaký workshop, bude to rychlejší nalejvárna než číst dokumentaci. V krátkodobém výhledu stáhneme vývojovou dokumentaci z webu do repozitáře a zprovozníme někde automatické buildy (aby se dala číst jako člověk a ne jako stroj).
|
||||||
|
|
||||||
|
## Uživatelská dokumentace
|
||||||
|
|
||||||
|
- K: Dělat ji ve spolupráci s těma uživatelema
|
||||||
|
- P: Dávný Hedgedoc může případně sloužit jako základ osnovy
|
||||||
|
- K: Dost možná nevíme, co bolí víc a co míň
|
||||||
|
- Jethro: Musí být na wiki, jinak tím zmateme oržstvo
|
||||||
|
|
||||||
|
__Závěr:__ Dokumentace bude na wiki (<https://mam.mff.cuni.cz/wiki/Web_user/Dokumentace>, výhledově možná ve stejné složce v dalších souborech), bude vznikat podle našich pocitů a dotazů od ostatních; výhledově bude schůzka na ukázání featur nového webu a tam se dosbírají náměty na to, co sepsat.
|
|
@ -62,11 +62,12 @@
|
||||||
</div>
|
</div>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endif%}
|
{% endif%}
|
||||||
|
<span id="nahoru" class="kotva_obrazku"></span>
|
||||||
<img src="{{obrazek.obrazek_stredni.url}}"
|
<img src="{{obrazek.obrazek_stredni.url}}"
|
||||||
height="{{vyska}}"
|
height="{{vyska}}"
|
||||||
width="{{sirka}}"
|
width="{{sirka}}"
|
||||||
alt="{{obrazek.popis}}"
|
alt="{{obrazek.popis}}"
|
||||||
class="obrazek" id="nahoru">
|
class="obrazek">
|
||||||
|
|
||||||
{% if obrazky_dalsi %}
|
{% if obrazky_dalsi %}
|
||||||
{% with obrazky_dalsi|first as dalsi_obrazek %}
|
{% with obrazky_dalsi|first as dalsi_obrazek %}
|
||||||
|
|
|
@ -2,6 +2,8 @@ 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
|
||||||
|
|
||||||
|
from django.core.mail import send_mail
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
# Register your models here.
|
# Register your models here.
|
||||||
class KorekturovanePDFAdmin(VersionAdmin):
|
class KorekturovanePDFAdmin(VersionAdmin):
|
||||||
|
@ -16,11 +18,31 @@ class KorekturovanePDFAdmin(VersionAdmin):
|
||||||
fieldsets = [
|
fieldsets = [
|
||||||
(None,
|
(None,
|
||||||
{'fields':
|
{'fields':
|
||||||
['pdf', 'cas', 'org', 'stran', 'nazev', 'komentar']}),
|
['pdf', 'cas', 'org', 'stran', 'nazev', 'komentar', 'poslat_mail']}),
|
||||||
# (u'PDF', {'fields': ['pdf']}),
|
# (u'PDF', {'fields': ['pdf']}),
|
||||||
]
|
]
|
||||||
list_display = ['nazev', 'cas', 'stran', 'org']
|
list_display = ['nazev', 'cas', 'stran', 'org']
|
||||||
list_filter = []
|
list_filter = []
|
||||||
search_fields = []
|
search_fields = []
|
||||||
|
|
||||||
|
def save_model(self, 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
|
||||||
|
odkaz = request.build_absolute_uri(reverse('korektury', kwargs={'pdf': obj.id}))
|
||||||
|
odesilatel = 'korekturovatko-nove-pdf@mam.mff.cuni.cz'
|
||||||
|
prijemce = 'org@mam.mff.cuni.cz'
|
||||||
|
predmet = f'Nové korektury: {obj.nazev}'
|
||||||
|
text = f'''\
|
||||||
|
V korekturovátku se objevil nový soubor: {obj.nazev}
|
||||||
|
{odkaz}
|
||||||
|
|
||||||
|
Popis souboru:
|
||||||
|
{obj.komentar}
|
||||||
|
|
||||||
|
---
|
||||||
|
S pozdravem a korekturám zdar!
|
||||||
|
Korekturovátko
|
||||||
|
'''
|
||||||
|
send_mail(predmet,text,odesilatel,[prijemce])
|
||||||
|
|
||||||
admin.site.register(KorekturovanePDF, KorekturovanePDFAdmin)
|
admin.site.register(KorekturovanePDF, KorekturovanePDFAdmin)
|
||||||
|
|
18
korektury/migrations/0018_korekturovanepdf_poslat_mail.py
Normal file
18
korektury/migrations/0018_korekturovanepdf_poslat_mail.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.24 on 2021-12-05 23:09
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('korektury', '0017_auto_20190610_2358'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='korekturovanepdf',
|
||||||
|
name='poslat_mail',
|
||||||
|
field=models.BooleanField(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.', verbose_name='Poslat mail o novém PDF'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -63,6 +63,10 @@ class KorekturovanePDF(models.Model):
|
||||||
status = models.CharField(u'stav PDF',max_length=16, choices=STATUS_CHOICES, blank=False,
|
status = models.CharField(u'stav PDF',max_length=16, choices=STATUS_CHOICES, blank=False,
|
||||||
default = STATUS_PRIDAVANI)
|
default = STATUS_PRIDAVANI)
|
||||||
|
|
||||||
|
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.',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
#TODO Nepovinný foreign key k číslu
|
#TODO Nepovinný foreign key k číslu
|
||||||
|
|
||||||
|
|
|
@ -89,6 +89,7 @@ form {
|
||||||
border: 4px solid red;
|
border: 4px solid red;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
|
opacity: 80%;
|
||||||
}
|
}
|
||||||
.close-button{
|
.close-button{
|
||||||
background-color: yellow;
|
background-color: yellow;
|
||||||
|
|
|
@ -4,10 +4,11 @@
|
||||||
<head>
|
<head>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
<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 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>
|
<script src="{% static "korektury/opraf.js"%}"></script>
|
||||||
<title>Korektury {{pdf.nazev}}</title>
|
<title>Korektury {{pdf.nazev}}</title>
|
||||||
</head>
|
</head>
|
||||||
<body {% if pdf.status == 'zanaseni'%} class="comitting" {% elif pdf.status == 'zastarale' %} class="deprecated" {% endif %} onload='place_comments()'>
|
<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>
|
<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 == '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 %}
|
{% if pdf.status == 'zastarale' %} <h2> Toto PDF je již zastaralé, nepřidávejte nové korektury </h2> {% endif %}
|
||||||
|
|
|
@ -22,9 +22,8 @@
|
||||||
<ul>
|
<ul>
|
||||||
{% for pdf in skupina.list %}
|
{% for pdf in skupina.list %}
|
||||||
<li><span {% if pdf.status == 'zanaseni'%} class="comitting-text" {% elif pdf.status == 'zastarale' %} class="deprecated-text" {% endif %}>
|
<li><span {% if pdf.status == 'zanaseni'%} class="comitting-text" {% elif pdf.status == 'zastarale' %} class="deprecated-text" {% endif %}>
|
||||||
<b>{{ pdf.nazev }}</b>
|
<b><a href="/korektury/{{pdf.id}}">{{ pdf.nazev }}</a></b>
|
||||||
<i>{{pdf.komentar}}</i>
|
<i>{{pdf.komentar}}</i>
|
||||||
<a href="/korektury/{{pdf.id}}">{{pdf.pdf.name}}</a>
|
|
||||||
(k opravě: {{pdf.k_oprave_cnt}},
|
(k opravě: {{pdf.k_oprave_cnt}},
|
||||||
opraveno: {{pdf.opraveno_cnt}},
|
opraveno: {{pdf.opraveno_cnt}},
|
||||||
není chyba: {{pdf.neni_chyba_cnt}},
|
není chyba: {{pdf.neni_chyba_cnt}},
|
||||||
|
|
|
@ -6,5 +6,4 @@ urlpatterns = [
|
||||||
path('korektury/', org_required(views.KorekturySeskupeneListView.as_view()), name='korektury_list'),
|
path('korektury/', org_required(views.KorekturySeskupeneListView.as_view()), name='korektury_list'),
|
||||||
path('korektury/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('korektury/<int:pdf>/', org_required(views.KorekturyView.as_view()), name='korektury'),
|
path('korektury/<int:pdf>/', org_required(views.KorekturyView.as_view()), name='korektury'),
|
||||||
path('korektury/help/', org_required(views.KorekturyHelpView.as_view()), name='korektury-help'),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -87,7 +87,7 @@ class KorekturyView(generic.TemplateView):
|
||||||
|
|
||||||
op = Oprava(x=x,y=y, autor=autor, text=text, strana=strana,pdf = pdf)
|
op = Oprava(x=x,y=y, autor=autor, text=text, strana=strana,pdf = pdf)
|
||||||
op.save()
|
op.save()
|
||||||
self.send_email_notification_komentar(op, autor, text)
|
self.send_email_notification_komentar(op,autor)
|
||||||
elif (action == 'del'):
|
elif (action == 'del'):
|
||||||
id = int(q.get('id'))
|
id = int(q.get('id'))
|
||||||
op = Oprava.objects.get(id=id)
|
op = Oprava.objects.get(id=id)
|
||||||
|
@ -125,7 +125,7 @@ class KorekturyView(generic.TemplateView):
|
||||||
text = q.get('txt')
|
text = q.get('txt')
|
||||||
kom = Komentar(oprava=op,autor=autor,text=text)
|
kom = Komentar(oprava=op,autor=autor,text=text)
|
||||||
kom.save()
|
kom.save()
|
||||||
self.send_email_notification_komentar(op, autor, text)
|
self.send_email_notification_komentar(op,autor)
|
||||||
elif (action == 'update-comment'):
|
elif (action == 'update-comment'):
|
||||||
id = int(q.get('id'))
|
id = int(q.get('id'))
|
||||||
kom = Komentar.objects.get(id=id)
|
kom = Komentar.objects.get(id=id)
|
||||||
|
@ -151,21 +151,25 @@ class KorekturyView(generic.TemplateView):
|
||||||
context['autor'] = autor
|
context['autor'] = autor
|
||||||
return render(request, 'korektury/opraf.html',context)
|
return render(request, 'korektury/opraf.html',context)
|
||||||
|
|
||||||
def send_email_notification_komentar(self, oprava, autor, text):
|
def send_email_notification_komentar(self, oprava, autor):
|
||||||
''' Rozesle e-mail pri pridani komentare,
|
''' Rozesle e-mail pri pridani komentare / opravy,
|
||||||
ktery obsahuje text komentare.
|
ktery obsahuje text vlakna opravy.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# parametry e-mailu
|
# parametry e-mailu
|
||||||
#odkaz = "https://mam.mff.cuni.cz/korektury/{}/".format(oprava.pdf.pk)
|
#odkaz = "https://mam.mff.cuni.cz/korektury/{}/".format(oprava.pdf.pk)
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
odkaz = self.request.build_absolute_uri(reverse('korektury', kwargs={'pdf': oprava.pdf.pk}))
|
odkaz = self.request.build_absolute_uri(reverse('korektury', kwargs={'pdf': oprava.pdf.pk}))
|
||||||
|
odkaz = f"{odkaz}#op{oprava.id}-pointer"
|
||||||
from_email = 'korekturovatko@mam.mff.cuni.cz'
|
from_email = 'korekturovatko@mam.mff.cuni.cz'
|
||||||
subject = 'Nová korektura od {} v {}'.format(autor,
|
subject = 'Nová korektura od {} v {}'.format(autor, oprava.pdf.nazev)
|
||||||
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\
|
text = u"Text komentáře:\n\n{}\n\n=== Konec textu komentáře ===\n\
|
||||||
\nodkaz do korekturovátka: {}\n\
|
\nodkaz do korekturovátka: {}\n\
|
||||||
\nVaše korekturovátko\n".format(text, odkaz)
|
\nVaše korekturovátko\n".format(optext, odkaz)
|
||||||
|
|
||||||
# Prijemci e-mailu
|
# Prijemci e-mailu
|
||||||
emails = set()
|
emails = set()
|
||||||
|
@ -193,6 +197,9 @@ class KorekturyView(generic.TemplateView):
|
||||||
|
|
||||||
if not settings.POSLI_MAILOVOU_NOTIFIKACI:
|
if not settings.POSLI_MAILOVOU_NOTIFIKACI:
|
||||||
print("Poslal bych upozornění na tyto adresy: ", " ".join(emails))
|
print("Poslal bych upozornění na tyto adresy: ", " ".join(emails))
|
||||||
|
print("---- Upozornění:")
|
||||||
|
print(text)
|
||||||
|
print("---- Konec upozornění")
|
||||||
return
|
return
|
||||||
|
|
||||||
send_mail(subject, text, from_email, list(emails))
|
send_mail(subject, text, from_email, list(emails))
|
||||||
|
|
|
@ -79,6 +79,7 @@ TEMPLATES = [
|
||||||
'django.contrib.messages.context_processors.messages',
|
'django.contrib.messages.context_processors.messages',
|
||||||
'sekizai.context_processors.sekizai',
|
'sekizai.context_processors.sekizai',
|
||||||
'header_fotky.context_processors.vzhled',
|
'header_fotky.context_processors.vzhled',
|
||||||
|
'various.context_processors.rozliseni',
|
||||||
'various.context_processors.april',
|
'various.context_processors.april',
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -28,7 +28,9 @@ INTERNAL_IPS = ['127.0.0.1']
|
||||||
|
|
||||||
TEMPLATES[0]['OPTIONS']['debug'] = True
|
TEMPLATES[0]['OPTIONS']['debug'] = True
|
||||||
|
|
||||||
ALLOWED_HOSTS = ['127.0.0.1', '192.168.43.34']
|
from ipaddress import ip_network
|
||||||
|
ALLOWED_HOSTS = [str(ip) for ip in ip_network('192.168.0.0/16')]
|
||||||
|
ALLOWED_HOSTS.append('127.0.0.1')
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
|
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
|
||||||
|
@ -97,3 +99,4 @@ LOGGING = {
|
||||||
# E-maily posílat chceme, ale do terminálu :-)
|
# E-maily posílat chceme, ale do terminálu :-)
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||||
SEND_EMAIL_NOTIFICATIONS = True
|
SEND_EMAIL_NOTIFICATIONS = True
|
||||||
|
LOCAL_TEST_PROD = "local"
|
||||||
|
|
|
@ -67,3 +67,4 @@ LOGGING['handlers']['registration_error_log']['filename'] = '/home/mam-web/logs/
|
||||||
|
|
||||||
# E-MAIL NOTIFICATIONS
|
# E-MAIL NOTIFICATIONS
|
||||||
POSLI_MAILOVOU_NOTIFIKACI = True
|
POSLI_MAILOVOU_NOTIFIKACI = True
|
||||||
|
LOCAL_TEST_PROD = "prod"
|
||||||
|
|
|
@ -76,3 +76,4 @@ EMAIL_BACKEND = 'various.mail_prefixer.PrefixingMailBackend'
|
||||||
# TODO Pouze na otestování testu… Zvolit konferu!
|
# TODO Pouze na otestování testu… Zvolit konferu!
|
||||||
# XXX: Je to pole, protože implementační detail backendu.
|
# XXX: Je to pole, protože implementační detail backendu.
|
||||||
TESTOVACI_EMAILOVA_KONFERENCE = ['betatest@mam.mff.cuni.cz']
|
TESTOVACI_EMAILOVA_KONFERENCE = ['betatest@mam.mff.cuni.cz']
|
||||||
|
LOCAL_TEST_PROD = "test"
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
@import url("rozliseni.css");
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'OpenSans';
|
font-family: 'OpenSans';
|
||||||
src: url("../fonts/OpenSans/OpenSans-Regular.ttf");
|
src: url("../fonts/OpenSans/OpenSans-Regular.ttf");
|
||||||
|
@ -1111,6 +1113,21 @@ div.zadani_termin .datum {
|
||||||
float: right;
|
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 {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
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 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**/
|
/**/
|
||||||
|
|
||||||
|
|
29
mamweb/static/css/rozliseni.css
Normal file
29
mamweb/static/css/rozliseni.css
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/* Rozlišení mezi lokálním, test a produkčním webem */
|
||||||
|
|
||||||
|
.localweb {
|
||||||
|
border-left: 20px solid greenyellow;
|
||||||
|
border-right: 20px solid greenyellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.localweb .login-bar {
|
||||||
|
margin-left: -20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.testweb {
|
||||||
|
border-left: 20px solid darkorange;
|
||||||
|
border-right: 20px solid darkorange;
|
||||||
|
}
|
||||||
|
|
||||||
|
.testweb .login-bar {
|
||||||
|
margin-left: -20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Produkční web z pohledu superuživatele */
|
||||||
|
.suprodweb {
|
||||||
|
border-left: 20px solid red;
|
||||||
|
border-right: 20px solid red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suprodweb .login-bar {
|
||||||
|
margin-left: -20px;
|
||||||
|
}
|
|
@ -4,8 +4,11 @@
|
||||||
{% block extrahead %}
|
{% block extrahead %}
|
||||||
<link rel="shortcut icon" href="{% static 'favicon.ico' %}" type="image/x-icon">
|
<link rel="shortcut icon" href="{% static 'favicon.ico' %}" type="image/x-icon">
|
||||||
<script src="{% static 'js/jquery-1.11.1.js' %}"></script>
|
<script src="{% static 'js/jquery-1.11.1.js' %}"></script>
|
||||||
|
<link href="{% static 'css/rozliseni.css' %}?version=1" rel="stylesheet">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block bodyclass %}{{ LOCAL_TEST_PROD }}web{% endblock %}
|
||||||
|
|
||||||
{% block branding %}
|
{% block branding %}
|
||||||
<h1 id="site-name"><a href="/"> M&M GWP web </a></h1>
|
<h1 id="site-name"><a href="/"> M&M GWP web </a></h1>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
{% block script %}{% endblock %}
|
{% block script %}{% endblock %}
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body class='{% if user.is_staff %}org-logged-in{% endif %}'>
|
<body class='{{ LOCAL_TEST_PROD }}web{% if user.is_staff %} org-logged-in{% endif %}'>
|
||||||
|
|
||||||
{% if user.is_staff %}
|
{% if user.is_staff %}
|
||||||
<div class="login-bar" >
|
<div class="login-bar" >
|
||||||
|
|
|
@ -49,7 +49,7 @@ urlpatterns = [
|
||||||
path('comments_dj/', include('django_comments.urls')),
|
path('comments_dj/', include('django_comments.urls')),
|
||||||
|
|
||||||
# REST API
|
# REST API
|
||||||
path('api/', include(router.urls)),
|
# path('api/', include(router.urls)),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<form method=get action=../odevzdavatko>
|
<form method=get action=.>
|
||||||
{{ filtr.resitele }}
|
{{ filtr.resitele }}
|
||||||
{{ filtr.problemy }}
|
{{ filtr.problemy }}
|
||||||
Od: {{ filtr.reseni_od }}
|
Od: {{ filtr.reseni_od }}
|
||||||
|
|
|
@ -312,12 +312,15 @@ class PrehledOdevzdanychReseni(ListView):
|
||||||
resitel = m.Resitel.objects.filter(osoba__user=self.request.user).first()
|
resitel = m.Resitel.objects.filter(osoba__user=self.request.user).first()
|
||||||
qs = super().get_queryset()
|
qs = super().get_queryset()
|
||||||
qs = qs.filter(reseni__resitele__in=[resitel])
|
qs = qs.filter(reseni__resitele__in=[resitel])
|
||||||
|
# Setřídíme podle času doručení řešení, aby se netřídily podle okamžiku vyrobení Hodnocení
|
||||||
|
qs = qs.order_by('reseni__cas_doruceni')
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
def get_context_data(self, *args, **kwargs):
|
def get_context_data(self, *args, **kwargs):
|
||||||
ctx = super().get_context_data(*args, **kwargs)
|
ctx = super().get_context_data(*args, **kwargs)
|
||||||
# Ročník určujeme podle čísla, do jehož deadlinu došlo řešení.
|
# Ročník určujeme podle čísla, do jehož deadlinu došlo řešení.
|
||||||
# Chceme to mít seřazené, takže místo comphrerehsion ručně postavíme pole polí. Django templates neumí použít OrderedDict :-/
|
# Chceme to mít seřazené, takže místo comphrerehsion ručně postavíme pole polí. Django templates neumí použít OrderedDict :-/
|
||||||
|
# TODO: Funkce deadline vrací deadliny v jiném ročníku, zvlášť pokud se vyrobí řešení až po deadlinu (třeba při poslání mailem)
|
||||||
podle_rocniku = []
|
podle_rocniku = []
|
||||||
for rocnik, hodnoceni in groupby(ctx['object_list'], lambda ho: deadline(ho.reseni.cas_doruceni)[1].rocnik if deadline(ho.reseni.cas_doruceni) is not None else None):
|
for rocnik, hodnoceni in groupby(ctx['object_list'], lambda ho: deadline(ho.reseni.cas_doruceni)[1].rocnik if deadline(ho.reseni.cas_doruceni) is not None else None):
|
||||||
podle_rocniku.append((rocnik, list(hodnoceni)))
|
podle_rocniku.append((rocnik, list(hodnoceni)))
|
||||||
|
|
|
@ -7,6 +7,7 @@ from django.views.generic.base import TemplateView
|
||||||
from django.contrib.auth.models import User, Permission, Group
|
from django.contrib.auth.models import User, Permission, Group
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
import seminar.models as s
|
import seminar.models as s
|
||||||
import seminar.models as m
|
import seminar.models as m
|
||||||
|
@ -14,6 +15,7 @@ from .forms import PrihlaskaForm, ProfileEditForm, PoMaturiteProfileEditForm
|
||||||
|
|
||||||
from datetime import date
|
from datetime import date
|
||||||
import logging
|
import logging
|
||||||
|
import csv
|
||||||
|
|
||||||
from seminar.views import formularOKView
|
from seminar.views import formularOKView
|
||||||
from various.autentizace.views import LoginView
|
from various.autentizace.views import LoginView
|
||||||
|
@ -304,3 +306,71 @@ def profilView(request):
|
||||||
return ResitelView.as_view()(request)
|
return ResitelView.as_view()(request)
|
||||||
else:
|
else:
|
||||||
return LoginView.as_view()(request)
|
return LoginView.as_view()(request)
|
||||||
|
|
||||||
|
def dataResiteluCsvResponse(queryset, columns=None, with_header=True):
|
||||||
|
"""Pomocná funkce pro vracení dat řešitelů jako CSV. Musí dostat správný QuerySet, který dává Řešitele"""
|
||||||
|
# TODO: Možná nějak zobecnit i na Osoby?
|
||||||
|
# TODO: Nemá to spíš být class-based? Tohle je efektivně metoda "get", které ale chybí "get_queryset"…
|
||||||
|
|
||||||
|
default_columns = (
|
||||||
|
'id',
|
||||||
|
'osoba__jmeno',
|
||||||
|
'osoba__prijmeni',
|
||||||
|
'osoba__prezdivka',
|
||||||
|
'osoba__email',
|
||||||
|
'osoba__telefon',
|
||||||
|
'osoba__user__username',
|
||||||
|
'osoba__datum_narozeni',
|
||||||
|
'osoba__pohlavi_muz',
|
||||||
|
'osoba__ulice',
|
||||||
|
'osoba__mesto',
|
||||||
|
'osoba__psc',
|
||||||
|
'osoba__stat',
|
||||||
|
'skola', #FIXME: dává jen ID
|
||||||
|
'poznamka',
|
||||||
|
'osoba__poznamka',
|
||||||
|
'rok_maturity',
|
||||||
|
'zasilat',
|
||||||
|
'zasilat_cislo_emailem',
|
||||||
|
'osoba__datum_registrace',
|
||||||
|
'osoba__datum_souhlasu_udaje',
|
||||||
|
'osoba__datum_souhlasu_zasilani',
|
||||||
|
)
|
||||||
|
if columns is None: columns = default_columns
|
||||||
|
|
||||||
|
field_name_overrides = {
|
||||||
|
# Zrušení prefixu "osoba__"
|
||||||
|
'osoba__jmeno': 'jmeno',
|
||||||
|
'osoba__prijmeni': 'prijmeni',
|
||||||
|
'osoba__prezdivka': 'prezdivka',
|
||||||
|
'osoba__email': 'email',
|
||||||
|
'osoba__telefon': 'telefon',
|
||||||
|
'osoba__user__username': 'user',
|
||||||
|
'osoba__datum_narozeni': 'datum_narozeni',
|
||||||
|
'osoba__pohlavi_muz': 'pohlavi_muz',
|
||||||
|
'osoba__ulice': 'ulice',
|
||||||
|
'osoba__mesto': 'mesto',
|
||||||
|
'osoba__psc': 'psc',
|
||||||
|
'osoba__stat': 'stat',
|
||||||
|
'osoba__datum_registrace': 'datum_registrace',
|
||||||
|
'osoba__datum_souhlasu_udaje': 'datum_souhlasu_udaje',
|
||||||
|
'osoba__datum_souhlasu_zasilani':'datum_souhlasu_zasilani',
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_field_name(column_name):
|
||||||
|
if column_name in field_name_overrides:
|
||||||
|
return field_name_overrides[column_name]
|
||||||
|
return column_name
|
||||||
|
|
||||||
|
response = HttpResponse(content_type='text/csv')
|
||||||
|
writer = csv.writer(response)
|
||||||
|
|
||||||
|
# První řádek je záhlaví
|
||||||
|
if with_header:
|
||||||
|
writer.writerow(map(get_field_name, columns))
|
||||||
|
|
||||||
|
# Data:
|
||||||
|
queryset_list = queryset.values_list(*columns)
|
||||||
|
writer.writerows(queryset_list)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
|
@ -23,7 +23,7 @@ django-solo
|
||||||
django-ckeditor
|
django-ckeditor
|
||||||
django-flat-theme
|
django-flat-theme
|
||||||
django-taggit
|
django-taggit
|
||||||
django-autocomplete-light
|
django-autocomplete-light>=3.9.0rc1
|
||||||
django-crispy-forms
|
django-crispy-forms
|
||||||
django-imagekit
|
django-imagekit
|
||||||
django-polymorphic
|
django-polymorphic
|
||||||
|
|
19
seminar/migrations/0100_auto_20211129_2354.py
Normal file
19
seminar/migrations/0100_auto_20211129_2354.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 2.2.24 on 2021-11-29 22:54
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import seminar.models.tvorba
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('seminar', '0099_auto_20210916_1509'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='cislo',
|
||||||
|
name='pdf',
|
||||||
|
field=models.FileField(blank=True, help_text='PDF čísla, které si mohou řešitelé stáhnout', null=True, storage=seminar.models.tvorba.OverwriteStorage(), upload_to=seminar.models.tvorba.cislo_pdf_filename, verbose_name='pdf'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,5 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from .base import SeminarModelBase
|
from .base import SeminarModelBase
|
||||||
|
@ -65,3 +66,5 @@ class Obrazek(SeminarModelBase):
|
||||||
upload_to='obrazky/%Y/%m/%d/', blank=True, null=True)
|
upload_to='obrazky/%Y/%m/%d/', blank=True, null=True)
|
||||||
|
|
||||||
# TODO placement hint - chci ho tady / pred textem / za textem
|
# TODO placement hint - chci ho tady / pred textem / za textem
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ from django.conf import settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||||
|
from django.core.files.storage import FileSystemStorage
|
||||||
from django.utils.text import get_valid_filename
|
from django.utils.text import get_valid_filename
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
|
|
||||||
|
@ -37,6 +38,13 @@ from .base import SeminarModelBase
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class OverwriteStorage(FileSystemStorage):
|
||||||
|
""" Varianta FileSystemStorage, která v případě, že soubor cílového
|
||||||
|
jména již existuje, ho smaže a místo něj uloží soubor nový"""
|
||||||
|
def get_available_name(self,name, max_length=None):
|
||||||
|
if self.exists(name):
|
||||||
|
os.remove(os.path.join(self.location,name))
|
||||||
|
return super().get_available_name(name,max_length)
|
||||||
|
|
||||||
@reversion.register(ignore_duplicates=True)
|
@reversion.register(ignore_duplicates=True)
|
||||||
class Rocnik(SeminarModelBase):
|
class Rocnik(SeminarModelBase):
|
||||||
|
@ -173,7 +181,7 @@ class Cislo(SeminarModelBase):
|
||||||
help_text='Neveřejná poznámka k číslu (plain text)')
|
help_text='Neveřejná poznámka k číslu (plain text)')
|
||||||
|
|
||||||
pdf = models.FileField('pdf', upload_to=cislo_pdf_filename, null=True, blank=True,
|
pdf = models.FileField('pdf', upload_to=cislo_pdf_filename, null=True, blank=True,
|
||||||
help_text='PDF čísla, které si mohou řešitelé stáhnout')
|
help_text='PDF čísla, které si mohou řešitelé stáhnout', storage=OverwriteStorage())
|
||||||
|
|
||||||
titulka_nahled = models.ImageField('Obrázek titulní strany', upload_to=cislo_png_filename, null=True, blank=True,
|
titulka_nahled = models.ImageField('Obrázek titulní strany', upload_to=cislo_png_filename, null=True, blank=True,
|
||||||
help_text='Obrázek titulní strany, generuje se automaticky')
|
help_text='Obrázek titulní strany, generuje se automaticky')
|
||||||
|
@ -390,10 +398,11 @@ class Problem(SeminarModelBase,PolymorphicModel):
|
||||||
# Implicitini implementace, jednotlivé dědící třídy si přepíšou
|
# Implicitini implementace, jednotlivé dědící třídy si přepíšou
|
||||||
@cached_property
|
@cached_property
|
||||||
def kod_v_rocniku(self):
|
def kod_v_rocniku(self):
|
||||||
if self.stav == 'zadany':
|
if self.stav == Problem.STAV_ZADANY or self.stav == Problem.STAV_VYRESENY:
|
||||||
if self.nadproblem:
|
if self.nadproblem:
|
||||||
return self.nadproblem.kod_v_rocniku+".{}".format(self.kod)
|
return self.nadproblem.kod_v_rocniku+".{}".format(self.kod)
|
||||||
return str(self.kod)
|
return str(self.kod)
|
||||||
|
logger.warning(f"K problému {self} byl vyžadován kód v ročníku, i když není zadaný ani vyřešený.")
|
||||||
return '<Není zadaný>'
|
return '<Není zadaný>'
|
||||||
|
|
||||||
# def verejne(self):
|
# def verejne(self):
|
||||||
|
@ -467,10 +476,11 @@ class Tema(Problem):
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def kod_v_rocniku(self):
|
def kod_v_rocniku(self):
|
||||||
if self.stav == 'zadany':
|
if self.stav == Problem.STAV_ZADANY or self.stav == Problem.STAV_VYRESENY:
|
||||||
if self.nadproblem:
|
if self.nadproblem:
|
||||||
return self.nadproblem.kod_v_rocniku+".t{}".format(self.kod)
|
return self.nadproblem.kod_v_rocniku+".t{}".format(self.kod)
|
||||||
return "t{}".format(self.kod)
|
return "t{}".format(self.kod)
|
||||||
|
logger.warning(f"K problému {self} byl vyžadován kód v ročníku, i když není zadaný ani vyřešený.")
|
||||||
return '<Není zadaný>'
|
return '<Není zadaný>'
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
@ -501,11 +511,12 @@ class Clanek(Problem):
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def kod_v_rocniku(self):
|
def kod_v_rocniku(self):
|
||||||
if self.stav == 'zadany':
|
if self.stav == Problem.STAV_ZADANY or self.stav == Problem.STAV_VYRESENY:
|
||||||
# Nemělo by být potřeba
|
# Nemělo by být potřeba
|
||||||
# if self.nadproblem:
|
# if self.nadproblem:
|
||||||
# return self.nadproblem.kod_v_rocniku+".c{}".format(self.kod)
|
# return self.nadproblem.kod_v_rocniku+".c{}".format(self.kod)
|
||||||
return "c{}".format(self.kod)
|
return "c{}".format(self.kod)
|
||||||
|
logger.warning(f"K problému {self} byl vyžadován kód v ročníku, i když není zadaný ani vyřešený.")
|
||||||
return '<Není zadaný>'
|
return '<Není zadaný>'
|
||||||
|
|
||||||
def node(self):
|
def node(self):
|
||||||
|
@ -538,11 +549,12 @@ class Uloha(Problem):
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def kod_v_rocniku(self):
|
def kod_v_rocniku(self):
|
||||||
if self.stav == 'zadany':
|
if self.stav == Problem.STAV_ZADANY or self.stav == Problem.STAV_VYRESENY:
|
||||||
name="{}.u{}".format(self.cislo_zadani.poradi,self.kod)
|
name="{}.u{}".format(self.cislo_zadani.poradi,self.kod)
|
||||||
if self.nadproblem:
|
if self.nadproblem:
|
||||||
return self.nadproblem.kod_v_rocniku+name
|
return self.nadproblem.kod_v_rocniku+name
|
||||||
return name
|
return name
|
||||||
|
logger.warning(f"K problému {self} byl vyžadován kód v ročníku, i když není zadaný ani vyřešený.")
|
||||||
return '<Není zadaný>'
|
return '<Není zadaný>'
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
|
|
@ -119,7 +119,9 @@
|
||||||
|
|
||||||
{% if user.je_org %}
|
{% if user.je_org %}
|
||||||
<div class='mam-org-only'>
|
<div class='mam-org-only'>
|
||||||
<a href='vysledkovka.tex' download>Výsledkovka ročníku (LaTeX, včetně neveřejných)</a>
|
<p><a href='vysledkovka.tex' download>Výsledkovka ročníku (LaTeX, včetně neveřejných)</a></p>
|
||||||
|
{# FIXME: Sice to sem asi nepatří sémanticky, ale bylo to nejjednodušší… #}
|
||||||
|
<p><a href='{% url 'seminar_rocnik_resitele_csv' rocnik=rocnik.rocnik %}' download>CSV export řešitelů</a></p>
|
||||||
<h2>Výsledková listina včetně neveřejných bodů</h2>
|
<h2>Výsledková listina včetně neveřejných bodů</h2>
|
||||||
{% include "vysledkovky/vysledkovka_rocnik_neverejna.html" %}
|
{% include "vysledkovky/vysledkovka_rocnik_neverejna.html" %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -36,6 +36,11 @@ urlpatterns = [
|
||||||
org_required(views.RocnikVysledkovkaView.as_view()),
|
org_required(views.RocnikVysledkovkaView.as_view()),
|
||||||
name='seminar_rocnik_vysledkovka'
|
name='seminar_rocnik_vysledkovka'
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
'rocnik/<int:rocnik>/resitele.csv',
|
||||||
|
org_required(views.resiteleRocnikuCsvExportView),
|
||||||
|
name='seminar_rocnik_resitele_csv'
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
'cislo/<int:rocnik>.<str:cislo>/vysledkovka.tex',
|
'cislo/<int:rocnik>.<str:cislo>/vysledkovka.tex',
|
||||||
org_required(views.CisloVysledkovkaView.as_view()),
|
org_required(views.CisloVysledkovkaView.as_view()),
|
||||||
|
|
|
@ -217,10 +217,10 @@ def aktivniResitele(cislo, pouze_letosni=False):
|
||||||
zacatek_rocniku = False
|
zacatek_rocniku = False
|
||||||
|
|
||||||
if not zacatek_rocniku:
|
if not zacatek_rocniku:
|
||||||
return resi_v_rocniku(letos, cislo)
|
return resi_v_rocniku(letos, cislo).filter(rok_maturity__gte=letos.druhy_rok())
|
||||||
else:
|
else:
|
||||||
# spojíme querysety s řešiteli loni a letos do daného čísla
|
# spojíme querysety s řešiteli loni a letos do daného čísla
|
||||||
return (resi_v_rocniku(loni) | resi_v_rocniku(letos, cislo)).distinct()
|
return (resi_v_rocniku(loni) | resi_v_rocniku(letos, cislo)).distinct().filter(rok_maturity__gte=letos.druhy_rok())
|
||||||
|
|
||||||
def viewMethodSwitch(get, post):
|
def viewMethodSwitch(get, post):
|
||||||
"""
|
"""
|
||||||
|
@ -309,6 +309,15 @@ def podproblemy_v_cislu(cislo, problemy=None, hlavni_problemy=None):
|
||||||
else:
|
else:
|
||||||
podproblemy[-1].append(problem)
|
podproblemy[-1].append(problem)
|
||||||
|
|
||||||
|
for podproblem in podproblemy.keys():
|
||||||
|
def int_or_zero(p):
|
||||||
|
try:
|
||||||
|
return int(p.kod)
|
||||||
|
except ValueError:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
podproblemy[podproblem] = sorted(podproblemy[podproblem], key=int_or_zero)
|
||||||
|
|
||||||
return podproblemy
|
return podproblemy
|
||||||
|
|
||||||
class TypDeadline(Enum):
|
class TypDeadline(Enum):
|
||||||
|
|
|
@ -390,6 +390,15 @@ class RocnikView(generic.DetailView):
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
def resiteleRocnikuCsvExportView(request, rocnik):
|
||||||
|
from personalni.views import dataResiteluCsvResponse
|
||||||
|
assert request.method in ('GET', 'HEAD')
|
||||||
|
return dataResiteluCsvResponse(
|
||||||
|
utils.resi_v_rocniku(
|
||||||
|
m.Rocnik.objects.get(rocnik=rocnik)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# FIXME: Pozor, výš je ještě jeden ProblemView!
|
# FIXME: Pozor, výš je ještě jeden ProblemView!
|
||||||
#class ProblemView(generic.DetailView):
|
#class ProblemView(generic.DetailView):
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
def april(req):
|
def april(req):
|
||||||
if 'X-April' in req.headers:
|
if 'X-April' in req.headers:
|
||||||
try:
|
try:
|
||||||
|
@ -12,3 +15,10 @@ def april(req):
|
||||||
return {'april': today.year}
|
return {'april': today.year}
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def rozliseni(request):
|
||||||
|
ltp = settings.LOCAL_TEST_PROD
|
||||||
|
if request.user.is_superuser and ltp == "prod":
|
||||||
|
ltp = "su" + ltp
|
||||||
|
return {"LOCAL_TEST_PROD": ltp}
|
||||||
|
|
||||||
|
|
|
@ -49,17 +49,21 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<p>Po kliknutí na políčko v záhlaví tabulky se u daného problému zobrazí (/skryje) detailní rozpis, za které podproblémy řešitelé dostali body.</p>
|
||||||
|
|
||||||
{# TODELETE #}
|
{# TODELETE #}
|
||||||
<script>
|
<script>
|
||||||
{% for p in problemy %}
|
{% for p in problemy %}
|
||||||
|
diplayed{{ forloop.counter }} = false;
|
||||||
$(".podproblem{{ forloop.counter }}").css("display", "none")
|
$(".podproblem{{ forloop.counter }}").css("display", "none")
|
||||||
$("#problem{{ forloop.counter }}")[0].addEventListener('mouseover', podproblem{{ forloop.counter }})
|
$("#problem{{ forloop.counter }}")[0].addEventListener('click', podproblem{{ forloop.counter }});
|
||||||
$("#problem{{ forloop.counter }}")[0].addEventListener('mouseout', podproblem{{ forloop.counter }}end)
|
|
||||||
function podproblem{{ forloop.counter }}(event) {
|
function podproblem{{ forloop.counter }}(event) {
|
||||||
$(".podproblem{{ forloop.counter }}").css("display", "")
|
diplayed{{ forloop.counter }} = !diplayed{{ forloop.counter }};
|
||||||
}
|
if (diplayed{{ forloop.counter }}) {
|
||||||
function podproblem{{ forloop.counter }}end(event) {
|
$(".podproblem{{ forloop.counter }}").css("display", "");
|
||||||
$(".podproblem{{ forloop.counter }}").css("display", "none")
|
} else {
|
||||||
|
$(".podproblem{{ forloop.counter }}").css("display", "none");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -196,6 +196,7 @@ def data_vysledkovky_rocniku(rocnik, jen_verejne=True):
|
||||||
end = time.time()
|
end = time.time()
|
||||||
print("Vysledkovka rocniku",end-start)
|
print("Vysledkovka rocniku",end-start)
|
||||||
|
|
||||||
|
radky_vysledkovky = [radek for radek in radky_vysledkovky if radek.body_rocnik > 0]
|
||||||
return radky_vysledkovky, cisla
|
return radky_vysledkovky, cisla
|
||||||
|
|
||||||
class RadekVysledkovkyCisla(object):
|
class RadekVysledkovkyCisla(object):
|
||||||
|
@ -451,6 +452,7 @@ def data_vysledkovky_cisla(cislo):
|
||||||
|
|
||||||
# vytahané informace předáváme do kontextu
|
# vytahané informace předáváme do kontextu
|
||||||
pt = [podproblemy[it.id] for it in temata_a_spol]+[podproblemy[-1]]
|
pt = [podproblemy[it.id] for it in temata_a_spol]+[podproblemy[-1]]
|
||||||
|
radky_vysledkovky = [radek for radek in radky_vysledkovky if radek.body_rocnik > 0]
|
||||||
return (
|
return (
|
||||||
radky_vysledkovky,
|
radky_vysledkovky,
|
||||||
temata_a_spol,
|
temata_a_spol,
|
||||||
|
|
Loading…
Reference in a new issue