Merge branch 'master' into korekturovatko
This commit is contained in:
		
						commit
						9ccacaecb5
					
				
					 39 changed files with 860 additions and 278 deletions
				
			
		
							
								
								
									
										144
									
								
								data/groups.json
									
									
									
									
									
								
							
							
						
						
									
										144
									
								
								data/groups.json
									
									
									
									
									
								
							|  | @ -14,12 +14,12 @@ | ||||||
| 					"flatpage" | 					"flatpage" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"delete_flatpage", | 					"change_flatpage", | ||||||
| 					"flatpages", | 					"flatpages", | ||||||
| 					"flatpage" | 					"flatpage" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"change_flatpage", | 					"delete_flatpage", | ||||||
| 					"flatpages", | 					"flatpages", | ||||||
| 					"flatpage" | 					"flatpage" | ||||||
| 				], | 				], | ||||||
|  | @ -34,12 +34,12 @@ | ||||||
| 					"galerie" | 					"galerie" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"delete_galerie", | 					"change_galerie", | ||||||
| 					"galerie", | 					"galerie", | ||||||
| 					"galerie" | 					"galerie" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"change_galerie", | 					"delete_galerie", | ||||||
| 					"galerie", | 					"galerie", | ||||||
| 					"galerie" | 					"galerie" | ||||||
| 				], | 				], | ||||||
|  | @ -54,12 +54,12 @@ | ||||||
| 					"obrazek" | 					"obrazek" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"delete_obrazek", | 					"change_obrazek", | ||||||
| 					"galerie", | 					"galerie", | ||||||
| 					"obrazek" | 					"obrazek" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"change_obrazek", | 					"delete_obrazek", | ||||||
| 					"galerie", | 					"galerie", | ||||||
| 					"obrazek" | 					"obrazek" | ||||||
| 				], | 				], | ||||||
|  | @ -104,12 +104,12 @@ | ||||||
| 					"komentar" | 					"komentar" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"delete_komentar", | 					"change_komentar", | ||||||
| 					"korektury", | 					"korektury", | ||||||
| 					"komentar" | 					"komentar" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"change_komentar", | 					"delete_komentar", | ||||||
| 					"korektury", | 					"korektury", | ||||||
| 					"komentar" | 					"komentar" | ||||||
| 				], | 				], | ||||||
|  | @ -124,12 +124,12 @@ | ||||||
| 					"korekturovanepdf" | 					"korekturovanepdf" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"delete_korekturovanepdf", | 					"change_korekturovanepdf", | ||||||
| 					"korektury", | 					"korektury", | ||||||
| 					"korekturovanepdf" | 					"korekturovanepdf" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"change_korekturovanepdf", | 					"delete_korekturovanepdf", | ||||||
| 					"korektury", | 					"korektury", | ||||||
| 					"korekturovanepdf" | 					"korekturovanepdf" | ||||||
| 				], | 				], | ||||||
|  | @ -144,12 +144,12 @@ | ||||||
| 					"oprava" | 					"oprava" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"delete_oprava", | 					"change_oprava", | ||||||
| 					"korektury", | 					"korektury", | ||||||
| 					"oprava" | 					"oprava" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"change_oprava", | 					"delete_oprava", | ||||||
| 					"korektury", | 					"korektury", | ||||||
| 					"oprava" | 					"oprava" | ||||||
| 				], | 				], | ||||||
|  | @ -164,12 +164,12 @@ | ||||||
| 					"novinky" | 					"novinky" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"delete_novinky", | 					"change_novinky", | ||||||
| 					"novinky", | 					"novinky", | ||||||
| 					"novinky" | 					"novinky" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"change_novinky", | 					"delete_novinky", | ||||||
| 					"novinky", | 					"novinky", | ||||||
| 					"novinky" | 					"novinky" | ||||||
| 				], | 				], | ||||||
|  | @ -204,12 +204,12 @@ | ||||||
| 					"prijemce" | 					"prijemce" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"delete_prijemce", | 					"change_prijemce", | ||||||
| 					"personalni", | 					"personalni", | ||||||
| 					"prijemce" | 					"prijemce" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"change_prijemce", | 					"delete_prijemce", | ||||||
| 					"personalni", | 					"personalni", | ||||||
| 					"prijemce" | 					"prijemce" | ||||||
| 				], | 				], | ||||||
|  | @ -234,12 +234,12 @@ | ||||||
| 					"skola" | 					"skola" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"delete_skola", | 					"change_skola", | ||||||
| 					"personalni", | 					"personalni", | ||||||
| 					"skola" | 					"skola" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"change_skola", | 					"delete_skola", | ||||||
| 					"personalni", | 					"personalni", | ||||||
| 					"skola" | 					"skola" | ||||||
| 				], | 				], | ||||||
|  | @ -248,38 +248,28 @@ | ||||||
| 					"personalni", | 					"personalni", | ||||||
| 					"skola" | 					"skola" | ||||||
| 				], | 				], | ||||||
| 				[ |  | ||||||
| 					"add_hlasovani", |  | ||||||
| 					"prednasky", |  | ||||||
| 					"hlasovani" |  | ||||||
| 				], |  | ||||||
| 				[ |  | ||||||
| 					"delete_hlasovani", |  | ||||||
| 					"prednasky", |  | ||||||
| 					"hlasovani" |  | ||||||
| 				], |  | ||||||
| 				[ |  | ||||||
| 					"change_hlasovani", |  | ||||||
| 					"prednasky", |  | ||||||
| 					"hlasovani" |  | ||||||
| 				], |  | ||||||
| 				[ | 				[ | ||||||
| 					"view_hlasovani", | 					"view_hlasovani", | ||||||
| 					"prednasky", | 					"prednasky", | ||||||
| 					"hlasovani" | 					"hlasovani" | ||||||
| 				], | 				], | ||||||
|  | 				[ | ||||||
|  | 					"view_hlasovanioznalostech", | ||||||
|  | 					"prednasky", | ||||||
|  | 					"hlasovanioznalostech" | ||||||
|  | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"add_prednaska", | 					"add_prednaska", | ||||||
| 					"prednasky", | 					"prednasky", | ||||||
| 					"prednaska" | 					"prednaska" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"delete_prednaska", | 					"change_prednaska", | ||||||
| 					"prednasky", | 					"prednasky", | ||||||
| 					"prednaska" | 					"prednaska" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"change_prednaska", | 					"delete_prednaska", | ||||||
| 					"prednasky", | 					"prednasky", | ||||||
| 					"prednaska" | 					"prednaska" | ||||||
| 				], | 				], | ||||||
|  | @ -294,12 +284,12 @@ | ||||||
| 					"seznam" | 					"seznam" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"delete_seznam", | 					"change_seznam", | ||||||
| 					"prednasky", | 					"prednasky", | ||||||
| 					"seznam" | 					"seznam" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"change_seznam", | 					"delete_seznam", | ||||||
| 					"prednasky", | 					"prednasky", | ||||||
| 					"seznam" | 					"seznam" | ||||||
| 				], | 				], | ||||||
|  | @ -308,18 +298,38 @@ | ||||||
| 					"prednasky", | 					"prednasky", | ||||||
| 					"seznam" | 					"seznam" | ||||||
| 				], | 				], | ||||||
|  | 				[ | ||||||
|  | 					"add_znalost", | ||||||
|  | 					"prednasky", | ||||||
|  | 					"znalost" | ||||||
|  | 				], | ||||||
|  | 				[ | ||||||
|  | 					"change_znalost", | ||||||
|  | 					"prednasky", | ||||||
|  | 					"znalost" | ||||||
|  | 				], | ||||||
|  | 				[ | ||||||
|  | 					"delete_znalost", | ||||||
|  | 					"prednasky", | ||||||
|  | 					"znalost" | ||||||
|  | 				], | ||||||
|  | 				[ | ||||||
|  | 					"view_znalost", | ||||||
|  | 					"prednasky", | ||||||
|  | 					"znalost" | ||||||
|  | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"add_konfera", | 					"add_konfera", | ||||||
| 					"soustredeni", | 					"soustredeni", | ||||||
| 					"konfera" | 					"konfera" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"delete_konfera", | 					"change_konfera", | ||||||
| 					"soustredeni", | 					"soustredeni", | ||||||
| 					"konfera" | 					"konfera" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"change_konfera", | 					"delete_konfera", | ||||||
| 					"soustredeni", | 					"soustredeni", | ||||||
| 					"konfera" | 					"konfera" | ||||||
| 				], | 				], | ||||||
|  | @ -334,12 +344,12 @@ | ||||||
| 					"konfery_ucastnici" | 					"konfery_ucastnici" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"delete_konfery_ucastnici", | 					"change_konfery_ucastnici", | ||||||
| 					"soustredeni", | 					"soustredeni", | ||||||
| 					"konfery_ucastnici" | 					"konfery_ucastnici" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"change_konfery_ucastnici", | 					"delete_konfery_ucastnici", | ||||||
| 					"soustredeni", | 					"soustredeni", | ||||||
| 					"konfery_ucastnici" | 					"konfery_ucastnici" | ||||||
| 				], | 				], | ||||||
|  | @ -354,12 +364,12 @@ | ||||||
| 					"soustredeni" | 					"soustredeni" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"delete_soustredeni", | 					"change_soustredeni", | ||||||
| 					"soustredeni", | 					"soustredeni", | ||||||
| 					"soustredeni" | 					"soustredeni" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"change_soustredeni", | 					"delete_soustredeni", | ||||||
| 					"soustredeni", | 					"soustredeni", | ||||||
| 					"soustredeni" | 					"soustredeni" | ||||||
| 				], | 				], | ||||||
|  | @ -374,12 +384,12 @@ | ||||||
| 					"soustredeni_organizatori" | 					"soustredeni_organizatori" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"delete_soustredeni_organizatori", | 					"change_soustredeni_organizatori", | ||||||
| 					"soustredeni", | 					"soustredeni", | ||||||
| 					"soustredeni_organizatori" | 					"soustredeni_organizatori" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"change_soustredeni_organizatori", | 					"delete_soustredeni_organizatori", | ||||||
| 					"soustredeni", | 					"soustredeni", | ||||||
| 					"soustredeni_organizatori" | 					"soustredeni_organizatori" | ||||||
| 				], | 				], | ||||||
|  | @ -394,12 +404,12 @@ | ||||||
| 					"soustredeni_ucastnici" | 					"soustredeni_ucastnici" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"delete_soustredeni_ucastnici", | 					"change_soustredeni_ucastnici", | ||||||
| 					"soustredeni", | 					"soustredeni", | ||||||
| 					"soustredeni_ucastnici" | 					"soustredeni_ucastnici" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"change_soustredeni_ucastnici", | 					"delete_soustredeni_ucastnici", | ||||||
| 					"soustredeni", | 					"soustredeni", | ||||||
| 					"soustredeni_ucastnici" | 					"soustredeni_ucastnici" | ||||||
| 				], | 				], | ||||||
|  | @ -414,12 +424,12 @@ | ||||||
| 					"tag" | 					"tag" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"delete_tag", | 					"change_tag", | ||||||
| 					"taggit", | 					"taggit", | ||||||
| 					"tag" | 					"tag" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"change_tag", | 					"delete_tag", | ||||||
| 					"taggit", | 					"taggit", | ||||||
| 					"tag" | 					"tag" | ||||||
| 				], | 				], | ||||||
|  | @ -434,12 +444,12 @@ | ||||||
| 					"taggeditem" | 					"taggeditem" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"delete_taggeditem", | 					"change_taggeditem", | ||||||
| 					"taggit", | 					"taggit", | ||||||
| 					"taggeditem" | 					"taggeditem" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"change_taggeditem", | 					"delete_taggeditem", | ||||||
| 					"taggit", | 					"taggit", | ||||||
| 					"taggeditem" | 					"taggeditem" | ||||||
| 				], | 				], | ||||||
|  | @ -454,12 +464,12 @@ | ||||||
| 					"cislo" | 					"cislo" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"delete_cislo", | 					"change_cislo", | ||||||
| 					"tvorba", | 					"tvorba", | ||||||
| 					"cislo" | 					"cislo" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"change_cislo", | 					"delete_cislo", | ||||||
| 					"tvorba", | 					"tvorba", | ||||||
| 					"cislo" | 					"cislo" | ||||||
| 				], | 				], | ||||||
|  | @ -474,12 +484,12 @@ | ||||||
| 					"clanek" | 					"clanek" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"delete_clanek", | 					"change_clanek", | ||||||
| 					"tvorba", | 					"tvorba", | ||||||
| 					"clanek" | 					"clanek" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"change_clanek", | 					"delete_clanek", | ||||||
| 					"tvorba", | 					"tvorba", | ||||||
| 					"clanek" | 					"clanek" | ||||||
| 				], | 				], | ||||||
|  | @ -509,12 +519,12 @@ | ||||||
| 					"pohadka" | 					"pohadka" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"delete_pohadka", | 					"change_pohadka", | ||||||
| 					"tvorba", | 					"tvorba", | ||||||
| 					"pohadka" | 					"pohadka" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"change_pohadka", | 					"delete_pohadka", | ||||||
| 					"tvorba", | 					"tvorba", | ||||||
| 					"pohadka" | 					"pohadka" | ||||||
| 				], | 				], | ||||||
|  | @ -529,12 +539,12 @@ | ||||||
| 					"problem" | 					"problem" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"delete_problem", | 					"change_problem", | ||||||
| 					"tvorba", | 					"tvorba", | ||||||
| 					"problem" | 					"problem" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"change_problem", | 					"delete_problem", | ||||||
| 					"tvorba", | 					"tvorba", | ||||||
| 					"problem" | 					"problem" | ||||||
| 				], | 				], | ||||||
|  | @ -549,12 +559,12 @@ | ||||||
| 					"rocnik" | 					"rocnik" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"delete_rocnik", | 					"change_rocnik", | ||||||
| 					"tvorba", | 					"tvorba", | ||||||
| 					"rocnik" | 					"rocnik" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"change_rocnik", | 					"delete_rocnik", | ||||||
| 					"tvorba", | 					"tvorba", | ||||||
| 					"rocnik" | 					"rocnik" | ||||||
| 				], | 				], | ||||||
|  | @ -569,12 +579,12 @@ | ||||||
| 					"tema" | 					"tema" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"delete_tema", | 					"change_tema", | ||||||
| 					"tvorba", | 					"tvorba", | ||||||
| 					"tema" | 					"tema" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"change_tema", | 					"delete_tema", | ||||||
| 					"tvorba", | 					"tvorba", | ||||||
| 					"tema" | 					"tema" | ||||||
| 				], | 				], | ||||||
|  | @ -589,12 +599,12 @@ | ||||||
| 					"uloha" | 					"uloha" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"delete_uloha", | 					"change_uloha", | ||||||
| 					"tvorba", | 					"tvorba", | ||||||
| 					"uloha" | 					"uloha" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"change_uloha", | 					"delete_uloha", | ||||||
| 					"tvorba", | 					"tvorba", | ||||||
| 					"uloha" | 					"uloha" | ||||||
| 				], | 				], | ||||||
|  | @ -609,12 +619,12 @@ | ||||||
| 					"nastaveni" | 					"nastaveni" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"delete_nastaveni", | 					"change_nastaveni", | ||||||
| 					"various", | 					"various", | ||||||
| 					"nastaveni" | 					"nastaveni" | ||||||
| 				], | 				], | ||||||
| 				[ | 				[ | ||||||
| 					"change_nastaveni", | 					"delete_nastaveni", | ||||||
| 					"various", | 					"various", | ||||||
| 					"nastaveni" | 					"nastaveni" | ||||||
| 				], | 				], | ||||||
|  |  | ||||||
|  | @ -36,6 +36,7 @@ extensions = [ | ||||||
|     'sphinx.ext.intersphinx', |     'sphinx.ext.intersphinx', | ||||||
|     'sphinx.ext.autosectionlabel', |     'sphinx.ext.autosectionlabel', | ||||||
|     'myst_parser', |     'myst_parser', | ||||||
|  | 	'sphinxcontrib_django', | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| # Add any paths that contain templates here, relative to this directory. | # Add any paths that contain templates here, relative to this directory. | ||||||
|  |  | ||||||
|  | @ -95,7 +95,7 @@ function safe_checkout_branch { | ||||||
| 		echo >&2 "Změna v $SCRIPT, prosím pullni manuálně" | 		echo >&2 "Změna v $SCRIPT, prosím pullni manuálně" | ||||||
| 		exit 1 | 		exit 1 | ||||||
| 	fi | 	fi | ||||||
| 	git checkout "$BRANCH" | 	git checkout "$BRANCH" -- | ||||||
| 	git pull | 	git pull | ||||||
| 	git clean -f | 	git clean -f | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -57,6 +57,7 @@ DOBA_ODHLASENI_PRI_ZASKRTNUTI_NEODHLASOVAT = 365 * 24 * 3600  # rok | ||||||
| CSRF_FAILURE_VIEW = 'various.views.csrf.csrf_error' | CSRF_FAILURE_VIEW = 'various.views.csrf.csrf_error' | ||||||
| 
 | 
 | ||||||
| # Modules configuration | # Modules configuration | ||||||
|  | FORM_RENDERER = "django.forms.renderers.DjangoDivFormRenderer" | ||||||
| 
 | 
 | ||||||
| AUTHENTICATION_BACKENDS = ( | AUTHENTICATION_BACKENDS = ( | ||||||
| 	'django.contrib.auth.backends.ModelBackend', | 	'django.contrib.auth.backends.ModelBackend', | ||||||
|  |  | ||||||
|  | @ -435,6 +435,7 @@ body.localweb, body.testweb, body.suprodweb { | ||||||
| 		height: 100%; | 		height: 100%; | ||||||
| 		top: 0; | 		top: 0; | ||||||
| 		z-index: -1000; | 		z-index: -1000; | ||||||
|  | 		opacity: 0.7; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	&:before { left: 0; } | 	&:before { left: 0; } | ||||||
|  |  | ||||||
|  | @ -503,5 +503,10 @@ label[for=id_skola] { | ||||||
| 	font-weight: bold; | 	font-weight: bold; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /* Přednášky */ | ||||||
|  | .textznalosti, .textprednasky { | ||||||
|  | 	font-style: italic; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /*******************/ | /*******************/ | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										20
									
								
								mamweb/static/css/rozliseni.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								mamweb/static/css/rozliseni.css
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | ||||||
|  | /**** ROZLIŠENÍ MEZI LOKÁLNÍM, TESTOVACÍM A PRODUKČNÍM WEBEM ****/ | ||||||
|  | body.localweb, body.testweb, body.suprodweb { | ||||||
|  | 	&:before, &:after { | ||||||
|  | 		content: ""; | ||||||
|  | 		position: fixed; | ||||||
|  | 		width: 20px; | ||||||
|  | 		height: 100%; | ||||||
|  | 		top: 0; | ||||||
|  | 		z-index: -1000; | ||||||
|  | 		opacity: 0.7; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	&:before { left: 0; } | ||||||
|  | 	&:after { right: 0; } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | body.localweb { &:before, &:after { background: greenyellow; } } | ||||||
|  | body.testweb { &:before, &:after { background: darkorange; } } | ||||||
|  | body.suprodweb { &:before, &:after { background: red; } } | ||||||
|  | /****************************************************************/ | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| # Tento soubor slouží pouze pro shell a podobné. Nikde neimportovat v kódu! | # Tento soubor slouží pouze pro shell a podobné. Nikde neimportovat v kódu! | ||||||
| 
 | 
 | ||||||
| print("Naimportoval jsi `seminar.models`. Pevně věřím, že to nebylo nikde v kódu. Díky.") | print("Naimportoval jsi `mamweb.vsechno`. Pevně věřím, že to nebylo nikde v kódu. Díky.") | ||||||
| 
 | 
 | ||||||
| from galerie.models import * | from galerie.models import * | ||||||
| from header_fotky.models import * | from header_fotky.models import * | ||||||
|  |  | ||||||
|  | @ -65,6 +65,9 @@ class Reseni(SeminarModelBase): | ||||||
| 	def absolute_url(self): | 	def absolute_url(self): | ||||||
| 		return "https://" + str(get_current_site(None)) + self.verejne_url() | 		return "https://" + str(get_current_site(None)) + self.verejne_url() | ||||||
| 
 | 
 | ||||||
|  | 	def resitel_url(self): | ||||||
|  | 		return f'https://{get_current_site(None)}{reverse_lazy("odevzdavatko_resitel_reseni", args=[self.id])}' | ||||||
|  | 
 | ||||||
| 	# má OneToOneField s: | 	# má OneToOneField s: | ||||||
| 	# Konfera | 	# Konfera | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -191,7 +191,7 @@ Sloupce: | ||||||
|     </ul> |     </ul> | ||||||
|   </li> |   </li> | ||||||
|   <li>Pokud nemáš důvod, deadline neměň. Sloupeček s deadlinem znamená, do kterého deadlinu se započítají body (nemusí se shodovat s deadlinem řešení).</li> |   <li>Pokud nemáš důvod, deadline neměň. Sloupeček s deadlinem znamená, do kterého deadlinu se započítají body (nemusí se shodovat s deadlinem řešení).</li> | ||||||
|   <li>Poslední sloupec je na zpětnou vazbu řešiteli, tedy (na rozdíl od Neveřejné poznámky, která je určena pro synchronizaci orgů) ji uvidí řešitelé. Zatím jen pasivně (nechodí e-mail). Pohled řešitele si můžete prohlédnout <a href="{% url 'odevzdavatko_resitel_reseni' reseni.id %}">zde</a>. Pokud chcete z nějakého důvodu napsat řešitelům e-mail, klikněte na „Poslat mail všem řešitelům“.</li> |   <li>Poslední sloupec je na zpětnou vazbu řešiteli, tedy (na rozdíl od Neveřejné poznámky, která je určena pro synchronizaci orgů) ji uvidí řešitelé. Změníte-li u nějakého hodnocení toto políčko, řešitel bude upozorněn emailem, pokud si tuto možnost nevypl ve svém profilu. Pohled řešitele si můžete prohlédnout <a href="{% url 'odevzdavatko_resitel_reseni' reseni.id %}">zde</a>. Pokud chcete z nějakého důvodu napsat řešitelům e-mail, klikněte na „Poslat mail všem řešitelům“.</li> | ||||||
| </ol> | </ol> | ||||||
| 
 | 
 | ||||||
| Další poznámky | Další poznámky | ||||||
|  |  | ||||||
|  | @ -222,6 +222,17 @@ class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixi | ||||||
| 		ctx["problem_id"] = self.kwargs['problem'] | 		ctx["problem_id"] = self.kwargs['problem'] | ||||||
| 		return ctx | 		return ctx | ||||||
| 
 | 
 | ||||||
|  | HODNOCENI_INITIAL_DATA = [ | ||||||
|  | 	"problem", | ||||||
|  | 	"body", | ||||||
|  | 	"body_celkem", | ||||||
|  | 	"body_neprepocitane", | ||||||
|  | 	"body_neprepocitane_celkem", | ||||||
|  | 	"body_max", | ||||||
|  | 	"body_neprepocitane_max", | ||||||
|  | 	"deadline_body", | ||||||
|  | 	"feedback", | ||||||
|  | ] | ||||||
| ## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex | ## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex | ||||||
| class DetailReseniView(DetailView): | class DetailReseniView(DetailView): | ||||||
| 	""" Náhled na řešení. Editace je v :py:class:`EditReseniView`. """ | 	""" Náhled na řešení. Editace je v :py:class:`EditReseniView`. """ | ||||||
|  | @ -232,18 +243,7 @@ class DetailReseniView(DetailView): | ||||||
| 		self.reseni = get_object_or_404(Reseni, id=self.kwargs['pk']) | 		self.reseni = get_object_or_404(Reseni, id=self.kwargs['pk']) | ||||||
| 		result = [] # Slovníky s klíči problem, body, deadline_body -- initial data pro f.OhodnoceniReseniFormSet | 		result = [] # Slovníky s klíči problem, body, deadline_body -- initial data pro f.OhodnoceniReseniFormSet | ||||||
| 		for hodn in Hodnoceni.objects.filter(reseni=self.reseni): | 		for hodn in Hodnoceni.objects.filter(reseni=self.reseni): | ||||||
| 			seznam_atributu = [ | 			result.append({attr: getattr(hodn, attr) for attr in HODNOCENI_INITIAL_DATA}) | ||||||
| 				"problem", |  | ||||||
| 				"body", |  | ||||||
| 				"body_celkem", |  | ||||||
| 				"body_neprepocitane", |  | ||||||
| 				"body_neprepocitane_celkem", |  | ||||||
| 				"body_max", |  | ||||||
| 				"body_neprepocitane_max", |  | ||||||
| 				"deadline_body", |  | ||||||
| 				"feedback", |  | ||||||
| 			] |  | ||||||
| 			result.append({attr: getattr(hodn, attr) for attr in seznam_atributu}) |  | ||||||
| 		return result | 		return result | ||||||
| 
 | 
 | ||||||
| 	def get_context_data(self, **kw): | 	def get_context_data(self, **kw): | ||||||
|  | @ -291,9 +291,9 @@ def hodnoceniReseniView(request, pk, *args, **kwargs): | ||||||
| 	reseni = get_object_or_404(Reseni, pk=pk) | 	reseni = get_object_or_404(Reseni, pk=pk) | ||||||
| 	success_url = reverse('odevzdavatko_detail_reseni', kwargs={'pk': pk}) | 	success_url = reverse('odevzdavatko_detail_reseni', kwargs={'pk': pk}) | ||||||
| 
 | 
 | ||||||
| 	# FIXME: Použit initial i tady a nebastlit hodnocení tak nízkoúrovňově | 	formset = f.OhodnoceniReseniFormSet(request.POST, initial=[ | ||||||
| 	# Also: https://docs.djangoproject.com/en/2.2/topics/forms/modelforms/#django.forms.ModelForm | 		{k: getattr(h, k) for k in HODNOCENI_INITIAL_DATA} for h in Hodnoceni.objects.filter(reseni=reseni) | ||||||
| 	formset = f.OhodnoceniReseniFormSet(request.POST) | 	]) | ||||||
| 	poznamka_form = f.PoznamkaReseniForm(request.POST, instance=reseni) | 	poznamka_form = f.PoznamkaReseniForm(request.POST, instance=reseni) | ||||||
| 	# TODO: Napsat validaci formuláře a formsetu | 	# TODO: Napsat validaci formuláře a formsetu | ||||||
| 	if not (formset.is_valid() and poznamka_form.is_valid()): | 	if not (formset.is_valid() and poznamka_form.is_valid()): | ||||||
|  | @ -309,7 +309,9 @@ def hodnoceniReseniView(request, pk, *args, **kwargs): | ||||||
| 		qs.delete() | 		qs.delete() | ||||||
| 
 | 
 | ||||||
| 		# Vyrobíme nová podle formsetu | 		# Vyrobíme nová podle formsetu | ||||||
|  | 		notifikace = False | ||||||
| 		for form in formset: | 		for form in formset: | ||||||
|  | 			notifikace |= 'feedback' in form.changed_data | ||||||
| 			data_for_hodnoceni = form.cleaned_data | 			data_for_hodnoceni = form.cleaned_data | ||||||
| 			data_for_body = data_for_hodnoceni.copy() | 			data_for_body = data_for_hodnoceni.copy() | ||||||
| 			del(data_for_hodnoceni["body_celkem"]) | 			del(data_for_hodnoceni["body_celkem"]) | ||||||
|  | @ -320,16 +322,44 @@ def hodnoceniReseniView(request, pk, *args, **kwargs): | ||||||
| 					**form.cleaned_data, | 					**form.cleaned_data, | ||||||
| 					) | 					) | ||||||
| 			logger.info(f"Creating Hodnoceni: {hodnoceni}") | 			logger.info(f"Creating Hodnoceni: {hodnoceni}") | ||||||
|  | 			# FIXME následující kód má velmi vysokou šanci se rozbít, vymyslet, jak to udělat jinak | ||||||
| 			zmeny_bodu = [it for it in form.changed_data if it.startswith("body")] | 			zmeny_bodu = [it for it in form.changed_data if it.startswith("body")] | ||||||
| 			if len(zmeny_bodu) == 1: | 			if len(zmeny_bodu) != 0: | ||||||
| 				hodnoceni.__setattr__(zmeny_bodu[0], data_for_body[zmeny_bodu[0]]) | 				body_nastaveny: None | tuple[str, object] = None | ||||||
| 			# > jedna změna je špatně, ale 4 "změny" znamenají že nebylo nic zadáno | 				def nastav_body(jake, na_kolik): | ||||||
| 			if len(zmeny_bodu) > 1 and len(zmeny_bodu) != 4 and len(zmeny_bodu) != 2: | 					nonlocal body_nastaveny | ||||||
| 				# 4 znamená vše už vyplněno a nic nezměněno, 2 znamená předvyplnili se součty a nic se nezměnilo | 					if body_nastaveny is not None: | ||||||
| 				logger.warning(f"Hodnocení {hodnoceni} mělo mít nastavené víc různých bodů: {zmeny_bodu}. Nastavuji -0.1.") | 						logger.warning(f"Hodnocení {hodnoceni} s id {hodnoceni.id} k řešení {reseni.id} mělo mít nastavené kromě {body_nastaveny[0]} na {body_nastaveny[1]} ještě další body: {jake} na {na_kolik}. Nastavuji -0.1.") | ||||||
| 				hodnoceni.body = -0.1 | 						hodnoceni.body = -0.1 | ||||||
|  | 					else: | ||||||
|  | 						body_nastaveny = (jake, na_kolik) | ||||||
|  | 						hodnoceni.__setattr__(jake, na_kolik) | ||||||
|  | 
 | ||||||
|  | 				for key, value in data_for_body.items(): | ||||||
|  | 					if key.startswith("body") and value is not None: | ||||||
|  | 						nastav_body(key, value) | ||||||
|  | 
 | ||||||
|  | 				# Něco se změnilo, ale nic není nastavené = něco bylo smazáno | ||||||
|  | 				if body_nastaveny is None: | ||||||
|  | 					hodnoceni.body = None | ||||||
| 			hodnoceni.save() | 			hodnoceni.save() | ||||||
| 
 | 
 | ||||||
|  | 		adresati = reseni.resitele.filter(upozornovat_na_opravy_reseni=True).values_list('osoba__email', flat=True) | ||||||
|  | 		if notifikace and adresati: | ||||||
|  | 			email = EmailMessage( | ||||||
|  | 					subject='Změna hodnocení odevzdaného řešení', | ||||||
|  | 					body=f"""Milá řešitelko, milý řešiteli, | ||||||
|  | 
 | ||||||
|  | došlo ke změně zpětné vazby k Tebou odevzdanému řešení. Zobrazit si ji můžeš na {reseni.resitel_url()}. | ||||||
|  | 
 | ||||||
|  | Tvoji organizátoři M&M | ||||||
|  | --- | ||||||
|  | Nechceš-li tato upozornění dostávat, můžeš si to nastavit ve svém profilu.""", | ||||||
|  | 					from_email='odevzdavatko@mam.mff.cuni.cz', | ||||||
|  | 					bcc=adresati, | ||||||
|  | 					) | ||||||
|  | 			email.send() | ||||||
|  | 
 | ||||||
| 	return redirect(success_url) | 	return redirect(success_url) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -71,6 +71,8 @@ class UdajeForm(forms.Form): | ||||||
| 	zasilat_cislo_emailem = forms.BooleanField(label='Chci dostávat e-mailem upozornění na vydání nového čísla', required=False, initial=True) | 	zasilat_cislo_emailem = forms.BooleanField(label='Chci dostávat e-mailem upozornění na vydání nového čísla', required=False, initial=True) | ||||||
| 	spam = forms.BooleanField(label='Souhlasím se zasíláním propagačních materiálů od MFF UK', required=False) | 	spam = forms.BooleanField(label='Souhlasím se zasíláním propagačních materiálů od MFF UK', required=False) | ||||||
| 
 | 
 | ||||||
|  | 	upozornovat_na_opravy_reseni = forms.BooleanField(label='Chci dostávat emailová upozornění na změnu zpětné vazby k mým řešením', required=False, initial=True) | ||||||
|  | 
 | ||||||
| 	def clean_prezdivka_resitele(self): | 	def clean_prezdivka_resitele(self): | ||||||
| 		prezdivka_resitele = self.cleaned_data.get('prezdivka_resitele') | 		prezdivka_resitele = self.cleaned_data.get('prezdivka_resitele') | ||||||
| 		if prezdivka_resitele == '': | 		if prezdivka_resitele == '': | ||||||
|  |  | ||||||
							
								
								
									
										22
									
								
								personalni/migrations/0018_resitel_upozorneni.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								personalni/migrations/0018_resitel_upozorneni.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2024-12-03 19:08 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations, models | ||||||
|  | 
 | ||||||
|  | def vypnuti_upozorneni_na_opravy_reseni(apps, schema_editor): | ||||||
|  |     Resitel = apps.get_model('personalni', 'Resitel') | ||||||
|  |     Resitel.objects.update(upozorneni=False) | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('personalni', '0017_odstrel_treenode_post'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name='resitel', | ||||||
|  |             name='upozorneni', | ||||||
|  |             field=models.BooleanField(default=True, verbose_name='zasílat upozornění na změnu zpětné vazby k řešení emailem'), | ||||||
|  |         ), | ||||||
|  |         migrations.RunPython(vypnuti_upozorneni_na_opravy_reseni), | ||||||
|  |     ] | ||||||
|  | @ -0,0 +1,18 @@ | ||||||
|  | # Generated by Django 4.2.18 on 2025-01-14 19:48 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('personalni', '0018_resitel_upozorneni'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.RenameField( | ||||||
|  |             model_name='resitel', | ||||||
|  |             old_name='upozorneni', | ||||||
|  |             new_name='upozornovat_na_opravy_reseni', | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
|  | @ -250,6 +250,8 @@ class Resitel(SeminarModelBase): | ||||||
| 	poznamka = models.TextField('neveřejná poznámka', blank=True, | 	poznamka = models.TextField('neveřejná poznámka', blank=True, | ||||||
| 		help_text='Neveřejná poznámka k řešiteli (plain text)') | 		help_text='Neveřejná poznámka k řešiteli (plain text)') | ||||||
| 
 | 
 | ||||||
|  | 	upozornovat_na_opravy_reseni = models.BooleanField('zasílat upozornění na změnu zpětné vazby k řešení emailem', default=True) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| 	def export_row(self): | 	def export_row(self): | ||||||
| 		"Slovnik pro pouziti v AESOP exportu" | 		"Slovnik pro pouziti v AESOP exportu" | ||||||
|  |  | ||||||
							
								
								
									
										34
									
								
								personalni/static/personalni/jak_se_dozvedeli.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								personalni/static/personalni/jak_se_dozvedeli.css
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,34 @@ | ||||||
|  | .seznam { | ||||||
|  | 	display: flex; | ||||||
|  | 	flex-direction: column; | ||||||
|  | 	gap: 0.3em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .hint { | ||||||
|  | 	border: 1px solid #ccc; | ||||||
|  | 	padding: 0.3em 1em; | ||||||
|  | 	border-radius: 5px; | ||||||
|  | 	margin-bottom: 1em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .osoba { | ||||||
|  | 	display: flex; | ||||||
|  | 	justify-content: space-between; | ||||||
|  | 	gap: 0.5em; | ||||||
|  | 
 | ||||||
|  | 	.uno { | ||||||
|  | 		flex: 2; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	.dos { | ||||||
|  | 		flex: 2; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	.tres { | ||||||
|  | 		flex: 1; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	.grey { | ||||||
|  | 		opacity: 0.5; | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										28
									
								
								personalni/templates/personalni/jak_se_dozvedeli.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								personalni/templates/personalni/jak_se_dozvedeli.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,28 @@ | ||||||
|  | {% extends "base.html" %} | ||||||
|  | 
 | ||||||
|  | {% block custom_css %} | ||||||
|  | {% load static %} | ||||||
|  | <link href="{% static 'personalni/jak_se_dozvedeli.css' %}" rel="stylesheet"> | ||||||
|  | {% endblock %} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | {% block content %} | ||||||
|  | <div class="seznam"> | ||||||
|  |   <div class="osoba hint"> | ||||||
|  |     <div class="uno">Jméno</div> | ||||||
|  |     <div class="dos">Jak se dozvěděli</div> | ||||||
|  |     <div class="tres">Datum registrace</div> | ||||||
|  |   </div> | ||||||
|  | 
 | ||||||
|  |   {% for osoba in object_list %} | ||||||
|  |   <div class="osoba"> | ||||||
|  |     <div class="uno">{{ osoba.jmeno }} {{ osoba.prijmeni }}</div> | ||||||
|  |     <div class="dos {% if not osoba.jak_se_dozvedeli %}grey{% endif %}">{% if osoba.jak_se_dozvedeli %} {{osoba.jak_se_dozvedeli}} {% else %} NEZADÁNO {% endif %}</div> | ||||||
|  |     <div class="tres">{{ osoba.datum_registrace }}</div> | ||||||
|  |   </div> | ||||||
|  |   {% endfor %} | ||||||
|  | </div> | ||||||
|  | {% endblock%} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @ -51,6 +51,7 @@ | ||||||
| </h4> | </h4> | ||||||
| <table class="form"> | <table class="form"> | ||||||
|   {% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat_cislo_emailem %} |   {% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat_cislo_emailem %} | ||||||
|  |   {% include "personalni/udaje/prihlaska_field.html" with field=form.upozornovat_na_opravy_reseni %} | ||||||
|   {% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat_cislo_papirove %} |   {% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat_cislo_papirove %} | ||||||
|   {% include "personalni/udaje/prihlaska_field.html" with field=form.spam %} |   {% include "personalni/udaje/prihlaska_field.html" with field=form.spam %} | ||||||
|   {% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat %} |   {% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat %} | ||||||
|  |  | ||||||
|  | @ -33,4 +33,11 @@ urlpatterns = [ | ||||||
| 		name='stari_organizatori' | 		name='stari_organizatori' | ||||||
| 	), | 	), | ||||||
| 
 | 
 | ||||||
|  | 	# Zpřístupnění dat z "jak jste se o nás dozvěděli" pro orgy propagace | ||||||
|  |     path( | ||||||
|  |         'org/propagace/jak-se-dozvedeli/', | ||||||
|  |         org_required(views.JakSeDozvedeliView.as_view()), | ||||||
|  |         name='jak_se_dozvedeli' | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | @ -34,7 +34,7 @@ from various.autentizace.utils import posli_reset_hesla | ||||||
| 
 | 
 | ||||||
| from django.forms.models import model_to_dict | from django.forms.models import model_to_dict | ||||||
| 
 | 
 | ||||||
| from .models import Organizator | from .models import Organizator, Osoba  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def aktivniOrganizatori(datum=timezone.now()): | def aktivniOrganizatori(datum=timezone.now()): | ||||||
|  | @ -62,6 +62,11 @@ class CojemamOrganizatoriStariView(generic.ListView): | ||||||
| 		id__in=aktivniOrganizatori() | 		id__in=aktivniOrganizatori() | ||||||
| 	).order_by('-organizuje_do') | 	).order_by('-organizuje_do') | ||||||
| 
 | 
 | ||||||
|  | class JakSeDozvedeliView(generic.ListView): | ||||||
|  | 	model = Osoba | ||||||
|  | 	template_name = 'personalni/jak_se_dozvedeli.html' | ||||||
|  | 	queryset = Osoba.objects.order_by('-datum_registrace') | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| def obalkyView(request, resitele): | def obalkyView(request, resitele): | ||||||
| 	if len(resitele) == 0: | 	if len(resitele) == 0: | ||||||
|  | @ -230,6 +235,7 @@ def resitelEditView(request): | ||||||
| 				resitel_edit.zasilat = fcd['zasilat'] | 				resitel_edit.zasilat = fcd['zasilat'] | ||||||
| 				resitel_edit.zasilat_cislo_emailem = fcd['zasilat_cislo_emailem'] | 				resitel_edit.zasilat_cislo_emailem = fcd['zasilat_cislo_emailem'] | ||||||
| 				resitel_edit.zasilat_cislo_papirove = fcd['zasilat_cislo_papirove'] | 				resitel_edit.zasilat_cislo_papirove = fcd['zasilat_cislo_papirove'] | ||||||
|  | 				resitel_edit.upozornovat_na_opravy_reseni = fcd['upozornovat_na_opravy_reseni'] | ||||||
| 				if fcd.get('skola'): | 				if fcd.get('skola'): | ||||||
| 					resitel_edit.skola = fcd['skola'] | 					resitel_edit.skola = fcd['skola'] | ||||||
| 				else: | 				else: | ||||||
|  |  | ||||||
|  | @ -0,0 +1,3 @@ | ||||||
|  | """ | ||||||
|  | Aplikace umožňující orgům vypisovat si přednášky a účastníkům o nich hlasovat. | ||||||
|  | """ | ||||||
|  | @ -4,11 +4,15 @@ from reversion.admin import VersionAdmin | ||||||
| from django.utils.safestring import mark_safe | from django.utils.safestring import mark_safe | ||||||
| from django.utils.html import escape | from django.utils.html import escape | ||||||
| 
 | 
 | ||||||
| from .models import Prednaska, Seznam, STAV_NAVRH | from .models import Prednaska, Seznam, Znalost | ||||||
| from soustredeni.models import Soustredeni | from soustredeni.models import Soustredeni | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Seznam_PrednaskaInline(admin.TabularInline): | class Seznam_PrednaskaInline(admin.TabularInline): | ||||||
|  | 	""" | ||||||
|  | 		Pomůcka pro :py:class:`prednasky.admin.SeznamAdmin` zobrazující hezky :py:class:`Přednášky <prednasky.models.Prednaska>` | ||||||
|  | 		v adminu :py:class:`Seznamu <prednasky.models.Seznam>`. | ||||||
|  | 	""" | ||||||
| 	model = Prednaska.seznamy.through | 	model = Prednaska.seznamy.through | ||||||
| 	extra = 0 | 	extra = 0 | ||||||
| 
 | 
 | ||||||
|  | @ -54,24 +58,57 @@ class Seznam_PrednaskaInline(admin.TabularInline): | ||||||
| 	def has_add_permission(self, req, obj): return False | 	def has_add_permission(self, req, obj): return False | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | class Seznam_ZnalostInline(admin.TabularInline): | ||||||
|  | 	""" | ||||||
|  | 		Pomůcka pro :py:class:`prednasky.admin.SeznamAdmin` zobrazující hezky :py:class:`Znalosti <prednasky.models.Znalost>` | ||||||
|  | 		v adminu :py:class:`Seznamu <prednasky.models.Seznam>`. | ||||||
|  | 	""" | ||||||
|  | 	model = Znalost.seznamy.through | ||||||
|  | 	extra = 0 | ||||||
|  | 
 | ||||||
|  | 	def znalost__nazev(self, obj): | ||||||
|  | 		return mark_safe( | ||||||
|  | 			f"<a href='/admin/prednasky/znalost/{obj.znalost.id}'>{obj.znalost.nazev}</a>" | ||||||
|  | 		) | ||||||
|  | 
 | ||||||
|  | 	def znalost__text(self, obj): | ||||||
|  | 		return mark_safe( | ||||||
|  | 			f"<div style='width: 200px'>{escape(obj.znalost.text)}</div>" | ||||||
|  | 		) | ||||||
|  | 
 | ||||||
|  | 	znalost__nazev.short_description = u'Přednáška' | ||||||
|  | 	znalost__text.short_description = u'Popis pro orgy' | ||||||
|  | 
 | ||||||
|  | 	readonly_fields = [ | ||||||
|  | 		'znalost__nazev', | ||||||
|  | 		'znalost__text', | ||||||
|  | 	] | ||||||
|  | 	exclude = ['znalost'] | ||||||
|  | 
 | ||||||
|  | 	def has_add_permission(self, req, obj): return False | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class SeznamAdmin(VersionAdmin): | class SeznamAdmin(VersionAdmin): | ||||||
|  | 	""" Admin pro :py:class:`Seznam <prednasky.models.Seznam>` """ | ||||||
| 	list_display = ['soustredeni', 'stav'] | 	list_display = ['soustredeni', 'stav'] | ||||||
| 	inlines = [Seznam_PrednaskaInline] | 	inlines = [Seznam_PrednaskaInline, Seznam_ZnalostInline] | ||||||
| 
 | 
 | ||||||
| admin.site.register(Seznam, SeznamAdmin) | admin.site.register(Seznam, SeznamAdmin) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class PrednaskaAdmin(VersionAdmin): | class PrednaskaAdmin(VersionAdmin): | ||||||
|  | 	""" Admin pro :py:class:`Přednášku <prednasky.models.Prednaska> """ | ||||||
| 	list_display = ['nazev', 'org', 'obor'] | 	list_display = ['nazev', 'org', 'obor'] | ||||||
| 	list_filter = ['org', 'obor'] | 	list_filter = ['org', 'obor'] | ||||||
| 	search_fields = [] | 	search_fields = ['nazev'] | ||||||
| 	filter_horizontal = ('seznamy', ) | 	filter_horizontal = ('seznamy', ) | ||||||
| 
 | 
 | ||||||
| 	actions = ['move_to_soustredeni'] | 	actions = ['move_to_soustredeni'] | ||||||
| 
 | 
 | ||||||
| 	def move_to_soustredeni(self, request, queryset): | 	def move_to_soustredeni(self, request, queryset): | ||||||
|  | 		""" Přidá dané přednášky do seznamu, o kterém se právě hlasuje """ | ||||||
| 		sous = Soustredeni.objects.first() | 		sous = Soustredeni.objects.first() | ||||||
| 		seznam = Seznam.objects.filter(soustredeni=sous, stav=STAV_NAVRH) | 		seznam = Seznam.objects.filter(soustredeni=sous, stav=Seznam.Stav.NAVRH) | ||||||
| 		if len(seznam) == 0: | 		if len(seznam) == 0: | ||||||
| 			self.message_user( | 			self.message_user( | ||||||
| 				request, | 				request, | ||||||
|  | @ -97,3 +134,14 @@ class PrednaskaAdmin(VersionAdmin): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| admin.site.register(Prednaska, PrednaskaAdmin) | admin.site.register(Prednaska, PrednaskaAdmin) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ZnalostAdmin(PrednaskaAdmin): # Trochu hack, ať nemusím vypisovat všechno znovu | ||||||
|  | 	""" | ||||||
|  | 		Admin pro :py:class:`Znalost <prednasky.models.Znalost> | ||||||
|  | 		TODO předělat, aby nedědila z :py:class:`prednasky.admin.PrednaskaAdmin`, ale společné věci byly zvlášť | ||||||
|  | 	""" | ||||||
|  | 	list_display = ("__str__",) | ||||||
|  | 	list_filter = () | ||||||
|  | 
 | ||||||
|  | admin.site.register(Znalost, ZnalostAdmin) | ||||||
|  |  | ||||||
|  | @ -1,7 +1,31 @@ | ||||||
| from django import forms | from django import forms | ||||||
| 
 | 
 | ||||||
| class NewPrednaskyForm(forms.Form): | from .models import Hlasovani, HlasovaniOZnalostech | ||||||
| 	ucastnik = forms.CharField(label = 'Tvoje jméno', max_length = 100) |  | ||||||
| 
 | 
 | ||||||
|  | class HlasovaniPrednaskaForm(forms.Form): | ||||||
|  | 	""" :py:class:`Formulář <django.forms.Form>` pro pro :py:class:`Hlasování <prednasky.models.Hlasovani>` o jedné :py:class:`Přednášce <prednasky.models.Prednaska>` | ||||||
|  | 	(neobsahuje téměř nic, většina se musí doplnit jiným způsobem) | ||||||
|  | 	""" | ||||||
| 
 | 
 | ||||||
|  | 	#: ID :py:class:`Přednášky <prednasky.models.Prednaska>`, o které se hlasuje | ||||||
|  | 	prednaska_id = forms.IntegerField(widget=forms.HiddenInput) | ||||||
|  | 	#: :py:class:`Hodnocení (Body) <prednasky.models.Hlasovani.Body>` této přednášky | ||||||
|  | 	body = forms.ChoiceField(label=False, widget=forms.RadioSelect, choices=Hlasovani.Body.choices, initial=Hlasovani.Body.JEDNO) | ||||||
| 
 | 
 | ||||||
|  | #: Množina formulářů (:py:class:`formset <django.forms.formsets.BaseFormSet>` :py:class:`HlasovaniPrednaskaFormů <prednasky.forms.HlasovaniPrednaskaForm>`) | ||||||
|  | #: pro :py:class:`Hlasování <prednasky.models.Hlasovani>` o množině :py:class:`Přednášek <prednasky.models.Prednaska>` | ||||||
|  | HlasovaniPrednaskaFormSet = forms.formset_factory(HlasovaniPrednaskaForm, extra=0) | ||||||
|  | 
 | ||||||
|  | class HlasovaniZnalostiForm(forms.Form): | ||||||
|  | 	""" :py:class:`Formulář <django.forms.Form>` pro pro :py:class:`HlasováníOZnalostech <prednasky.models.HlasovaniOZnalostech>` o jedné :py:class:`Znalosti <prednasky.models.Znalost>` | ||||||
|  | 	(neobsahuje téměř nic, většina se musí doplnit jiným způsobem) | ||||||
|  | 	""" | ||||||
|  | 
 | ||||||
|  | 	#: ID :py:class:`Znalosti <prednasky.models.Znalost>`, o které hlasujeme | ||||||
|  | 	znalost_id = forms.IntegerField(widget=forms.HiddenInput) | ||||||
|  | 	#: :py:class:`Odpověď <prednasky.models.HlasovaniOZnalostech.Odpoved>` na tuto znalost | ||||||
|  | 	odpoved = forms.ChoiceField(label=False, widget=forms.RadioSelect, choices=HlasovaniOZnalostech.Odpoved.choices) | ||||||
|  | 
 | ||||||
|  | #: Množina formulářů (:py:class:`formset <django.forms.formsets.BaseFormSet>` :py:class:`HlasovaniZnalostiFormů <prednasky.forms.HlasovaniZnalostiForm>`) | ||||||
|  | #: pro :py:class:`HlasováníOZnalostech <prednasky.models.HlasovaniOZnalostech>` o množině :py:class:`Znalostí <prednasky.models.Znalost>` | ||||||
|  | HlasovaniZnalostiFormSet = forms.formset_factory(HlasovaniZnalostiForm, extra=0) | ||||||
|  |  | ||||||
							
								
								
									
										39
									
								
								prednasky/migrations/0019_znalost_hlasovanioznalostech.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								prednasky/migrations/0019_znalost_hlasovanioznalostech.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,39 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2025-01-24 13:41 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations, models | ||||||
|  | import django.db.models.deletion | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('personalni', '0019_rename_upozorneni_resitel_upozornovat_na_opravy_reseni'), | ||||||
|  |         ('prednasky', '0018_post_split_soustredeni'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Znalost', | ||||||
|  |             fields=[ | ||||||
|  |                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||||
|  |                 ('nazev', models.CharField(help_text='Např. Neuronové sítě', max_length=200, verbose_name='Nadpis')), | ||||||
|  |                 ('text', models.TextField(blank=True, help_text='Např. Perceptron, vrstevnatá síť, forward a backward propagation', null=True, verbose_name='Detailní popis')), | ||||||
|  |                 ('seznamy', models.ManyToManyField(to='prednasky.seznam')), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'verbose_name': 'Znalost k přednáškám', | ||||||
|  |                 'verbose_name_plural': 'Znalosti k přednáškám', | ||||||
|  |                 'db_table': 'prednasky_znalost', | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='HlasovaniOZnalostech', | ||||||
|  |             fields=[ | ||||||
|  |                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||||
|  |                 ('odpoved', models.CharField(choices=[(-1, 'Tohle celkem umím'), (0, 'Už jsem o tom slyšel, ale neřekl bychm, že to úplně umím'), (1, 'Tohle vůbec neznám')], max_length=16, verbose_name='odpověď')), | ||||||
|  |                 ('seznam', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='prednasky.seznam')), | ||||||
|  |                 ('ucastnik', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='personalni.osoba')), | ||||||
|  |                 ('znalost', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='prednasky.znalost')), | ||||||
|  |             ], | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										18
									
								
								prednasky/migrations/0020_alter_hlasovani_body.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								prednasky/migrations/0020_alter_hlasovani_body.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2025-01-24 20:04 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations, models | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('prednasky', '0019_znalost_hlasovanioznalostech'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='hlasovani', | ||||||
|  |             name='body', | ||||||
|  |             field=models.IntegerField(choices=[(-1, 'rozhodně nechci'), (0, 'je mi to jedno'), (1, 'rozhodně chci')], default=0, verbose_name='Body'), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
|  | @ -0,0 +1,24 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2025-02-04 20:09 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations, models | ||||||
|  | 
 | ||||||
|  | def zmena_bodu(apps, _schema_editor): | ||||||
|  |     HlasovaniOZnalostech = apps.get_model('prednasky','HlasovaniOZnalostech') | ||||||
|  |     for h in HlasovaniOZnalostech.objects.all(): | ||||||
|  |         h.odpoved = -int(h.odpoved) | ||||||
|  |         h.save() | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('prednasky', '0020_alter_hlasovani_body'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='hlasovanioznalostech', | ||||||
|  |             name='odpoved', | ||||||
|  |             field=models.IntegerField(choices=[(1, 'Tohle celkem umím'), (0, 'Už jsem o tom slyšel, ale neřekl bychm, že to úplně umím'), (-1, 'Tohle vůbec neznám')], verbose_name='odpověď'), | ||||||
|  |         ), | ||||||
|  |         migrations.RunPython(zmena_bodu, reverse_code=zmena_bodu), | ||||||
|  |     ] | ||||||
|  | @ -0,0 +1,18 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2025-02-09 21:10 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations, models | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('prednasky', '0021_alter_hlasovanioznalostech_odpoved'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='hlasovanioznalostech', | ||||||
|  |             name='odpoved', | ||||||
|  |             field=models.IntegerField(choices=[(1, 'Tohle celkem umím'), (0, 'Už jsem o tom slyšel, ale neřekl bych, že to úplně umím'), (-1, 'Tohle vůbec neznám')], verbose_name='odpověď'), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
|  | @ -1,81 +1,134 @@ | ||||||
| from django.db import models | from django.db import models | ||||||
| 
 | 
 | ||||||
| from soustredeni.models import Soustredeni | from soustredeni.models import Soustredeni | ||||||
| from personalni.models import Organizator | from personalni.models import Organizator, Osoba | ||||||
| 
 |  | ||||||
| STAV_NAVRH = 1 |  | ||||||
| STAV_BUDE = 2 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| STAV_CHOICES = ( |  | ||||||
| (STAV_NAVRH, 'Návrh'), |  | ||||||
| (STAV_BUDE, 'Bude') |  | ||||||
| ) |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Seznam(models.Model): | class Seznam(models.Model): | ||||||
| 	class Meta: | 	""" | ||||||
| 		db_table = 'prednasky_seznam' | 		Spojuje :py:class:`Přednášky <prednasky.models.Prednaska>` | ||||||
| 		verbose_name = 'Seznam přednášek' | 		se :py:class:`Soustředěními <soustredeni.models.Soustredeni>`, | ||||||
| 		verbose_name_plural = 'Seznamy přednášek' | 		kde by mohly zaznít, nebo zazní/zazněly. | ||||||
| 		ordering = ['soustredeni', 'stav'] | 	""" | ||||||
| 
 | 
 | ||||||
| 	id = models.AutoField(primary_key = True)  | 	class Meta: | ||||||
| 	soustredeni = models.ForeignKey(Soustredeni,null = True, default = None,  | 		db_table = "prednasky_seznam" | ||||||
| 		on_delete=models.PROTECT) | 		verbose_name = "Seznam přednášek" | ||||||
| 	stav = models.IntegerField('Stav',choices=STAV_CHOICES,default = STAV_NAVRH) | 		verbose_name_plural = "Seznamy přednášek" | ||||||
|  | 		ordering = ["soustredeni", "stav"] | ||||||
|  | 
 | ||||||
|  | 	class Stav(models.IntegerChoices): | ||||||
|  | 		""" Stav seznamu přednášek (NAVRH se používá k hlasování viz :py:func:`daný view <prednasky.views.newPrednaska>`). """ | ||||||
|  | 		NAVRH = 1, "Návrh" | ||||||
|  | 		BUDE = 2, "Bude" | ||||||
|  | 
 | ||||||
|  | 	id = models.AutoField(primary_key=True) | ||||||
|  | 	soustredeni = models.ForeignKey(Soustredeni, null=True, default=None, on_delete=models.PROTECT) | ||||||
|  | 	stav = models.IntegerField("Stav", choices=Stav.choices, default=Stav.NAVRH) #: :py:class:`Stav <prednasky.models.Seznam.Stav>` Seznamu | ||||||
| 
 | 
 | ||||||
| 	def __str__(self): | 	def __str__(self): | ||||||
| 			return "Seznam {}přednášek na {}".format("návrhů "  | 		return f"Seznam {'návrhů ' if self.stav == Seznam.Stav.NAVRH else ''}přednášek na {self.soustredeni}" | ||||||
| 					if self.stav == STAV_NAVRH else "", self.soustredeni) |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| CHOICES_OBTIZNOST = ( |  | ||||||
| 				(1, 'Lehká'), |  | ||||||
| 				(2, 'Střední'), |  | ||||||
| 				(3, 'Těžká'), |  | ||||||
| 				) |  | ||||||
| 
 |  | ||||||
| CHOICES_BODY = ( |  | ||||||
| 		(-1, '-1'), |  | ||||||
| 		(0, '0'), |  | ||||||
| 		(1, '1'), |  | ||||||
| 		) |  | ||||||
| 
 |  | ||||||
| class Prednaska(models.Model): | class Prednaska(models.Model): | ||||||
|  | 	""" | ||||||
|  | 		Reprezentuje přednášku, kterou si org může vypsat a účastník o ní hlasovat. | ||||||
|  | 		(Viz :py:class:`Hlasování <prednasky.models.Hlasovani>`.) | ||||||
|  | 	""" | ||||||
| 	class Meta: | 	class Meta: | ||||||
| 		db_table = 'prednasky_prednaska' | 		db_table = "prednasky_prednaska" | ||||||
| 		verbose_name = 'Přednáška' | 		verbose_name = "Přednáška" | ||||||
| 		verbose_name_plural = 'Přednášky' | 		verbose_name_plural = "Přednášky" | ||||||
| 		ordering = ['org', 'nazev'] | 		ordering = ["org", "nazev"] | ||||||
| 
 | 
 | ||||||
| 	id = models.AutoField(primary_key = True)  | 	class Obtiznost(models.IntegerChoices): | ||||||
| 	nazev = models.CharField('Název', max_length = 300) | 		LEHKA = 1, "Lehká" | ||||||
|  | 		STREDNI = 2, "Střední" | ||||||
|  | 		TEZKA = 3, "Těžká" | ||||||
|  | 
 | ||||||
|  | 	id = models.AutoField(primary_key=True) | ||||||
|  | 	nazev = models.CharField("Název", max_length=300) | ||||||
| 	org = models.ForeignKey(Organizator, on_delete=models.PROTECT) | 	org = models.ForeignKey(Organizator, on_delete=models.PROTECT) | ||||||
| 	popis = models.TextField('Popis pro orgy',null = True, blank = True,help_text = 'Neveřejný popis pro ostatní orgy') | 	popis = models.TextField("Popis pro orgy", null=True, blank=True, help_text="Neveřejný popis pro ostatní orgy") | ||||||
| 	anotace = models.TextField('Anotace',null = True, blank = True, help_text = 'Veřejná anotace v hlasování') | 	anotace = models.TextField("Anotace", null=True, blank=True, help_text="Veřejná anotace v hlasování") | ||||||
| 	obtiznost = models.IntegerField('Obtížnost', choices=CHOICES_OBTIZNOST) | 	obtiznost = models.IntegerField("Obtížnost", choices=Obtiznost.choices) #: :py:class:`Obtížnost <prednasky.models.Prednaska.Obtiznost>` Přednášky | ||||||
| 	obor = models.CharField('Obor', max_length = 5, help_text = 'Podmnožina MFIOB') | 	obor = models.CharField("Obor", max_length=5, help_text="Podmnožina MFIOB") | ||||||
| 	klicova = models.CharField('Klíčová slova', max_length = 200, null = True, blank = True) | 	klicova = models.CharField("Klíčová slova", max_length=200, null=True, blank=True) | ||||||
| 	seznamy = models.ManyToManyField(Seznam) | 	seznamy = models.ManyToManyField(Seznam) | ||||||
| 
 | 
 | ||||||
| 	def __str__(self): | 	def __str__(self): | ||||||
| 		return "{} ({})".format(self.nazev, self.org) | 		return f"{self.nazev} ({self.org})" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Hlasovani(models.Model): | class Hlasovani(models.Model): | ||||||
|  | 	""" | ||||||
|  | 		Reprezentuje hlasování jednoho účastníka | ||||||
|  | 		o jedné :py:class:`Přednášce <prednasky.models.Prednaska>` | ||||||
|  | 		v jednom :py:class:`Seznamu <prednasky.models.Seznam>` (účastníkův pohled se totiž mezi sousy změnit) | ||||||
|  | 	""" | ||||||
| 	class Meta: | 	class Meta: | ||||||
| 		db_table = 'prednasky_hlasovani' | 		db_table = "prednasky_hlasovani" | ||||||
| 		verbose_name = 'Hlasování' | 		verbose_name = "Hlasování" | ||||||
| 		verbose_name_plural = 'Hlasování' | 		verbose_name_plural = "Hlasování" | ||||||
| 		ordering = ['ucastnik', 'prednaska'] | 		ordering = ["ucastnik", "prednaska"] | ||||||
| 	id = models.AutoField(primary_key = True)  | 
 | ||||||
|  | 	class Body(models.IntegerChoices): | ||||||
|  | 		""" Ohodnocení přednášky v daném Hlasování (větší číslo = víc chci) """ | ||||||
|  | 		NECHCI = -1, "rozhodně nechci" | ||||||
|  | 		JEDNO = 0, "je mi to jedno" | ||||||
|  | 		CHCI = 1, "rozhodně chci" | ||||||
|  | 
 | ||||||
|  | 	id = models.AutoField(primary_key=True) | ||||||
| 	prednaska = models.ForeignKey(Prednaska, on_delete=models.CASCADE) | 	prednaska = models.ForeignKey(Prednaska, on_delete=models.CASCADE) | ||||||
| 	body = models.IntegerField('Body', default = 0, choices = CHOICES_BODY) | 	#: Příslušné hlasování: :py:class:`Body <prednasky.models.Hlasovani.Body>` | ||||||
| 	ucastnik = models.CharField('Účastník', max_length = 100) | 	body = models.IntegerField("Body", default=Body.JEDNO, choices=Body.choices) | ||||||
| 	seznam = models.ForeignKey(Seznam,null=True,on_delete=models.SET_NULL) | 
 | ||||||
|  | 	#: Účastník, který hlasoval. Pouze string: | ||||||
|  | 	#: *(přechod z jména na objekt Osoby nějak kape na tom, | ||||||
|  | 	#: že všechna předchozí hlasování zde mají náhodný string…) | ||||||
|  | 	#: TODO Změnit to na Osobu* | ||||||
|  | 	ucastnik = models.CharField("Účastník", max_length=100) | ||||||
|  | 	seznam = models.ForeignKey(Seznam, null=True, on_delete=models.SET_NULL) | ||||||
| 
 | 
 | ||||||
| 	def __str__(self): | 	def __str__(self): | ||||||
| 		return "{} dal {} bodů {} v seznamu {}".format(self.ucastnik,  | 		return f"{self.ucastnik} dal {self.body} bodů {self.prednaska} v seznamu {self.seznam}" | ||||||
| 					self.body, self.prednaska, self.seznam) | 
 | ||||||
|  | 
 | ||||||
|  | class Znalost(models.Model): | ||||||
|  | 	""" | ||||||
|  | 		Reprezentuje znalost, na kterou se můžeme účastníka ptát (nechat je hlasovat). | ||||||
|  | 		(Viz :py:class:`HlasováníOZnalostech <prednasky.models.HlasovaniOZnalostech>`.) | ||||||
|  | 	""" | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = "prednasky_znalost" | ||||||
|  | 		verbose_name = "Znalost k přednáškám" | ||||||
|  | 		verbose_name_plural = "Znalosti k přednáškám" | ||||||
|  | 
 | ||||||
|  | 	nazev = models.CharField("Nadpis", max_length=200, blank=False, null=False, help_text="Např. Neuronové sítě") | ||||||
|  | 	text = models.TextField("Detailní popis", blank=True, null=True, help_text="Např. Perceptron, vrstevnatá síť, forward a backward propagation") | ||||||
|  | 	seznamy = models.ManyToManyField(Seznam) | ||||||
|  | 
 | ||||||
|  | 	def __str__(self): | ||||||
|  | 		return self.nazev | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class HlasovaniOZnalostech(models.Model): | ||||||
|  | 	""" | ||||||
|  | 		Reprezentuje hlasování jednoho účastníka | ||||||
|  | 		o jedné :py:class:`Znalosti <prednasky.models.Znalost>` | ||||||
|  | 		v jednom :py:class:`Seznamu <prednasky.models.Seznam>` (účastníkův pohled se totiž mezi sousy změnit) | ||||||
|  | 	""" | ||||||
|  | 	class Odpoved(models.IntegerChoices): | ||||||
|  | 		""" Na kolik danou znalost účastník ovládá v daném Hlasování (větší číslo = víc zná) """ | ||||||
|  | 		UMIM = 1, "Tohle celkem umím" | ||||||
|  | 		CIRCA = 0, "Už jsem o tom slyšel, ale neřekl bych, že to úplně umím" | ||||||
|  | 		NEUMIM = -1, "Tohle vůbec neznám" | ||||||
|  | 
 | ||||||
|  | 	odpoved = models.IntegerField(u"odpověď", choices=Odpoved.choices, blank=False, null=False) #: :py:class:`Odpověď <prednasky.models.Prednaska.Odpoved>` na HlasováníOZnalostech | ||||||
|  | 	znalost = models.ForeignKey(Znalost, on_delete=models.CASCADE, blank=False, null=False) | ||||||
|  | 	ucastnik = models.ForeignKey(Osoba, on_delete=models.CASCADE, blank=False, null=False) | ||||||
|  | 	seznam = models.ForeignKey(Seznam, on_delete=models.SET_NULL, blank=True, null=True) | ||||||
|  | 
 | ||||||
|  | 	def __str__(self): | ||||||
|  | 		return f"{self.ucastnik} dal {self.znalost} bodů {self.znalost} v seznamu {self.seznam}" | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -5,34 +5,36 @@ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| {% block content %} | {% block content %} | ||||||
| <h1> | <h1>{% block nadpis1a %}Hlasování o přednáškách{% endblock %}</h1> | ||||||
| {% block nadpis1a %}Hlasování o přednáškách{% endblock %} |  | ||||||
| </h1> |  | ||||||
| 
 |  | ||||||
| <p> |  | ||||||
| Jak moc by ses chtěl(a) zúčastnit následujících přednášek? |  | ||||||
| <br> |  | ||||||
| <span style="font-size: 75%">Obtížnost 1 je nejlehčí, 3 nejtěžší.</span> |  | ||||||
| </p> |  | ||||||
| 
 | 
 | ||||||
| <form enctype="multipart/form-data" action="." method="post"> | <form enctype="multipart/form-data" action="." method="post"> | ||||||
|   {% csrf_token %} |   {% csrf_token %} | ||||||
|   <table> | 
 | ||||||
|     {% for p, h in prednasky %} | <h3>Jak moc by ses chtěl(a) zúčastnit následujících přednášek?</h3> | ||||||
|     <tr><td><label>{{p.org}}: <span style="font-size: 175%">{{p.nazev}}</span></label></td></tr> | <p>Obtížnost 1 je nejlehčí, 3 nejtěžší.</p> | ||||||
|     <tr><td><p><i>{{p.anotace}}</i></p></td></tr> |   {{ form_set_prednasky.management_form }} | ||||||
|     <tr><td><label>Obor: </label> {{p.obor}}</td></tr> |   {% for f, p in formy_a_prednasky %} | ||||||
|     <tr><td><label>Obtížnost: </label> {{p.obtiznost}}</td>   </tr> |     <h4>{{p.nazev}} ({{p.org}})</h4> | ||||||
|     {% if p.klicova %}<tr><td><label>Klíčová slova: </label> {{p.klicova}}</td></tr>{% endif%} |     <p class="textprednasky">{{p.anotace | linebreaksbr}}</p> | ||||||
|     <tr><td>Hodnocení: |     <label>Obor: </label> {{p.obor}}<br> | ||||||
|         <INPUT TYPE="radio" NAME="q{{p.pk}}" VALUE="-1" {% if h == -1 %} CHECKED="checked" {% endif %} > rozhodně nechci |     <label>Obtížnost: </label> {{p.obtiznost}}<br> | ||||||
|         <INPUT TYPE="radio" NAME="q{{p.pk}}" VALUE="0" {% if h == 0 %} CHECKED="checked" {% endif %}> je mi to jedno |     {% if p.klicova %}<label>Klíčová slova: </label> {{p.klicova}}<br>{% endif%} | ||||||
|         <INPUT TYPE="radio" NAME="q{{p.pk}}" VALUE="1" {% if h == 1 %} CHECKED="checked" {% endif %}> rozhodně chci |     <br> | ||||||
|     </td></tr> |     {{ f }} | ||||||
|     <tr><td> </td></tr> |     <br> | ||||||
|     {% endfor %} |   {% empty %} | ||||||
|     <tr><td><input name="odeslat" type="submit" value="Odeslat"></td><tr> |     Nejsou žádné přednášky o kterých by šlo hlasovat. | ||||||
|   </table> |   {% endfor %} | ||||||
|  | 
 | ||||||
|  |   {{ form_set_znalosti.management_form }} | ||||||
|  |   {% for f, z in formy_a_znalosti %} | ||||||
|  |     {% if forloop.first %}<hr/><h3>Jak moc znáš následující?</h3>{% endif %} | ||||||
|  |     <h4>{{z.nazev}}</h4> | ||||||
|  |     <p class="textznalosti">{{z.text | linebreaksbr}}</p> | ||||||
|  |     {{ f }} | ||||||
|  |     <br> | ||||||
|  |   {% endfor %} | ||||||
|  |   <input type="submit" value="Odeslat"/> | ||||||
| </form> | </form> | ||||||
|     |     | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
|  | @ -1,11 +0,0 @@ | ||||||
| {% extends 'base.html' %} |  | ||||||
| 
 |  | ||||||
| {% load humanize %} |  | ||||||
| {% load static %} |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| {% block content %} |  | ||||||
| 
 |  | ||||||
| <h1> Děkujeme. </h1> |  | ||||||
|     |  | ||||||
| {% endblock %} |  | ||||||
|  | @ -14,7 +14,7 @@ | ||||||
|     {% else %} |     {% else %} | ||||||
|         <a href="/prednasky/seznam_prednasek/{{seznam.id}}">Seznam přednášek na soustředění {{seznam.soustredeni.misto}} </a> |         <a href="/prednasky/seznam_prednasek/{{seznam.id}}">Seznam přednášek na soustředění {{seznam.soustredeni.misto}} </a> | ||||||
|     {% endif %} |     {% endif %} | ||||||
|     <a href="/prednasky/seznam_prednasek/{{seznam.id}}/export">Export</a> |     <a href="/prednasky/seznam_prednasek/{{seznam.id}}/hlasovani.csv">Export</a> | ||||||
|     </li> |     </li> | ||||||
|   {% endfor %} |   {% endfor %} | ||||||
|   </ul> |   </ul> | ||||||
|  |  | ||||||
|  | @ -12,10 +12,15 @@ urlpatterns = [ | ||||||
| 		'prednasky/metaseznam_prednasek', | 		'prednasky/metaseznam_prednasek', | ||||||
| 		org_required(views.MetaSeznamListView.as_view()), | 		org_required(views.MetaSeznamListView.as_view()), | ||||||
| 		name='metaseznam-list'), | 		name='metaseznam-list'), | ||||||
|  | 	# path( | ||||||
|  | 	# 	'prednasky/seznam_prednasek/<int:seznam>/export', | ||||||
|  | 	# 	org_required(views.SeznamExportView), | ||||||
|  | 	# 	name='seznam-export' | ||||||
|  | 	# ), | ||||||
| 	path( | 	path( | ||||||
| 		'prednasky/seznam_prednasek/<int:seznam>/export', | 		'prednasky/seznam_prednasek/<int:seznam>/hlasovani.csv', | ||||||
| 		org_required(views.SeznamExportView), | 		org_required(views.PrednaskyExportView), | ||||||
| 		name='seznam-export' | 		name='seznam-export-csv' | ||||||
| 	), | 	), | ||||||
| 	path( | 	path( | ||||||
| 		'prednasky/seznam_prednasek/<int:seznam>/', | 		'prednasky/seznam_prednasek/<int:seznam>/', | ||||||
|  |  | ||||||
|  | @ -1,67 +1,142 @@ | ||||||
|  | import csv | ||||||
|  | import http | ||||||
|  | import logging | ||||||
|  | 
 | ||||||
|  | from django.http import HttpResponse, HttpRequest | ||||||
| from django.shortcuts import render, get_object_or_404 | from django.shortcuts import render, get_object_or_404 | ||||||
| from django.views import generic | from django.views import generic | ||||||
| from django.shortcuts import HttpResponseRedirect | from django.shortcuts import HttpResponseRedirect | ||||||
| from django.core.exceptions import ObjectDoesNotExist | from django.core.exceptions import ObjectDoesNotExist | ||||||
| from django.db.models import Sum | from django.db import transaction | ||||||
| from django.forms import Form |  | ||||||
| 
 | 
 | ||||||
| from prednasky.models import Prednaska, Hlasovani, Seznam, STAV_NAVRH | from various.views.pomocne import formularOKView | ||||||
|  | from .forms import HlasovaniPrednaskaFormSet, HlasovaniZnalostiFormSet | ||||||
|  | 
 | ||||||
|  | from various.models import Nastaveni | ||||||
|  | from prednasky.models import Prednaska, Hlasovani, Znalost, HlasovaniOZnalostech, Seznam | ||||||
| from soustredeni.models import Soustredeni | from soustredeni.models import Soustredeni | ||||||
| from personalni.models import Osoba | from personalni.models import Osoba | ||||||
| 
 | 
 | ||||||
| def newPrednaska(request): | PREDNASKY_PREFIX = "prednasky" | ||||||
|  | ZNALOSTI_PREFIX = "znalosti" | ||||||
|  | 
 | ||||||
|  | logger = logging.getLogger(__name__) | ||||||
|  | 
 | ||||||
|  | def newPrednaska(request: HttpRequest) -> HttpResponse: | ||||||
|  | 	""" | ||||||
|  | 		View zobrazující a ukládající účastnické hlasování | ||||||
|  | 		(:py:class:`Hlasování <prednasky.models.Hlasovani>` | ||||||
|  | 		a :py:class:`HlasováníOZnalostech <prednasky.models.HlasovaniOZnalostech>`) | ||||||
|  | 		o :py:class:`Přednáškách <prednasky.models.Prednaska>` | ||||||
|  | 		a :py:class:`Znalostech <prednasky.models.Znalost>` | ||||||
|  | 	""" | ||||||
| 	# hlasovani se vztahuje k nejnovejsimu soustredeni | 	# hlasovani se vztahuje k nejnovejsimu soustredeni | ||||||
| 	sous = Soustredeni.objects.first() | 	sous = Nastaveni.get_solo().aktualni_sous | ||||||
| 	seznam = Seznam.objects.filter(soustredeni = sous, stav = STAV_NAVRH).first() | 	seznam = Seznam.objects.filter(soustredeni = sous, stav=Seznam.Stav.NAVRH).first() | ||||||
|  | 	if sous is None or seznam is None: | ||||||
|  | 		return render(request, 'universal.html', { | ||||||
|  | 			'title': "Nelze hlasovat", | ||||||
|  | 			'text': "Není žádný seznam přednášek, o kterém by se dalo hlasovat.", | ||||||
|  | 		}, status=http.HTTPStatus.NOT_FOUND) | ||||||
|  | 
 | ||||||
| 	osoba = Osoba.objects.filter(user=request.user).first() | 	osoba = Osoba.objects.filter(user=request.user).first() | ||||||
| 	ucastnik = osoba.plne_jmeno() + ' ' + str(osoba.id) | 	ucastnik = osoba.plne_jmeno() + ' ' + str(osoba.id) # id, kvůli kolizi jmen | ||||||
| 	# obsluha formulare |  | ||||||
| 	if request.method == 'POST': |  | ||||||
| 		form = Form(request.POST, request.FILES) |  | ||||||
| 		if form.is_valid(): |  | ||||||
| 			# id z důvodu duplicitních jmen (přechod z jména na objekt Osoby nějak kape na tom, |  | ||||||
| 			# že všechna předchozí hlasování zde mají náhodný string…) |  | ||||||
| 			# TODO Změnit to na Osobu |  | ||||||
| 
 | 
 | ||||||
| 			# TODO v následujících řádcích je zbytečně mnoho dotazů na QuerySet (pokud účastník hlasoval, hlasoval u všech) | 	if request.method == 'POST': # Když to byl POST, tak ukládáme. | ||||||
| 			for i in request.POST: | 		# Načteme data do formsetů | ||||||
| 				if i[0] == 'q': | 		form_set_prednasky = HlasovaniPrednaskaFormSet(request.POST, prefix=PREDNASKY_PREFIX) | ||||||
| 					prednaska = Prednaska.objects.filter(pk=int(i[1:]))[0] | 		form_set_znalosti = HlasovaniZnalostiFormSet(request.POST, prefix=ZNALOSTI_PREFIX) | ||||||
| 					hlasovani = Hlasovani.objects.filter(ucastnik=ucastnik, prednaska=prednaska).first() | 
 | ||||||
| 					if not hlasovani: | 		if form_set_prednasky.is_valid() and form_set_znalosti.is_valid(): | ||||||
| 						hlasovani = Hlasovani() | 			with transaction.atomic(): | ||||||
| 						hlasovani.prednaska = prednaska | 				# Místo updatování data prostě smažeme a vytvoříme nová | ||||||
| 						hlasovani.ucastnik = ucastnik | 				seznam.hlasovani_set.filter(ucastnik=ucastnik).delete() | ||||||
| 						hlasovani.seznam = seznam | 				seznam.hlasovanioznalostech_set.filter(ucastnik=osoba).delete() | ||||||
| 					hlasovani.body = int(request.POST[i]) | 
 | ||||||
| 					hlasovani.save() | 				for form in form_set_prednasky: | ||||||
|  | 					prednaska_id = form.cleaned_data['prednaska_id'] | ||||||
|  | 					prednaska = Prednaska.objects.filter(id=prednaska_id).first() | ||||||
|  | 					if prednaska is None: | ||||||
|  | 						logger.error(f"Účastník {ucastnik} hodnotil neexistující přednášku {prednaska_id} číslem {form.cleaned_data['body']}") | ||||||
|  | 						continue | ||||||
|  | 
 | ||||||
|  | 					Hlasovani.objects.create( | ||||||
|  | 						prednaska=prednaska, | ||||||
|  | 						body=form.cleaned_data['body'], | ||||||
|  | 						ucastnik=ucastnik, | ||||||
|  | 						seznam=seznam, | ||||||
|  | 					) | ||||||
|  | 
 | ||||||
|  | 				for form in form_set_znalosti: | ||||||
|  | 					znalost_id = form.cleaned_data['znalost_id'] | ||||||
|  | 					znalost = Znalost.objects.filter(id=znalost_id).first() | ||||||
|  | 					if znalost is None: | ||||||
|  | 						logger.error(f"Účastník {ucastnik} hodnotil neexistující znalost {znalost_id} číslem {form.cleaned_data['odpoved']}") | ||||||
|  | 						continue | ||||||
|  | 
 | ||||||
|  | 					HlasovaniOZnalostech.objects.create( | ||||||
|  | 						odpoved=form.cleaned_data['odpoved'], | ||||||
|  | 						znalost=znalost, | ||||||
|  | 						ucastnik=osoba, | ||||||
|  | 						seznam=seznam, | ||||||
|  | 					) | ||||||
| 
 | 
 | ||||||
| 			# presmerovani na prave vzniklou galerii |  | ||||||
| 			return HttpResponseRedirect('./hotovo') | 			return HttpResponseRedirect('./hotovo') | ||||||
| 
 | 
 | ||||||
| 	def prednaska_hodnoceni(prednaska): | 		else: # Pokud je nějaký formset nevalidní, vracíme je k přepracování | ||||||
| 		h = Hlasovani.objects.filter(ucastnik=ucastnik, prednaska=prednaska).first() | 			prednasky = seznam.prednaska_set.all() | ||||||
| 		if h: | 			znalosti = seznam.znalost_set.all() | ||||||
| 			return prednaska, h.body | 			# FIXME Spadnout, pokud nesedí přednáška/znalost s formulářem. (Nějak se mi to nepovedlo.) | ||||||
| 		else: | 			# Může se totiž stát, že se mezitím změnily přednášky (nějaká byla přidána/odebrána) | ||||||
| 			return prednaska, 0 |  | ||||||
| 
 | 
 | ||||||
|  | 	else: # Když to nebyl POST, tak inicializujeme (pokud už o přednášce/znalosti účastník hlasoval, předvyplníme mu to). | ||||||
|  | 		def odpoved_prednasky(p: Prednaska) -> Hlasovani.Body: | ||||||
|  | 			hlasovani = p.hlasovani_set.filter(ucastnik=ucastnik).first() | ||||||
|  | 			return hlasovani.body if hlasovani else Hlasovani.Body.JEDNO | ||||||
|  | 
 | ||||||
|  | 		def odpoved_znalosti(z: Znalost) -> HlasovaniOZnalostech.Odpoved: | ||||||
|  | 			hlasovani = z.hlasovanioznalostech_set.filter(ucastnik=osoba).first() | ||||||
|  | 			return hlasovani.odpoved if hlasovani else HlasovaniOZnalostech.Odpoved.CIRCA | ||||||
|  | 
 | ||||||
|  | 		prednasky = seznam.prednaska_set.all() | ||||||
|  | 		znalosti = seznam.znalost_set.all() | ||||||
|  | 
 | ||||||
|  | 		form_set_prednasky = HlasovaniPrednaskaFormSet(initial=[ | ||||||
|  | 			{"prednaska_id": p.id, "body": odpoved_prednasky(p)} for p in prednasky | ||||||
|  | 		], prefix=PREDNASKY_PREFIX) | ||||||
|  | 
 | ||||||
|  | 		form_set_znalosti = HlasovaniZnalostiFormSet(initial=[ | ||||||
|  | 			{"znalost_id": z.id, "odpoved": odpoved_znalosti(z)} for z in znalosti | ||||||
|  | 		], prefix=ZNALOSTI_PREFIX) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 	# V případě nePOSTu nebo chyby při ukládání vracíme hlasování | ||||||
| 	return render( | 	return render( | ||||||
| 		request, | 		request, | ||||||
| 		'prednasky/base.html', | 		'prednasky/base.html', | ||||||
| 		{'prednasky': map(prednaska_hodnoceni, seznam.prednaska_set.all())} | 		{ | ||||||
|  | 			'form_set_prednasky': form_set_prednasky, 'form_set_znalosti': form_set_znalosti, | ||||||
|  | 			'formy_a_prednasky': zip(form_set_prednasky, prednasky), | ||||||
|  | 			'formy_a_znalosti': zip(form_set_znalosti, znalosti), | ||||||
|  | 		} | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def Prednaska_hotovo(request): | def Prednaska_hotovo(request: HttpRequest) -> HttpResponse: | ||||||
| 	return render(request, 'prednasky/hotovo.html') | 	""" View po vyplnění :py:func:`hlasování <prednasky.views.newPrednaska>` """ | ||||||
|  | 	return formularOKView(request, "Děkujeme za vyplnění hlasování o přednáškách a těšíme se na soustředění.") | ||||||
| 
 | 
 | ||||||
| class MetaSeznamListView(generic.ListView): | class MetaSeznamListView(generic.ListView): | ||||||
|  | 	""" Seznam všech :py:class:`Seznamů <prednasky.models.Seznam>` s odkazy na exporty """ | ||||||
| 	model = Seznam | 	model = Seznam | ||||||
| 	template_name = 'prednasky/metaseznam_prednasek.html' | 	template_name = 'prednasky/metaseznam_prednasek.html' | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SeznamListView(generic.ListView): | class SeznamListView(generic.ListView): | ||||||
|  | 	""" | ||||||
|  | 		Náhled na to, kolik má která přednáška v :py:class:`Seznamu <prednasky.models.Seznam>` :py:class:`hlasů <prednasky.models.Hlasovani.Body>`. | ||||||
|  | 		(Je otázka, zda tento View vůbec chceme. Pokud ano, hodilo by se do něj přidat i znalosti.) | ||||||
|  | 	""" | ||||||
| 	template_name = 'prednasky/seznam_prednasek.html' | 	template_name = 'prednasky/seznam_prednasek.html' | ||||||
| 
 | 
 | ||||||
| 	def get_queryset(self): | 	def get_queryset(self): | ||||||
|  | @ -77,7 +152,7 @@ class SeznamListView(generic.ListView): | ||||||
| 
 | 
 | ||||||
| 		# hlasovani se vztahuje k nejnovejsimu soustredeni | 		# hlasovani se vztahuje k nejnovejsimu soustredeni | ||||||
| 		sous = Soustredeni.objects.first() | 		sous = Soustredeni.objects.first() | ||||||
| 		seznam = Seznam.objects.filter(soustredeni = sous, stav = STAV_NAVRH).first() | 		seznam = Seznam.objects.filter(soustredeni = sous, stav=Seznam.Stav.NAVRH).first() | ||||||
| 	 | 	 | ||||||
| 		for obj in self.object_list: | 		for obj in self.object_list: | ||||||
| 			hlasovani_set = obj.hlasovani_set.filter(seznam=seznam).only('body') | 			hlasovani_set = obj.hlasovani_set.filter(seznam=seznam).only('body') | ||||||
|  | @ -86,32 +161,86 @@ class SeznamListView(generic.ListView): | ||||||
| 		return context | 		return context | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def SeznamExportView(request, seznam): | # def SeznamExportView(request, seznam): | ||||||
| 	"""Vypíše výsledky hlasování ve formátu pro prologovský optimalizátor""" | # 	"""Vypíše výsledky hlasování ve formátu pro prologovský optimalizátor""" | ||||||
| 	# TODO zřejmě se nepoužívá, časem vyřadit? nahradit tabulkou vhodnější pro | # 	# TODO zřejmě se nepoužívá, časem vyřadit? nahradit tabulkou vhodnější pro | ||||||
| 	# lidi? | # 	# lidi? | ||||||
| 	hlasovani = Hlasovani.objects.filter(seznam=seznam) | # 	hlasovani = Hlasovani.objects.filter(seznam=seznam) | ||||||
| 	prednasky = Prednaska.objects.filter(seznamy=seznam) | # 	prednasky = Prednaska.objects.filter(seznamy=seznam) | ||||||
| 	orgove = set(p.org for p in prednasky) | # 	orgove = set(p.org for p in prednasky) | ||||||
| 	ucastnici = set(h.ucastnik for h in hlasovani) | # 	ucastnici = set(h.ucastnik for h in hlasovani) | ||||||
|  | # | ||||||
|  | # 	for p in prednasky: | ||||||
|  | # 		p.body = [] | ||||||
|  | # 		for u in ucastnici: | ||||||
|  | # 			try: | ||||||
|  | # 				p.body.append(hlasovani.get(ucastnik=u, prednaska=p).body) | ||||||
|  | # 			except ObjectDoesNotExist: | ||||||
|  | # 				# účastník nehlasoval | ||||||
|  | # 				p.body.append("?") | ||||||
|  | # | ||||||
|  | # 	for h in hlasovani: | ||||||
|  | # 		h.ucastnik = hash(h.ucastnik) | ||||||
|  | # | ||||||
|  | # 	return render( | ||||||
|  | # 		request, | ||||||
|  | # 		'prednasky/seznam_prednasek_export.txt', | ||||||
|  | # 		{"hlasovani": hlasovani, "prednasky": prednasky, "orgove": orgove}, | ||||||
|  | # 		content_type="text/plain" | ||||||
|  | # 	) | ||||||
| 
 | 
 | ||||||
| 	for p in prednasky: | 
 | ||||||
| 		p.body = [] | def PrednaskyExportView(request: HttpRequest, seznam: int, **kwargs) -> HttpResponse: | ||||||
| 		for u in ucastnici: | 	""" | ||||||
| 			try: | 		Vrátí všechna :py:class:`Hlasování <prednasky.models.Hlasovani>` | ||||||
| 				p.body.append(hlasovani.get(ucastnik=u, prednaska=p).body) | 		i :py:class:`HlasováníOZnalostech <prednasky.models.HlasovaniOZnalostech>` | ||||||
| 			except ObjectDoesNotExist: | 		v daném :py:class:`Seznamu <prednasky.models.Seznam>` | ||||||
| 				# účastník nehlasoval | 		jako csv soubor (řádky = účastníci, sloupce = přednášky&znalosti). | ||||||
| 				p.body.append("?") | 
 | ||||||
|  | 		:param seznam: ID daného :py:class:`Seznamu <prednasky.models.Seznam>` | ||||||
|  | 	""" | ||||||
|  | 	hlasovani = Hlasovani.objects.filter(seznam=seznam).select_related("prednaska") | ||||||
|  | 	hlasovani_o_znalostech = HlasovaniOZnalostech.objects.filter(seznam=seznam).select_related('ucastnik', 'znalost') | ||||||
|  | 
 | ||||||
|  | 	# Inicializujeme sloupce | ||||||
|  | 	prednasky = list(Prednaska.objects.filter(seznamy=seznam)) | ||||||
|  | 	znalosti = list(Znalost.objects.filter(seznamy=seznam)) | ||||||
|  | 
 | ||||||
|  | 	prednasky_map: dict[int, int] = {p.id: i for i, p in enumerate(prednasky, 1)} | ||||||
|  | 	offset = len(prednasky_map) | ||||||
|  | 	znalosti_map: dict[int, int] = {z.id: i for i, z in enumerate(znalosti, offset + 1)} | ||||||
|  | 	width = offset + len(znalosti_map) | ||||||
|  | 
 | ||||||
|  | 	# A po inicializaci sloupců vyplníme tabulku | ||||||
|  | 	table: [str, list[str|Prednaska|Znalost,]] = {} | ||||||
| 
 | 
 | ||||||
| 	for h in hlasovani: | 	for h in hlasovani: | ||||||
| 		h.ucastnik = hash(h.ucastnik) | 		if h.ucastnik not in table: # Pokud jsme účastníka ještě neviděli, předgenerujeme si jeho řádek | ||||||
|  | 			table[h.ucastnik] = [h.ucastnik] + ([""] * width) | ||||||
| 
 | 
 | ||||||
| 	return render( | 		if h.prednaska.id in prednasky_map: | ||||||
| 		request, | 			table[h.ucastnik][prednasky_map[h.prednaska.id]] = h.body | ||||||
| 		'prednasky/seznam_prednasek_export.txt', | 		else: | ||||||
| 		{"hlasovani": hlasovani, "prednasky": prednasky, "orgove": orgove}, | 			pass # TODO Padat hlasitě? | ||||||
| 		content_type="text/plain" | 
 | ||||||
| 	) | 	for h in hlasovani_o_znalostech: | ||||||
|  | 		ucastnik = str(h.ucastnik) + ' ' + str(h.ucastnik.id) # id, kvůli kolizi jmen | ||||||
|  | 		if ucastnik not in table: # Pokud jsme účastníka ještě neviděli, předgenerujeme si jeho řádek | ||||||
|  | 			table[ucastnik] = [ucastnik] + ([""] * width) | ||||||
|  | 
 | ||||||
|  | 		if h.znalost.id in znalosti_map: | ||||||
|  | 			table[ucastnik][znalosti_map[h.znalost.id]] = h.odpoved | ||||||
|  | 		else: | ||||||
|  | 			pass # TODO Padat hlasitě? | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 	response = HttpResponse(content_type="text/csv", charset="utf-8") | ||||||
|  | 	response["Content-Disposition"] = 'attachment; filename="hlasovani.csv"' | ||||||
|  | 
 | ||||||
|  | 	writer = csv.writer(response) | ||||||
|  | 	writer.writerow(["jména \\ přednáška|znalost"] + list(map(str, prednasky + znalosti))) | ||||||
|  | 	for row in table.values(): | ||||||
|  | 		writer.writerow(list(map(str, row))) | ||||||
|  | 	return response | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -17,7 +17,7 @@ django-solo # Singleton model (speciálně Nastavení) | ||||||
| django-ckeditor-5 # Editor htmlka (hlavně v adminu u flatpages) | django-ckeditor-5 # Editor htmlka (hlavně v adminu u flatpages) | ||||||
| django-cleanup  # Uklízí media/ od smazaných „databázových“ souborů | django-cleanup  # Uklízí media/ od smazaných „databázových“ souborů | ||||||
| django-taggit # Taggy v djangu (speciálně zaměření problémů) | django-taggit # Taggy v djangu (speciálně zaměření problémů) | ||||||
| django-autocomplete-light>=3.9.0 # Automatické doplňování (problémů, účastníků, …) ve formulářích | django-autocomplete-light>=3.9.0,<3.12.0 # Automatické doplňování (problémů, účastníků, …) ve formulářích | ||||||
| django-imagekit # Všechny možné obrázky v Djangu | django-imagekit # Všechny možné obrázky v Djangu | ||||||
| django-polymorphic # Polymorfismus na django modelech (hlavně Problém nebo treenode) | django-polymorphic # Polymorfismus na django modelech (hlavně Problém nebo treenode) | ||||||
| django-sitetree # Struktura stránek, hlavně pro meníčko | django-sitetree # Struktura stránek, hlavně pro meníčko | ||||||
|  | @ -49,4 +49,5 @@ lorem | ||||||
| 
 | 
 | ||||||
| sphinx | sphinx | ||||||
| sphinx_rtd_theme | sphinx_rtd_theme | ||||||
|  | sphinxcontrib-django | ||||||
| myst_parser | myst_parser | ||||||
|  |  | ||||||
|  | @ -0,0 +1,27 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2025-01-21 20:54 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations, models | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('personalni', '0019_rename_upozorneni_resitel_upozornovat_na_opravy_reseni'), | ||||||
|  |         ('tvorba', '0007_alter_deadline_typ'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.SeparateDatabaseAndState( | ||||||
|  |             database_operations=[], | ||||||
|  |             state_operations=[ | ||||||
|  |                 migrations.AlterField( | ||||||
|  |                     model_name='problem', | ||||||
|  |                     name='opravovatele', | ||||||
|  |                     field=models.ManyToManyField(blank=True, db_table='seminar_problemy_opravovatele', related_name='opravovatele_%(class)s', to='personalni.organizator', verbose_name='opravovatelé'), | ||||||
|  |                 ), | ||||||
|  |                 migrations.DeleteModel( | ||||||
|  |                     name='Problemy_Opravovatele', | ||||||
|  |                 ), | ||||||
|  |             ] | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
|  | @ -393,20 +393,6 @@ class ZmrazenaVysledkovka(SeminarModelBase): | ||||||
| 
 | 
 | ||||||
| 	html = models.TextField(null=False, blank=False) | 	html = models.TextField(null=False, blank=False) | ||||||
| 
 | 
 | ||||||
| class Problemy_Opravovatele(SeminarModelBase): |  | ||||||
| 	"""Jen vazebná tabulka pro opravovatele. |  | ||||||
| 
 |  | ||||||
| 	Ona stejně existovala, při přesunu mezi aplikacemi jen potřebujeme zajistit nepřejmenování DB tabulky. |  | ||||||
| 	Proto taky nepotřebuje žádná specifika, ze :py:class:SeminarModelBase: dědí ze zvyku než že by to k něčemu kdy měo být. |  | ||||||
| 	""" |  | ||||||
| 	class Meta: |  | ||||||
| 		db_table = 'seminar_problemy_opravovatele' |  | ||||||
| 	 |  | ||||||
| 	id = models.AutoField(primary_key = True) |  | ||||||
| 
 |  | ||||||
| 	problem = models.ForeignKey('Problem', on_delete=models.CASCADE) |  | ||||||
| 	organizator = models.ForeignKey(Organizator, on_delete=models.CASCADE) |  | ||||||
| 
 |  | ||||||
| @reversion.register(ignore_duplicates=True) | @reversion.register(ignore_duplicates=True) | ||||||
| # Pozor na následující řádek. *Nekrmit, asi kouše!* | # Pozor na následující řádek. *Nekrmit, asi kouše!* | ||||||
| class Problem(SeminarModelBase,PolymorphicModel): | class Problem(SeminarModelBase,PolymorphicModel): | ||||||
|  | @ -462,7 +448,7 @@ class Problem(SeminarModelBase,PolymorphicModel): | ||||||
| 		on_delete=models.SET_NULL) | 		on_delete=models.SET_NULL) | ||||||
| 
 | 
 | ||||||
| 	opravovatele = models.ManyToManyField(Organizator, verbose_name='opravovatelé', | 	opravovatele = models.ManyToManyField(Organizator, verbose_name='opravovatelé', | ||||||
| 		blank=True, related_name='opravovatele_%(class)s', through=Problemy_Opravovatele) | 		blank=True, related_name='opravovatele_%(class)s', db_table='seminar_problemy_opravovatele') | ||||||
| 
 | 
 | ||||||
| 	kod = models.CharField('lokální kód', max_length=32, blank=True, default='', | 	kod = models.CharField('lokální kód', max_length=32, blank=True, default='', | ||||||
| 		help_text='Číslo/kód úlohy v čísle nebo kód tématu/článku/seriálu v ročníku') | 		help_text='Číslo/kód úlohy v čísle nebo kód tématu/článku/seriálu v ročníku') | ||||||
|  |  | ||||||
|  | @ -27,7 +27,7 @@ class HromadnePridaniForm(Form): | ||||||
| 	""" Formulář pro hromadné přidání úložek a problémů """ | 	""" Formulář pro hromadné přidání úložek a problémů """ | ||||||
| 
 | 
 | ||||||
| 	tema = CharField(label="Název tématu:") | 	tema = CharField(label="Název tématu:") | ||||||
| 	dil = IntegerField(label="Díl:", min_value=1) | 	cislo = IntegerField(label="Číslo:", min_value=1) | ||||||
| 	body = CharField(label="Počty bodů (0 pro problém) oddělené čárkami:") | 	body = CharField(label="Počty bodů (0 pro problém) oddělené čárkami:") | ||||||
| 
 | 
 | ||||||
| 	def clean_tema(self): | 	def clean_tema(self): | ||||||
|  | @ -41,7 +41,7 @@ class HromadnePridaniForm(Form): | ||||||
| 	def clean_body(self): | 	def clean_body(self): | ||||||
| 		""" Kontrola, že `body` je seznam čísel """ | 		""" Kontrola, že `body` je seznam čísel """ | ||||||
| 		try: | 		try: | ||||||
| 			list(map(int, self.cleaned_data["body"].split(","))) | 			list(map(float, self.cleaned_data["body"].split(","))) | ||||||
| 		except ValueError: | 		except ValueError: | ||||||
| 			raise ValidationError("Špatný formát bodů") | 			raise ValidationError("Špatný formát bodů") | ||||||
| 		return self.cleaned_data['body'] | 		return self.cleaned_data['body'] | ||||||
|  | @ -64,21 +64,21 @@ class HromadnePridaniView(FormView): | ||||||
| 		""" Upravený Pavlův skript na hromadné přidání úložek a problémů. """ | 		""" Upravený Pavlův skript na hromadné přidání úložek a problémů. """ | ||||||
| 		cd = form.cleaned_data | 		cd = form.cleaned_data | ||||||
| 		tema = cd["tema"] | 		tema = cd["tema"] | ||||||
| 		dil = cd["dil"] | 		cislo = cd["cislo"] | ||||||
| 		body = list(map(int, cd["body"].split(","))) | 		body = list(map(float, cd["body"].split(","))) | ||||||
| 
 | 
 | ||||||
| 		t = Problem.objects.get(nazev__exact=tema, nadproblem=None) | 		t = Problem.objects.get(nazev__exact=tema, nadproblem=None) | ||||||
| 		with transaction.atomic(): | 		with transaction.atomic(): | ||||||
| 			pfx = f"{t.nazev}, díl {dil}, " | 			pfx = f"{t.nazev}, " | ||||||
| 
 | 
 | ||||||
| 			for k, b in enumerate(body, 1): | 			for k, b in enumerate(body, 1): | ||||||
| 				u = Uloha.objects.create( | 				u = Uloha.objects.create( | ||||||
| 					nadproblem=t, | 					nadproblem=t, | ||||||
| 					nazev=pfx + f"{'úloha' if b > 0 else 'problém'} {k}", | 					nazev=pfx + f"{'úloha' if b > 0 else 'problém'} {cislo}.{k}", | ||||||
| 					autor=t.autor, | 					autor=t.autor, | ||||||
| 					garant=t.garant, | 					garant=t.garant, | ||||||
| 					max_body=b, | 					max_body=b, | ||||||
| 					cislo_zadani=Cislo.get(t.rocnik.rocnik, dil), | 					cislo_zadani=Cislo.get(t.rocnik.rocnik, cislo), | ||||||
| 					kod=k, | 					kod=k, | ||||||
| 					stav=Problem.STAV_ZADANY, | 					stav=Problem.STAV_ZADANY, | ||||||
| 				) | 				) | ||||||
|  |  | ||||||
							
								
								
									
										20
									
								
								various/migrations/0007_nastaveni_aktualni_sous.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								various/migrations/0007_nastaveni_aktualni_sous.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | ||||||
|  | # Generated by Django 4.2.16 on 2025-01-21 20:34 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations, models | ||||||
|  | import django.db.models.deletion | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('soustredeni', '0013_alter_soustredeni_kontaktnicek_pdf_and_more'), | ||||||
|  |         ('various', '0006_tvorba_post'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name='nastaveni', | ||||||
|  |             name='aktualni_sous', | ||||||
|  |             field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='soustredeni.soustredeni', verbose_name='Aktuálně připravovaný sous'), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
|  | @ -26,6 +26,11 @@ class Nastaveni(SingletonModel): | ||||||
| 									verbose_name="Účastnický poplatek za soustředění", | 									verbose_name="Účastnický poplatek za soustředění", | ||||||
| 									default=1000) | 									default=1000) | ||||||
| 
 | 
 | ||||||
|  | 	aktualni_sous = models.ForeignKey( | ||||||
|  | 		"soustredeni.Soustredeni", verbose_name='Aktuálně připravovaný sous', | ||||||
|  | 		null=True, blank=True, on_delete=models.PROTECT, | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
| 	@property | 	@property | ||||||
| 	def aktualni_rocnik(self): | 	def aktualni_rocnik(self): | ||||||
| 		return self.aktualni_cislo.rocnik | 		return self.aktualni_cislo.rocnik | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue