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  | ||||
| 	@echo Downloading current version of flatpages from mamweb-prod. | ||||
| 	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" | ||||
| 	rsync -ave ssh mam-web@gimli.ms.mff.cuni.cz:/akce/mam/www/mamweb-prod/flat.json ./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_fixed.json data/flat.json | ||||
| 	@echo "Applying downloaded flatpages." | ||||
| 	./manage.py loaddata flat.json | ||||
| 	./manage.py loaddata data/flat.json | ||||
| 	@echo "Done." | ||||
| 
 | ||||
| # 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 | ||||
| 	rsync -av --delete /akce/mam/www/mamweb-prod/media/ ./media | ||||
| 
 | ||||
| # Sync 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 (with drop) test database with production database
 | ||||
| sync_test_db_aggressive: | ||||
| 	@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 | ||||
| 	@# 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 | ||||
| 	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. | ||||
| 
 | ||||
| # 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… | ||||
| 
 | ||||
| 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": { | ||||
| 			"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, | ||||
| 			"registration_required": false, | ||||
| 			"sites": [ | ||||
|  | @ -136,7 +136,7 @@ | |||
| 	}, | ||||
| 	{ | ||||
| 		"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, | ||||
| 			"registration_required": false, | ||||
| 			"sites": [ | ||||
|  | @ -178,5 +178,20 @@ | |||
| 		}, | ||||
| 		"model": "flatpages.flatpage", | ||||
| 		"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> | ||||
|       {% endwith %} | ||||
|     {% endif%} | ||||
|     <span id="nahoru" class="kotva_obrazku"></span> | ||||
|     <img src="{{obrazek.obrazek_stredni.url}}" | ||||
|          height="{{vyska}}" | ||||
|          width="{{sirka}}" | ||||
|          alt="{{obrazek.popis}}" | ||||
|          class="obrazek" id="nahoru"> | ||||
|          class="obrazek"> | ||||
|           | ||||
|     {% if obrazky_dalsi %} | ||||
|       {% with obrazky_dalsi|first as dalsi_obrazek %} | ||||
|  |  | |||
|  | @ -2,6 +2,8 @@ from django.contrib import admin | |||
| from reversion.admin import VersionAdmin | ||||
| from korektury.models import KorekturovanePDF | ||||
| 
 | ||||
| from django.core.mail import send_mail | ||||
| from django.urls import reverse | ||||
| 
 | ||||
| # Register your models here. | ||||
| class KorekturovanePDFAdmin(VersionAdmin): | ||||
|  | @ -16,11 +18,31 @@ class KorekturovanePDFAdmin(VersionAdmin): | |||
| 	fieldsets = [ | ||||
| 			(None, | ||||
| 				{'fields': | ||||
| 					['pdf', 'cas', 'org', 'stran', 'nazev', 'komentar']}), | ||||
| 					['pdf', 'cas', 'org', 'stran', 'nazev', 'komentar', 'poslat_mail']}), | ||||
| 			# (u'PDF',       {'fields': ['pdf']}), | ||||
| 				] | ||||
| 	list_display = ['nazev', 'cas', 'stran', 'org'] | ||||
| 	list_filter = [] | ||||
| 	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) | ||||
|  |  | |||
							
								
								
									
										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, | ||||
| 			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 | ||||
| 
 | ||||
|  |  | |||
|  | @ -89,6 +89,7 @@ form { | |||
| 	border: 4px solid red; | ||||
| 	border-radius: 10px; | ||||
| 	background-color: white; | ||||
| 	opacity: 80%; | ||||
| } | ||||
| .close-button{ | ||||
| 	background-color: yellow;	 | ||||
|  |  | |||
|  | @ -4,10 +4,11 @@ | |||
| <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 {% 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> | ||||
| 	{% 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 %} | ||||
|  |  | |||
|  | @ -22,9 +22,8 @@ | |||
| <ul> | ||||
|   {% for pdf in skupina.list %} | ||||
| 	  <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> | ||||
| 		<a href="/korektury/{{pdf.id}}">{{pdf.pdf.name}}</a> | ||||
| 		(k opravě: {{pdf.k_oprave_cnt}},  | ||||
| 		opraveno: {{pdf.opraveno_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/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/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.save() | ||||
| 			self.send_email_notification_komentar(op, autor, text) | ||||
| 			self.send_email_notification_komentar(op,autor) | ||||
| 		elif (action == 'del'): | ||||
| 			id = int(q.get('id')) | ||||
| 			op = Oprava.objects.get(id=id) | ||||
|  | @ -125,7 +125,7 @@ class KorekturyView(generic.TemplateView): | |||
| 			text = q.get('txt') | ||||
| 			kom = Komentar(oprava=op,autor=autor,text=text) | ||||
| 			kom.save() | ||||
| 			self.send_email_notification_komentar(op, autor, text) | ||||
| 			self.send_email_notification_komentar(op,autor) | ||||
| 		elif (action == 'update-comment'): | ||||
| 			id = int(q.get('id')) | ||||
| 			kom = Komentar.objects.get(id=id) | ||||
|  | @ -151,21 +151,25 @@ class KorekturyView(generic.TemplateView): | |||
| 		context['autor'] = autor | ||||
| 		return render(request, 'korektury/opraf.html',context) | ||||
| 
 | ||||
| 	def send_email_notification_komentar(self, oprava, autor, text): | ||||
| 		''' Rozesle e-mail pri pridani komentare, | ||||
| 			ktery obsahuje text komentare. | ||||
| 	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 = 'korekturovatko@mam.mff.cuni.cz' | ||||
| 		subject = 'Nová korektura od {} v {}'.format(autor, | ||||
| 													 oprava.pdf.nazev) | ||||
| 		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(text, odkaz) | ||||
| 				\nVaše korekturovátko\n".format(optext, odkaz) | ||||
| 
 | ||||
| 		# Prijemci e-mailu | ||||
| 		emails = set() | ||||
|  | @ -193,6 +197,9 @@ class KorekturyView(generic.TemplateView): | |||
| 
 | ||||
| 		if not settings.POSLI_MAILOVOU_NOTIFIKACI: | ||||
| 			print("Poslal bych upozornění na tyto adresy: ", " ".join(emails)) | ||||
| 			print("---- Upozornění:") | ||||
| 			print(text) | ||||
| 			print("---- Konec upozornění") | ||||
| 			return | ||||
| 
 | ||||
| 		send_mail(subject, text, from_email, list(emails)) | ||||
|  |  | |||
|  | @ -79,6 +79,7 @@ TEMPLATES = [ | |||
|                 'django.contrib.messages.context_processors.messages', | ||||
|                 'sekizai.context_processors.sekizai', | ||||
|                 'header_fotky.context_processors.vzhled', | ||||
|                 'various.context_processors.rozliseni', | ||||
|                 'various.context_processors.april', | ||||
|             ) | ||||
|         }, | ||||
|  |  | |||
|  | @ -28,7 +28,9 @@ INTERNAL_IPS = ['127.0.0.1'] | |||
| 
 | ||||
| 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 | ||||
| # https://docs.djangoproject.com/en/1.7/ref/settings/#databases | ||||
|  | @ -97,3 +99,4 @@ LOGGING = { | |||
| # E-maily posílat chceme, ale do terminálu :-) | ||||
| EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' | ||||
| 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 | ||||
| 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! | ||||
| # XXX: Je to pole, protože implementační detail backendu. | ||||
| TESTOVACI_EMAILOVA_KONFERENCE = ['betatest@mam.mff.cuni.cz'] | ||||
| LOCAL_TEST_PROD = "test" | ||||
|  |  | |||
|  | @ -1,3 +1,5 @@ | |||
| @import url("rozliseni.css"); | ||||
| 
 | ||||
| @font-face { | ||||
| font-family: 'OpenSans'; | ||||
| src: url("../fonts/OpenSans/OpenSans-Regular.ttf"); | ||||
|  | @ -1111,6 +1113,21 @@ div.zadani_termin .datum { | |||
|     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 %} | ||||
| <link rel="shortcut icon" href="{% static 'favicon.ico' %}" type="image/x-icon"> | ||||
| <script src="{% static 'js/jquery-1.11.1.js' %}"></script> | ||||
| <link href="{% static 'css/rozliseni.css' %}?version=1" rel="stylesheet"> | ||||
| {% endblock %} | ||||
| 
 | ||||
| {% block bodyclass %}{{ LOCAL_TEST_PROD }}web{% endblock %} | ||||
| 
 | ||||
| {% block branding %} | ||||
| <h1 id="site-name"><a href="/"> M&M GWP web </a></h1> | ||||
| {% endblock %} | ||||
|  |  | |||
|  | @ -36,7 +36,7 @@ | |||
|     {% block script %}{% endblock %} | ||||
| 
 | ||||
|   </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 %} | ||||
|       <div class="login-bar" > | ||||
|  |  | |||
|  | @ -49,7 +49,7 @@ urlpatterns = [ | |||
| 	path('comments_dj/', include('django_comments.urls')), | ||||
| 
 | ||||
| 	# REST API | ||||
| 	path('api/', include(router.urls)), | ||||
| #	path('api/', include(router.urls)), | ||||
| 
 | ||||
| ] | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ | |||
| 
 | ||||
| {% block content %} | ||||
| 
 | ||||
| <form method=get action=../odevzdavatko> | ||||
| <form method=get action=.> | ||||
| {{ filtr.resitele }} | ||||
| {{ filtr.problemy }} | ||||
| Od: {{ filtr.reseni_od }} | ||||
|  |  | |||
|  | @ -312,12 +312,15 @@ class PrehledOdevzdanychReseni(ListView): | |||
| 		resitel = m.Resitel.objects.filter(osoba__user=self.request.user).first() | ||||
| 		qs = super().get_queryset() | ||||
| 		qs = qs.filter(reseni__resitele__in=[resitel]) | ||||
| 		# Setřídíme podle času doručení řešení, aby se netřídily podle okamžiku vyrobení Hodnocení | ||||
| 		qs = qs.order_by('reseni__cas_doruceni') | ||||
| 		return qs | ||||
| 	 | ||||
| 	def get_context_data(self, *args, **kwargs): | ||||
| 		ctx = super().get_context_data(*args, **kwargs) | ||||
| 		# 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 :-/ | ||||
| 		# 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 = [] | ||||
| 		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))) | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ from django.views.generic.base import TemplateView | |||
| from django.contrib.auth.models import User, Permission, Group | ||||
| from django.contrib.auth.mixins import LoginRequiredMixin | ||||
| from django.db import transaction | ||||
| from django.http import HttpResponse | ||||
| 
 | ||||
| import seminar.models as s | ||||
| import seminar.models as m | ||||
|  | @ -14,6 +15,7 @@ from .forms import PrihlaskaForm, ProfileEditForm, PoMaturiteProfileEditForm | |||
| 
 | ||||
| from datetime import date | ||||
| import logging | ||||
| import csv | ||||
| 
 | ||||
| from seminar.views import formularOKView | ||||
| from various.autentizace.views import LoginView | ||||
|  | @ -304,3 +306,71 @@ def profilView(request): | |||
| 		return ResitelView.as_view()(request) | ||||
| 	else: | ||||
| 		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-flat-theme | ||||
| django-taggit | ||||
| django-autocomplete-light | ||||
| django-autocomplete-light>=3.9.0rc1 | ||||
| django-crispy-forms | ||||
| django-imagekit | ||||
| 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 -*- | ||||
| import logging | ||||
| import os | ||||
| from django.db import models | ||||
| 
 | ||||
| from .base import SeminarModelBase | ||||
|  | @ -65,3 +66,5 @@ class Obrazek(SeminarModelBase): | |||
| 		upload_to='obrazky/%Y/%m/%d/', blank=True, null=True) | ||||
| 
 | ||||
| 	# 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.core.cache import cache | ||||
| 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.functional import cached_property | ||||
| 
 | ||||
|  | @ -37,6 +38,13 @@ from .base import SeminarModelBase | |||
| 
 | ||||
| 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) | ||||
| class Rocnik(SeminarModelBase): | ||||
|  | @ -173,7 +181,7 @@ class Cislo(SeminarModelBase): | |||
| 		help_text='Neveřejná poznámka k číslu (plain text)') | ||||
| 
 | ||||
| 	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, | ||||
| 		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 | ||||
| 	@cached_property | ||||
| 	def kod_v_rocniku(self): | ||||
| 		if self.stav == 'zadany': | ||||
| 		if self.stav == Problem.STAV_ZADANY or self.stav == Problem.STAV_VYRESENY: | ||||
| 			if self.nadproblem: | ||||
| 				return self.nadproblem.kod_v_rocniku+".{}".format(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ý>' | ||||
| 
 | ||||
| #	def verejne(self): | ||||
|  | @ -467,10 +476,11 @@ class Tema(Problem): | |||
| 
 | ||||
| 	@cached_property | ||||
| 	def kod_v_rocniku(self): | ||||
| 		if self.stav == 'zadany': | ||||
| 		if self.stav == Problem.STAV_ZADANY or self.stav == Problem.STAV_VYRESENY: | ||||
| 			if self.nadproblem: | ||||
| 				return self.nadproblem.kod_v_rocniku+".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ý>' | ||||
| 
 | ||||
| 	def save(self, *args, **kwargs): | ||||
|  | @ -501,11 +511,12 @@ class Clanek(Problem): | |||
| 
 | ||||
| 	@cached_property | ||||
| 	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 | ||||
| #			if self.nadproblem: | ||||
| #				return self.nadproblem.kod_v_rocniku+".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ý>' | ||||
| 	 | ||||
| 	def node(self): | ||||
|  | @ -538,11 +549,12 @@ class Uloha(Problem): | |||
| 
 | ||||
| 	@cached_property | ||||
| 	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) | ||||
| 			if self.nadproblem: | ||||
| 				return self.nadproblem.kod_v_rocniku+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ý>' | ||||
| 
 | ||||
| 	def save(self, *args, **kwargs): | ||||
|  |  | |||
|  | @ -119,7 +119,9 @@ | |||
| 
 | ||||
|   {% if user.je_org %} | ||||
|     <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> | ||||
|         {% include "vysledkovky/vysledkovka_rocnik_neverejna.html" %} | ||||
|     </div> | ||||
|  |  | |||
|  | @ -36,6 +36,11 @@ urlpatterns = [ | |||
| 		org_required(views.RocnikVysledkovkaView.as_view()), | ||||
| 		name='seminar_rocnik_vysledkovka' | ||||
| 	), | ||||
| 	path( | ||||
| 		'rocnik/<int:rocnik>/resitele.csv', | ||||
| 		org_required(views.resiteleRocnikuCsvExportView), | ||||
| 		name='seminar_rocnik_resitele_csv' | ||||
| 	), | ||||
| 	path( | ||||
| 		'cislo/<int:rocnik>.<str:cislo>/vysledkovka.tex', | ||||
| 		org_required(views.CisloVysledkovkaView.as_view()), | ||||
|  |  | |||
|  | @ -217,10 +217,10 @@ def aktivniResitele(cislo, pouze_letosni=False): | |||
| 		zacatek_rocniku = False | ||||
| 
 | ||||
| 	if not zacatek_rocniku: | ||||
| 		return resi_v_rocniku(letos, cislo) | ||||
| 		return resi_v_rocniku(letos, cislo).filter(rok_maturity__gte=letos.druhy_rok()) | ||||
| 	else: | ||||
| 		# spojíme querysety s řešiteli loni a letos do daného čísla | ||||
| 		return (resi_v_rocniku(loni) | resi_v_rocniku(letos, cislo)).distinct() | ||||
| 		return (resi_v_rocniku(loni) | resi_v_rocniku(letos, cislo)).distinct().filter(rok_maturity__gte=letos.druhy_rok()) | ||||
| 
 | ||||
| def viewMethodSwitch(get, post): | ||||
| 	""" | ||||
|  | @ -309,6 +309,15 @@ def podproblemy_v_cislu(cislo, problemy=None, hlavni_problemy=None): | |||
| 		else: | ||||
| 			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 | ||||
| 
 | ||||
| class TypDeadline(Enum): | ||||
|  |  | |||
|  | @ -390,6 +390,15 @@ class RocnikView(generic.DetailView): | |||
| 
 | ||||
| 		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! | ||||
| #class ProblemView(generic.DetailView): | ||||
|  |  | |||
|  | @ -1,3 +1,6 @@ | |||
| from django.conf import settings | ||||
| 
 | ||||
| 
 | ||||
| def april(req): | ||||
| 	if 'X-April' in req.headers: | ||||
| 		try: | ||||
|  | @ -12,3 +15,10 @@ def april(req): | |||
| 		return {'april': today.year} | ||||
| 	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 %} | ||||
| </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 #} | ||||
| <script> | ||||
|     {% for p in problemy %} | ||||
|         diplayed{{ forloop.counter }} = false; | ||||
|         $(".podproblem{{ forloop.counter }}").css("display", "none") | ||||
|         $("#problem{{ forloop.counter }}")[0].addEventListener('mouseover', podproblem{{ forloop.counter }}) | ||||
|         $("#problem{{ forloop.counter }}")[0].addEventListener('mouseout', podproblem{{ forloop.counter }}end) | ||||
|         $("#problem{{ forloop.counter }}")[0].addEventListener('click', podproblem{{ forloop.counter }}); | ||||
|         function podproblem{{ forloop.counter }}(event) { | ||||
|             $(".podproblem{{ forloop.counter }}").css("display", "") | ||||
|         } | ||||
|         function podproblem{{ forloop.counter }}end(event) { | ||||
|             $(".podproblem{{ forloop.counter }}").css("display", "none") | ||||
|             diplayed{{ forloop.counter }} = !diplayed{{ forloop.counter }}; | ||||
|             if (diplayed{{ forloop.counter }}) { | ||||
|                 $(".podproblem{{ forloop.counter }}").css("display", ""); | ||||
|             } else { | ||||
|                 $(".podproblem{{ forloop.counter }}").css("display", "none"); | ||||
|             } | ||||
|         } | ||||
|     {% endfor %} | ||||
| </script> | ||||
|  |  | |||
|  | @ -196,6 +196,7 @@ def data_vysledkovky_rocniku(rocnik, jen_verejne=True): | |||
| 	end = time.time() | ||||
| 	print("Vysledkovka rocniku",end-start) | ||||
| 
 | ||||
| 	radky_vysledkovky = [radek for radek in radky_vysledkovky if radek.body_rocnik > 0] | ||||
| 	return radky_vysledkovky, cisla | ||||
| 
 | ||||
| class RadekVysledkovkyCisla(object): | ||||
|  | @ -451,6 +452,7 @@ def data_vysledkovky_cisla(cislo): | |||
| 
 | ||||
| 	# vytahané informace předáváme do kontextu | ||||
| 	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 ( | ||||
| 		radky_vysledkovky, | ||||
| 		temata_a_spol, | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Kateřina Č
						Kateřina Č