Merge branch 'data_migrations' into test
This commit is contained in:
		
						commit
						e15425d60a
					
				
					 43 changed files with 5016 additions and 1903 deletions
				
			
		
							
								
								
									
										4
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								Makefile
									
									
									
									
									
								
							|  | @ -34,8 +34,8 @@ install_web: venv_check | ||||||
| 	pip install --upgrade setuptools | 	pip install --upgrade setuptools | ||||||
| 	# Instalace závislostí webu | 	# Instalace závislostí webu | ||||||
| 	pip install -r requirements.txt --upgrade | 	pip install -r requirements.txt --upgrade | ||||||
| 	# Po vygenerování testdat spusť  ./manage.py loaddata sitetree_new.json, ať máš menu | 	# Po vygenerování testdat spusť  ./manage.py loaddata data/*, ať máš menu a další modely | ||||||
| 	# Pro synchronizaci flatpages spusť make sync_prod_flatpages | :x | ||||||
| 
 | 
 | ||||||
| install_venv: | install_venv: | ||||||
| 	${VENV} ${VENV_PATH} | 	${VENV} ${VENV_PATH} | ||||||
|  |  | ||||||
							
								
								
									
										249
									
								
								data/auth_groups.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										249
									
								
								data/auth_groups.json
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,249 @@ | ||||||
|  | [ | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"name": "org", | ||||||
|  | 			"permissions": [ | ||||||
|  | 				23, | ||||||
|  | 				24, | ||||||
|  | 				25, | ||||||
|  | 				1, | ||||||
|  | 				26, | ||||||
|  | 				72, | ||||||
|  | 				73, | ||||||
|  | 				74, | ||||||
|  | 				75, | ||||||
|  | 				76, | ||||||
|  | 				77, | ||||||
|  | 				78, | ||||||
|  | 				79, | ||||||
|  | 				51, | ||||||
|  | 				55, | ||||||
|  | 				52, | ||||||
|  | 				53, | ||||||
|  | 				54, | ||||||
|  | 				56, | ||||||
|  | 				57, | ||||||
|  | 				58, | ||||||
|  | 				59, | ||||||
|  | 				60, | ||||||
|  | 				61, | ||||||
|  | 				62, | ||||||
|  | 				63, | ||||||
|  | 				43, | ||||||
|  | 				44, | ||||||
|  | 				45, | ||||||
|  | 				46, | ||||||
|  | 				228, | ||||||
|  | 				229, | ||||||
|  | 				230, | ||||||
|  | 				231, | ||||||
|  | 				232, | ||||||
|  | 				233, | ||||||
|  | 				234, | ||||||
|  | 				235, | ||||||
|  | 				260, | ||||||
|  | 				261, | ||||||
|  | 				262, | ||||||
|  | 				263, | ||||||
|  | 				264, | ||||||
|  | 				265, | ||||||
|  | 				266, | ||||||
|  | 				267, | ||||||
|  | 				244, | ||||||
|  | 				245, | ||||||
|  | 				246, | ||||||
|  | 				247, | ||||||
|  | 				236, | ||||||
|  | 				237, | ||||||
|  | 				238, | ||||||
|  | 				239, | ||||||
|  | 				240, | ||||||
|  | 				241, | ||||||
|  | 				242, | ||||||
|  | 				243, | ||||||
|  | 				248, | ||||||
|  | 				249, | ||||||
|  | 				250, | ||||||
|  | 				251, | ||||||
|  | 				252, | ||||||
|  | 				253, | ||||||
|  | 				254, | ||||||
|  | 				255, | ||||||
|  | 				256, | ||||||
|  | 				257, | ||||||
|  | 				258, | ||||||
|  | 				259, | ||||||
|  | 				212, | ||||||
|  | 				213, | ||||||
|  | 				214, | ||||||
|  | 				215, | ||||||
|  | 				80, | ||||||
|  | 				81, | ||||||
|  | 				82, | ||||||
|  | 				83, | ||||||
|  | 				180, | ||||||
|  | 				181, | ||||||
|  | 				182, | ||||||
|  | 				183, | ||||||
|  | 				172, | ||||||
|  | 				173, | ||||||
|  | 				174, | ||||||
|  | 				175, | ||||||
|  | 				168, | ||||||
|  | 				169, | ||||||
|  | 				170, | ||||||
|  | 				171, | ||||||
|  | 				132, | ||||||
|  | 				133, | ||||||
|  | 				134, | ||||||
|  | 				135, | ||||||
|  | 				224, | ||||||
|  | 				225, | ||||||
|  | 				226, | ||||||
|  | 				227, | ||||||
|  | 				184, | ||||||
|  | 				185, | ||||||
|  | 				186, | ||||||
|  | 				187, | ||||||
|  | 				112, | ||||||
|  | 				113, | ||||||
|  | 				114, | ||||||
|  | 				115, | ||||||
|  | 				120, | ||||||
|  | 				121, | ||||||
|  | 				122, | ||||||
|  | 				123, | ||||||
|  | 				164, | ||||||
|  | 				165, | ||||||
|  | 				166, | ||||||
|  | 				167, | ||||||
|  | 				124, | ||||||
|  | 				125, | ||||||
|  | 				126, | ||||||
|  | 				127, | ||||||
|  | 				216, | ||||||
|  | 				217, | ||||||
|  | 				218, | ||||||
|  | 				219, | ||||||
|  | 				136, | ||||||
|  | 				137, | ||||||
|  | 				138, | ||||||
|  | 				139, | ||||||
|  | 				152, | ||||||
|  | 				153, | ||||||
|  | 				154, | ||||||
|  | 				155, | ||||||
|  | 				208, | ||||||
|  | 				209, | ||||||
|  | 				210, | ||||||
|  | 				211, | ||||||
|  | 				140, | ||||||
|  | 				141, | ||||||
|  | 				142, | ||||||
|  | 				143, | ||||||
|  | 				108, | ||||||
|  | 				109, | ||||||
|  | 				110, | ||||||
|  | 				111, | ||||||
|  | 				84, | ||||||
|  | 				85, | ||||||
|  | 				86, | ||||||
|  | 				87, | ||||||
|  | 				104, | ||||||
|  | 				105, | ||||||
|  | 				106, | ||||||
|  | 				107, | ||||||
|  | 				160, | ||||||
|  | 				161, | ||||||
|  | 				162, | ||||||
|  | 				163, | ||||||
|  | 				220, | ||||||
|  | 				221, | ||||||
|  | 				222, | ||||||
|  | 				223, | ||||||
|  | 				88, | ||||||
|  | 				89, | ||||||
|  | 				90, | ||||||
|  | 				91, | ||||||
|  | 				92, | ||||||
|  | 				93, | ||||||
|  | 				94, | ||||||
|  | 				95, | ||||||
|  | 				188, | ||||||
|  | 				189, | ||||||
|  | 				190, | ||||||
|  | 				191, | ||||||
|  | 				96, | ||||||
|  | 				97, | ||||||
|  | 				98, | ||||||
|  | 				99, | ||||||
|  | 				100, | ||||||
|  | 				101, | ||||||
|  | 				102, | ||||||
|  | 				103, | ||||||
|  | 				128, | ||||||
|  | 				129, | ||||||
|  | 				130, | ||||||
|  | 				131, | ||||||
|  | 				116, | ||||||
|  | 				117, | ||||||
|  | 				118, | ||||||
|  | 				119, | ||||||
|  | 				156, | ||||||
|  | 				157, | ||||||
|  | 				158, | ||||||
|  | 				159, | ||||||
|  | 				192, | ||||||
|  | 				193, | ||||||
|  | 				194, | ||||||
|  | 				195, | ||||||
|  | 				144, | ||||||
|  | 				145, | ||||||
|  | 				146, | ||||||
|  | 				147, | ||||||
|  | 				196, | ||||||
|  | 				197, | ||||||
|  | 				198, | ||||||
|  | 				199, | ||||||
|  | 				176, | ||||||
|  | 				177, | ||||||
|  | 				178, | ||||||
|  | 				179, | ||||||
|  | 				148, | ||||||
|  | 				149, | ||||||
|  | 				150, | ||||||
|  | 				151, | ||||||
|  | 				200, | ||||||
|  | 				201, | ||||||
|  | 				202, | ||||||
|  | 				203, | ||||||
|  | 				204, | ||||||
|  | 				205, | ||||||
|  | 				206, | ||||||
|  | 				207, | ||||||
|  | 				64, | ||||||
|  | 				65, | ||||||
|  | 				66, | ||||||
|  | 				67, | ||||||
|  | 				68, | ||||||
|  | 				69, | ||||||
|  | 				70, | ||||||
|  | 				71, | ||||||
|  | 				35, | ||||||
|  | 				36, | ||||||
|  | 				37, | ||||||
|  | 				38, | ||||||
|  | 				39, | ||||||
|  | 				40, | ||||||
|  | 				41, | ||||||
|  | 				42, | ||||||
|  | 				47, | ||||||
|  | 				48, | ||||||
|  | 				49, | ||||||
|  | 				50 | ||||||
|  | 			] | ||||||
|  | 		}, | ||||||
|  | 		"model": "auth.group", | ||||||
|  | 		"pk": 1 | ||||||
|  | 	} | ||||||
|  | ] | ||||||
							
								
								
									
										2441
									
								
								data/auth_permissions.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2441
									
								
								data/auth_permissions.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										386
									
								
								data/flat.json
									
									
									
									
									
								
							
							
						
						
									
										386
									
								
								data/flat.json
									
									
									
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										786
									
								
								data/sitetree.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										786
									
								
								data/sitetree.json
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,786 @@ | ||||||
|  | [ | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"alias": "main_menu", | ||||||
|  | 			"title": "Hlavní menu" | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.tree", | ||||||
|  | 		"pk": 1 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": null, | ||||||
|  | 			"sort_order": 1, | ||||||
|  | 			"title": "Co je M&M", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "/co-je-MaM/uvod/", | ||||||
|  | 			"urlaspattern": false | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 1 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": null, | ||||||
|  | 			"sort_order": 2, | ||||||
|  | 			"title": "Jak řešit", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "/jak-resit/", | ||||||
|  | 			"urlaspattern": false | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 2 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": null, | ||||||
|  | 			"sort_order": 3, | ||||||
|  | 			"title": "Aktuální<br/> ročník", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "seminar_aktualni_zadani", | ||||||
|  | 			"urlaspattern": true | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 3 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": null, | ||||||
|  | 			"sort_order": 4, | ||||||
|  | 			"title": "Soustředění", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "/soustredeni/", | ||||||
|  | 			"urlaspattern": false | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 4 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": null, | ||||||
|  | 			"sort_order": 5, | ||||||
|  | 			"title": "Archiv", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "seminar_archiv_rocniky", | ||||||
|  | 			"urlaspattern": true | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 5 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": true, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": null, | ||||||
|  | 			"sort_order": 6, | ||||||
|  | 			"title": "Přihlásit", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "login", | ||||||
|  | 			"urlaspattern": true | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 6 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": 1, | ||||||
|  | 			"sort_order": 7, | ||||||
|  | 			"title": "Úvod", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "/co-je-MaM/uvod/", | ||||||
|  | 			"urlaspattern": false | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 7 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": 1, | ||||||
|  | 			"sort_order": 8, | ||||||
|  | 			"title": "Organizátoři", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "organizatori", | ||||||
|  | 			"urlaspattern": true | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 8 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": 1, | ||||||
|  | 			"sort_order": 9, | ||||||
|  | 			"title": "FAQ", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "/co-je-MaM/FAQ/", | ||||||
|  | 			"urlaspattern": false | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 9 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": 1, | ||||||
|  | 			"sort_order": 10, | ||||||
|  | 			"title": "Kontakt", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "/co-je-MaM/kontakt/", | ||||||
|  | 			"urlaspattern": false | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 10 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": 2, | ||||||
|  | 			"sort_order": 11, | ||||||
|  | 			"title": "Témata", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "/jak-resit/", | ||||||
|  | 			"urlaspattern": false | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 11 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": 2, | ||||||
|  | 			"sort_order": 12, | ||||||
|  | 			"title": "Jak psát příspěvek", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "/jak-resit/jak-psat-prispevek/", | ||||||
|  | 			"urlaspattern": false | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 12 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": 2, | ||||||
|  | 			"sort_order": 13, | ||||||
|  | 			"title": "Odměny", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "/co-je-MaM/odmeny/", | ||||||
|  | 			"urlaspattern": false | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 13 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": 3, | ||||||
|  | 			"sort_order": 33, | ||||||
|  | 			"title": "Výsledková listina", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "seminar_aktualni_vysledky", | ||||||
|  | 			"urlaspattern": true | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 16 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": 4, | ||||||
|  | 			"sort_order": 18, | ||||||
|  | 			"title": "Úvod", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "/soustredeni/", | ||||||
|  | 			"urlaspattern": false | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 18 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": 4, | ||||||
|  | 			"sort_order": 19, | ||||||
|  | 			"title": "Připravujeme", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "/soustredeni/pripravujeme/", | ||||||
|  | 			"urlaspattern": false | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 19 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": 4, | ||||||
|  | 			"sort_order": 20, | ||||||
|  | 			"title": "Proběhlo", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "seminar_seznam_soustredeni", | ||||||
|  | 			"urlaspattern": true | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 20 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": true, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": null, | ||||||
|  | 			"sort_order": 21, | ||||||
|  | 			"title": "Profil", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "profil", | ||||||
|  | 			"urlaspattern": true | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 21 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": 21, | ||||||
|  | 			"sort_order": 23, | ||||||
|  | 			"title": "Osobní údaje", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "seminar_resitel_edit", | ||||||
|  | 			"urlaspattern": true | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 22 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [ | ||||||
|  | 				2 | ||||||
|  | 			], | ||||||
|  | 			"access_restricted": true, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": 21, | ||||||
|  | 			"sort_order": 36, | ||||||
|  | 			"title": "Poslat řešení", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "seminar_nahraj_reseni", | ||||||
|  | 			"urlaspattern": true | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 23 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": 5, | ||||||
|  | 			"sort_order": 35, | ||||||
|  | 			"title": "Témata", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "seminar_archiv_temata", | ||||||
|  | 			"urlaspattern": true | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 24 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [ | ||||||
|  | 				1 | ||||||
|  | 			], | ||||||
|  | 			"access_restricted": true, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": null, | ||||||
|  | 			"sort_order": 28, | ||||||
|  | 			"title": "HIDDEN", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "/korektury/", | ||||||
|  | 			"urlaspattern": false | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 28 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": 28, | ||||||
|  | 			"sort_order": 30, | ||||||
|  | 			"title": "Aktuální", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "korektury_list", | ||||||
|  | 			"urlaspattern": true | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 30 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": 28, | ||||||
|  | 			"sort_order": 31, | ||||||
|  | 			"title": "Zastaralé", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "korektury_stare_list", | ||||||
|  | 			"urlaspattern": true | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 31 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": 28, | ||||||
|  | 			"sort_order": 32, | ||||||
|  | 			"title": "Nápověda", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "/korektury/help/", | ||||||
|  | 			"urlaspattern": false | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 32 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": 3, | ||||||
|  | 			"sort_order": 15, | ||||||
|  | 			"title": "Aktuální číslo", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "seminar_aktualni_zadani", | ||||||
|  | 			"urlaspattern": true | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 33 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": 5, | ||||||
|  | 			"sort_order": 24, | ||||||
|  | 			"title": "Čísla", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "seminar_archiv_rocniky", | ||||||
|  | 			"urlaspattern": true | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 35 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": 21, | ||||||
|  | 			"sort_order": 22, | ||||||
|  | 			"title": "Úvod", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "profil", | ||||||
|  | 			"urlaspattern": true | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 36 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [ | ||||||
|  | 				1 | ||||||
|  | 			], | ||||||
|  | 			"access_restricted": true, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": 21, | ||||||
|  | 			"sort_order": 37, | ||||||
|  | 			"title": "Odevzdaná řešení", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "odevzdavatko_tabulka", | ||||||
|  | 			"urlaspattern": true | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 37 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": true, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": 21, | ||||||
|  | 			"sort_order": 38, | ||||||
|  | 			"title": "Odhlásit se", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "/logout/", | ||||||
|  | 			"urlaspattern": false | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 38 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [ | ||||||
|  | 				1 | ||||||
|  | 			], | ||||||
|  | 			"access_restricted": true, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": 21, | ||||||
|  | 			"sort_order": 36, | ||||||
|  | 			"title": "Nahrát řešení", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "seminar_vloz_reseni", | ||||||
|  | 			"urlaspattern": true | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 39 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fields": { | ||||||
|  | 			"access_guest": false, | ||||||
|  | 			"access_loggedin": false, | ||||||
|  | 			"access_perm_type": 1, | ||||||
|  | 			"access_permissions": [], | ||||||
|  | 			"access_restricted": false, | ||||||
|  | 			"alias": null, | ||||||
|  | 			"description": "", | ||||||
|  | 			"hidden": false, | ||||||
|  | 			"hint": "", | ||||||
|  | 			"inbreadcrumbs": true, | ||||||
|  | 			"inmenu": true, | ||||||
|  | 			"insitetree": true, | ||||||
|  | 			"parent": 5, | ||||||
|  | 			"sort_order": 40, | ||||||
|  | 			"title": "Řešitelské články", | ||||||
|  | 			"tree": 1, | ||||||
|  | 			"url": "/archiv/clanky", | ||||||
|  | 			"urlaspattern": false | ||||||
|  | 		}, | ||||||
|  | 		"model": "sitetree.treeitem", | ||||||
|  | 		"pk": 40 | ||||||
|  | 	} | ||||||
|  | ] | ||||||
|  | @ -1,816 +0,0 @@ | ||||||
| [ |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.tree", |  | ||||||
|         "pk": 1, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Hlavní menu", |  | ||||||
|             "alias": "main_menu" |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 1, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Co je M&M", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "/co-je-MaM/uvod/", |  | ||||||
|             "urlaspattern": false, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": null, |  | ||||||
|             "sort_order": 1, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 2, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Jak řešit", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "/jak-resit/", |  | ||||||
|             "urlaspattern": false, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": null, |  | ||||||
|             "sort_order": 2, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 3, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Aktuální<br/> ročník", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "seminar_aktualni_zadani", |  | ||||||
|             "urlaspattern": true, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": null, |  | ||||||
|             "sort_order": 3, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 4, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Soustředění", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "/soustredeni/", |  | ||||||
|             "urlaspattern": false, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": null, |  | ||||||
|             "sort_order": 4, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 5, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Archiv", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "seminar_archiv_rocniky", |  | ||||||
|             "urlaspattern": true, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": null, |  | ||||||
|             "sort_order": 5, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 6, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Přihlásit", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "login", |  | ||||||
|             "urlaspattern": true, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": true, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": null, |  | ||||||
|             "sort_order": 6, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 7, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Úvod", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "/co-je-MaM/uvod/", |  | ||||||
|             "urlaspattern": false, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": 1, |  | ||||||
|             "sort_order": 7, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 8, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Organizátoři", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "organizatori", |  | ||||||
|             "urlaspattern": true, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": 1, |  | ||||||
|             "sort_order": 8, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 9, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "FAQ", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "/co-je-MaM/FAQ/", |  | ||||||
|             "urlaspattern": false, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": 1, |  | ||||||
|             "sort_order": 9, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 10, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Kontakt", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "/co-je-MaM/kontakt/", |  | ||||||
|             "urlaspattern": false, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": 1, |  | ||||||
|             "sort_order": 10, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 11, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Témata", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "/jak-resit/", |  | ||||||
|             "urlaspattern": false, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": 2, |  | ||||||
|             "sort_order": 11, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 12, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Jak psát příspěvek", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "/jak-resit/jak-psat-prispevek/", |  | ||||||
|             "urlaspattern": false, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": 2, |  | ||||||
|             "sort_order": 12, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 13, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Odměny", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "/co-je-MaM/odmeny/", |  | ||||||
|             "urlaspattern": false, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": 2, |  | ||||||
|             "sort_order": 13, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 16, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Výsledková listina", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "zadani/vysledkova-listina/", |  | ||||||
|             "urlaspattern": false, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": 3, |  | ||||||
|             "sort_order": 33, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 17, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Články", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "clanky_resitel", |  | ||||||
|             "urlaspattern": true, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": 3, |  | ||||||
|             "sort_order": 34, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 18, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Úvod", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "/soustredeni/", |  | ||||||
|             "urlaspattern": false, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": 4, |  | ||||||
|             "sort_order": 18, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 19, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Připravujeme", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "/soustredeni/pripravujeme/", |  | ||||||
|             "urlaspattern": false, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": 4, |  | ||||||
|             "sort_order": 19, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 20, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Proběhlo", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "seminar_seznam_soustredeni", |  | ||||||
|             "urlaspattern": true, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": 4, |  | ||||||
|             "sort_order": 20, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 21, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Profil", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "profil", |  | ||||||
|             "urlaspattern": true, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": true, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": null, |  | ||||||
|             "sort_order": 21, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 22, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Osobní údaje", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "seminar_resitel_edit", |  | ||||||
|             "urlaspattern": true, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": 21, |  | ||||||
|             "sort_order": 23, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 23, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Poslat řešení", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "seminar_nahraj_reseni", |  | ||||||
|             "urlaspattern": true, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": 21, |  | ||||||
|             "sort_order": 36, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 24, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Témata", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "seminar_temata", |  | ||||||
|             "urlaspattern": true, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": 5, |  | ||||||
|             "sort_order": 35, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 28, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "HIDDEN", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "/korektury/", |  | ||||||
|             "urlaspattern": false, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": true, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": null, |  | ||||||
|             "sort_order": 28, |  | ||||||
|             "access_permissions": [ |  | ||||||
|                 1 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 30, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Aktuální", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "korektury_list", |  | ||||||
|             "urlaspattern": true, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": 28, |  | ||||||
|             "sort_order": 30, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 31, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Zastaralé", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "korektury_stare_list", |  | ||||||
|             "urlaspattern": true, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": 28, |  | ||||||
|             "sort_order": 31, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 32, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Nápověda", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "/korektury/help/", |  | ||||||
|             "urlaspattern": false, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": 28, |  | ||||||
|             "sort_order": 32, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 33, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Aktuální číslo", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "seminar_aktualni_zadani", |  | ||||||
|             "urlaspattern": true, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": 3, |  | ||||||
|             "sort_order": 15, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 34, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Témata", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "seminar_temata", |  | ||||||
|             "urlaspattern": true, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": 3, |  | ||||||
|             "sort_order": 17, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 35, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Čísla", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "seminar_archiv_rocniky", |  | ||||||
|             "urlaspattern": true, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": 5, |  | ||||||
|             "sort_order": 24, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 36, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Úvod", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "profil", |  | ||||||
|             "urlaspattern": true, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": false, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": 21, |  | ||||||
|             "sort_order": 22, |  | ||||||
|             "access_permissions": [ |  | ||||||
| 
 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         "model": "sitetree.treeitem", |  | ||||||
|         "pk": 37, |  | ||||||
|         "fields": { |  | ||||||
|             "title": "Odevzdaná řešení", |  | ||||||
|             "hint": "", |  | ||||||
|             "url": "odevzdavatko_tabulka", |  | ||||||
|             "urlaspattern": true, |  | ||||||
|             "tree": 1, |  | ||||||
|             "hidden": false, |  | ||||||
|             "alias": null, |  | ||||||
|             "description": "", |  | ||||||
|             "inmenu": true, |  | ||||||
|             "inbreadcrumbs": true, |  | ||||||
|             "insitetree": true, |  | ||||||
|             "access_loggedin": false, |  | ||||||
|             "access_guest": false, |  | ||||||
|             "access_restricted": true, |  | ||||||
|             "access_perm_type": 1, |  | ||||||
|             "parent": 21, |  | ||||||
|             "sort_order": 37, |  | ||||||
|             "access_permissions": [ |  | ||||||
|                 1 |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| ] |  | ||||||
							
								
								
									
										12
									
								
								fix_json.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										12
									
								
								fix_json.py
									
									
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,12 @@ | ||||||
|  | #!/usr/bin/python3 | ||||||
|  | 
 | ||||||
|  | import json | ||||||
|  | import argparse | ||||||
|  | 
 | ||||||
|  | parser = argparse.ArgumentParser() | ||||||
|  | parser.add_argument("input", type=argparse.FileType('r', encoding='utf-8')) | ||||||
|  | parser.add_argument('output', type=argparse.FileType('w', encoding='utf-8')) | ||||||
|  | args = parser.parse_args() | ||||||
|  | 
 | ||||||
|  | data = json.load(args.input) | ||||||
|  | json.dump(data, args.output, ensure_ascii=False, sort_keys=True, indent='\t') | ||||||
|  | @ -186,7 +186,7 @@ class KorekturyView(generic.TemplateView): | ||||||
| 		if email: | 		if email: | ||||||
| 			emails.discard(email) | 			emails.discard(email) | ||||||
| 
 | 
 | ||||||
| 		if not settings.SEND_EMAIL_NOTIFICATIONS: | 		if not settings.POSLI_MAILOVOU_NOTIFIKACI: | ||||||
| 			print("Poslal bych upozornění na tyto adresy: ", " ".join(emails)) | 			print("Poslal bych upozornění na tyto adresy: ", " ".join(emails)) | ||||||
| 			return | 			return | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -308,4 +308,4 @@ CISLO_IMG_DIR = os.path.join('cislo', 'img') | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # E-MAIL NOTIFICATIONS | # E-MAIL NOTIFICATIONS | ||||||
| SEND_EMAIL_NOTIFICATIONS = False | POSLI_MAILOVOU_NOTIFIKACI = False | ||||||
|  |  | ||||||
|  | @ -66,4 +66,4 @@ LOGGING['handlers']['registration_error_log']['filename'] = '/home/mam-web/logs/ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # E-MAIL NOTIFICATIONS | # E-MAIL NOTIFICATIONS | ||||||
| SEND_EMAIL_NOTIFICATIONS = True | POSLI_MAILOVOU_NOTIFIKACI = True | ||||||
|  |  | ||||||
|  | @ -9,10 +9,15 @@ body { | ||||||
| 	background-color: #fffbf6; | 	background-color: #fffbf6; | ||||||
| 	min-height: 100%; | 	min-height: 100%; | ||||||
| } | } | ||||||
|  | div.content-wrapper { | ||||||
|  | 	padding-bottom: 200px; /* Footer height */ | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| div.container { | div.container { | ||||||
| 	width: 970px; | 	width: 970px; | ||||||
| 	margin: auto; | 	margin: auto; | ||||||
|  | 	min-height: 100vh; | ||||||
|  | 	position: relative; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .org-logged-in div.container { | .org-logged-in div.container { | ||||||
|  | @ -321,11 +326,7 @@ div.novinky_name { | ||||||
| 	font-style: italic; | 	font-style: italic; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| div.zadani_azad_termin { | 
 | ||||||
| 	text-align: center; |  | ||||||
| 	font-size: large; |  | ||||||
| 	font-weight: bold |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| /********** | /********** | ||||||
|  * Footer |  * Footer | ||||||
|  | @ -333,6 +334,9 @@ div.zadani_azad_termin { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| #footer { | #footer { | ||||||
|  | 	position: absolute; | ||||||
|  |   	bottom: 0; | ||||||
|  |     width: 100%; | ||||||
| 	background: url("../images/mozaika-footer.svg") no-repeat top center; | 	background: url("../images/mozaika-footer.svg") no-repeat top center; | ||||||
| 	height: 200px; | 	height: 200px; | ||||||
| 	background-position: relative; | 	background-position: relative; | ||||||
|  | @ -353,6 +357,7 @@ div.zadani_azad_termin { | ||||||
| 
 | 
 | ||||||
| p.license-mobile { | p.license-mobile { | ||||||
| 	display: none; | 	display: none; | ||||||
|  | 	margin-bottom: 0px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /*********************/ | /*********************/ | ||||||
|  | @ -390,8 +395,8 @@ input[type="file"] { | ||||||
|   border-width:1px; |   border-width:1px; | ||||||
| 	border-radius: 5px; | 	border-radius: 5px; | ||||||
|   padding:3px; |   padding:3px; | ||||||
|   top:20px; |   top:50px; | ||||||
|   left:20px; |   left:10px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .field-with-comment:hover span.field-comment{ | .field-with-comment:hover span.field-comment{ | ||||||
|  | @ -763,7 +768,7 @@ div.odpocet { | ||||||
| 
 | 
 | ||||||
| /*stránka organizátorů*/ | /*stránka organizátorů*/ | ||||||
| 
 | 
 | ||||||
| div.seznam_orgu, div.rozcestnik_temat { | div.seznam_orgu, div.rozcestnik_temat, div.seznam_archiv { | ||||||
| 	text-align: center; | 	text-align: center; | ||||||
| 	padding-bottom: 10px; | 	padding-bottom: 10px; | ||||||
| } | } | ||||||
|  | @ -897,6 +902,38 @@ div.cislo_odkazy ul { | ||||||
| 		padding: 0px; | 		padding: 0px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /* aktuální zadání */ | ||||||
|  | .stranka_aktualni_zadani { | ||||||
|  | 	text-align: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #azad_obrazek { | ||||||
|  | 	margin-top: 15px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | div.zadani_termin { | ||||||
|  | 	text-align: center; | ||||||
|  | 	font-size: large; | ||||||
|  | 	font-weight: bold; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @media (max-width: 420px) { | ||||||
|  | 	div.zadani_termin { | ||||||
|  | 		font-size: small; | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | div.zadani_termin .datum { | ||||||
|  | 	color:#e84e10; | ||||||
|  | 	margin:0px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #obrazek_cisla_archiv { | ||||||
|  | 	text-align: center; | ||||||
|  | 	margin: 10px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| /* galerie */ | /* galerie */ | ||||||
| 
 | 
 | ||||||
|  | @ -961,8 +998,6 @@ div.cislo_odkazy ul { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* titulní obrázek hlavní galerie soustředění */ | /* titulní obrázek hlavní galerie soustředění */ | ||||||
| .titulni_obrazek { |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| .galerie_nahledy{ | .galerie_nahledy{ | ||||||
|   /*margin: 1em 0;*/ |   /*margin: 1em 0;*/ | ||||||
|  |  | ||||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 3.2 MiB After Width: | Height: | Size: 476 KiB | 
|  | @ -31,6 +31,10 @@ | ||||||
|       src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.6/MathJax.js?config=TeX-AMS-MML_HTMLorMML"> |       src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.6/MathJax.js?config=TeX-AMS-MML_HTMLorMML"> | ||||||
|     </script> |     </script> | ||||||
| 
 | 
 | ||||||
|  |     {# Případné skripty widgetů formulářů #} | ||||||
|  |     {% if form %} | ||||||
|  |         {{form.media}} | ||||||
|  |     {% endif %} | ||||||
|     {# script specifický pro stránku #} |     {# script specifický pro stránku #} | ||||||
|     {% block script %}{% endblock %} |     {% block script %}{% endblock %} | ||||||
| 
 | 
 | ||||||
|  | @ -52,9 +56,8 @@ | ||||||
|     {% endif %} |     {% endif %} | ||||||
| 
 | 
 | ||||||
|    <div class="container"> |    <div class="container"> | ||||||
|  |    <div class="content-wrapper"> | ||||||
| 
 | 
 | ||||||
| 	  <div class='row'> |  | ||||||
| 		<div class='col-md-12'> |  | ||||||
|           <a href='/'> |           <a href='/'> | ||||||
| 	    <div id="title" >M&M - korespondenční seminář a časopis MFF UK</div> | 	    <div id="title" >M&M - korespondenční seminář a časopis MFF UK</div> | ||||||
|             <div id="header"> |             <div id="header"> | ||||||
|  | @ -65,11 +68,7 @@ | ||||||
| 		<img class="logo-mobile" src="{% static 'images/logo-mobile.svg' %}" /> | 		<img class="logo-mobile" src="{% static 'images/logo-mobile.svg' %}" /> | ||||||
|             </div> |             </div> | ||||||
|             </a> |             </a> | ||||||
|         </div> |  | ||||||
|       </div> |  | ||||||
| 
 | 
 | ||||||
|       <div class='row'> |  | ||||||
| 	    <div class='col-md-12'> |  | ||||||
| 
 | 
 | ||||||
| {# ========= MENU ========== #} | {# ========= MENU ========== #} | ||||||
| 
 | 
 | ||||||
|  | @ -77,8 +76,6 @@ | ||||||
| 
 | 
 | ||||||
| {# ========= MENU MOBILE ========== #} | {# ========= MENU MOBILE ========== #} | ||||||
| 
 | 
 | ||||||
|         </div> |  | ||||||
|       </div> |  | ||||||
| 
 | 
 | ||||||
| <!--Navbar--> | <!--Navbar--> | ||||||
| <nav class="nav-button"> | <nav class="nav-button"> | ||||||
|  | @ -102,25 +99,23 @@ | ||||||
| 
 | 
 | ||||||
| {# ========= END MENU ========== #} | {# ========= END MENU ========== #} | ||||||
| 
 | 
 | ||||||
|     <div class='row'> | 
 | ||||||
| 	<div class='row content'> | 	<div class='content'> | ||||||
| 	<div class='col-md-12'> | 
 | ||||||
| 	    {% block content %} | 	    {% block content %} | ||||||
| 	    {% endblock content %} | 	    {% endblock content %} | ||||||
| 	        </div> | 	        </div> | ||||||
|         </div> |  | ||||||
|     </div> |  | ||||||
| 
 | 
 | ||||||
| 	<div class='row'> | 
 | ||||||
| 	  <div class='col-md-12'> |    </div> <!-- content-wrapper --> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 	   <!-- Credit: https://www.freecodecamp.org/news/how-to-keep-your-footer-where-it-belongs-59c6aa05c59c/ --> | ||||||
| 		<div id="footer"> | 		<div id="footer"> | ||||||
| 			<p class="license">S obsahem webu M&M je možné nakládat dle licence <a href="https://creativecommons.org/licenses/by/3.0/cz/">Creative Commons Attribution 3.0</a>.</p> | 			<p class="license">S obsahem webu M&M je možné nakládat dle licence <a href="https://creativecommons.org/licenses/by/3.0/cz/">Creative Commons Attribution 3.0</a>.</p> | ||||||
|     </div> |     </div> | ||||||
| 		<p class="license-mobile">Korespondenční seminář M&M organizují převážně studenti <a href="https://www.mff.cuni.cz/">MFF UK</a>. Organizaci semináře a vydávání časopisu podporuje <a href="https://jcmf.cz/">Jednota českých matematiků a fyziků</a>. S obsahem webu M&M je možné nakládat dle licence <a href="https://creativecommons.org/licenses/by/3.0/cz/">Creative Commons Attribution 3.0</a>.</p> | 		<p class="license-mobile">Korespondenční seminář M&M organizují převážně studenti <a href="https://www.mff.cuni.cz/">MFF UK</a>. Organizaci semináře a vydávání časopisu podporuje <a href="https://jcmf.cz/">Jednota českých matematiků a fyziků</a>. S obsahem webu M&M je možné nakládat dle licence <a href="https://creativecommons.org/licenses/by/3.0/cz/">Creative Commons Attribution 3.0</a>.</p> | ||||||
| 	  </div> |  | ||||||
| 	</div> |  | ||||||
|      |      | ||||||
|     </div> |  | ||||||
| 
 | 
 | ||||||
|     <script src="{% static 'js/bootstrap.js' %}"></script> |     <script src="{% static 'js/bootstrap.js' %}"></script> | ||||||
|     <script src="{% static 'js/jquery.jcarousel-core.js' %}" type="text/javascript"></script> |     <script src="{% static 'js/jquery.jcarousel-core.js' %}" type="text/javascript"></script> | ||||||
|  |  | ||||||
|  | @ -350,7 +350,7 @@ | ||||||
|     </a> |     </a> | ||||||
|     <a |     <a | ||||||
|        id="casopis" |        id="casopis" | ||||||
|        href="/archiv/cisla/" |        href="/archiv/rocniky" | ||||||
|        transform="matrix(0.70138313,0,0,0.7462289,-192.38886,20.298351)"> |        transform="matrix(0.70138313,0,0,0.7462289,-192.38886,20.298351)"> | ||||||
|       <g |       <g | ||||||
|          id="g959"> |          id="g959"> | ||||||
|  | @ -379,7 +379,7 @@ | ||||||
|       </g> |       </g> | ||||||
|     </a> |     </a> | ||||||
|     <a |     <a | ||||||
|        href="/clanky/uvod/" |        href="/jak-resit" | ||||||
|        id="clanky" |        id="clanky" | ||||||
|        transform="matrix(0.70138313,0,0,0.7462289,-192.38886,20.298351)"> |        transform="matrix(0.70138313,0,0,0.7462289,-192.38886,20.298351)"> | ||||||
|       <g |       <g | ||||||
|  |  | ||||||
| Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB | 
|  | @ -3,7 +3,7 @@ | ||||||
| {% autoescape off %} | {% autoescape off %} | ||||||
| <ul class="menu"> | <ul class="menu"> | ||||||
|     {% for item in sitetree_items %} |     {% for item in sitetree_items %} | ||||||
|         <li class="{% if item.has_children %}dropdown{% endif %} {% if item.is_current or item.in_current_branch %}active{% endif %}" |         <li class="dropdown {% if item.is_current or item.in_current_branch %}active{% endif %}" | ||||||
|             style="{% if item.title == "HIDDEN" %}display:none{% endif %}" |             style="{% if item.title == "HIDDEN" %}display:none{% endif %}" | ||||||
|         > |         > | ||||||
|             <a href="{% sitetree_url for item %}" > |             <a href="{% sitetree_url for item %}" > | ||||||
|  |  | ||||||
|  | @ -1,11 +1,14 @@ | ||||||
| from django.contrib import admin | from django.contrib import admin | ||||||
| from django.contrib.auth.models import Permission | from django.contrib.auth.models import Group | ||||||
|  | from django.db import models | ||||||
|  | from django.forms import widgets | ||||||
| 
 | 
 | ||||||
| from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter | from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter | ||||||
| from reversion.admin import VersionAdmin | from reversion.admin import VersionAdmin | ||||||
| from django_reverse_admin import ReverseModelAdmin | from django_reverse_admin import ReverseModelAdmin | ||||||
| from solo.admin import SingletonModelAdmin | from solo.admin import SingletonModelAdmin | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| # Todo: reversion | # Todo: reversion | ||||||
| 
 | 
 | ||||||
| import seminar.models as m | import seminar.models as m | ||||||
|  | @ -14,8 +17,6 @@ admin.site.register(m.Skola) | ||||||
| admin.site.register(m.Prijemce) | admin.site.register(m.Prijemce) | ||||||
| admin.site.register(m.Rocnik) | admin.site.register(m.Rocnik) | ||||||
| admin.site.register(m.Cislo) | admin.site.register(m.Cislo) | ||||||
| admin.site.register(m.Organizator) |  | ||||||
| admin.site.register(m.Soustredeni) |  | ||||||
| 
 | 
 | ||||||
| @admin.register(m.Osoba) | @admin.register(m.Osoba) | ||||||
| class OsobaAdmin(admin.ModelAdmin): | class OsobaAdmin(admin.ModelAdmin): | ||||||
|  | @ -31,18 +32,26 @@ class OsobaAdmin(admin.ModelAdmin): | ||||||
| 	synchronizuj_maily.short_description = "Synchronizuj vybraným osobám e-maily do uživatelů" | 	synchronizuj_maily.short_description = "Synchronizuj vybraným osobám e-maily do uživatelů" | ||||||
| 
 | 
 | ||||||
| 	def udelej_orgem(self,request,queryset): | 	def udelej_orgem(self,request,queryset): | ||||||
| 		org_perm = Permission.objects.filter(codename__exact='org').first() | 		org_group = Group.objects.get(name='org') | ||||||
| 		print(queryset) | 		print(queryset) | ||||||
| 		for o in queryset: | 		for o in queryset: | ||||||
| 			user = o.user | 			user = o.user | ||||||
| 			user.user_permissions.add(org_perm) | 			print(user) | ||||||
|  | 			user.groups.add(org_group) | ||||||
| 			user.is_staff = True | 			user.is_staff = True | ||||||
| 			user.save() | 			user.save() | ||||||
| 			org = m.Organizator.objects.create(osoba=o) | 			org = m.Organizator.objects.create(osoba=o) | ||||||
| 			org.save() | 			org.save() | ||||||
| 	udelej_orgem.short_description = "Udělej vybraných osob organizátory" | 	udelej_orgem.short_description = "Udělej vybraných osob organizátory" | ||||||
| 
 | 
 | ||||||
|  | @admin.register(m.Organizator) | ||||||
|  | class OrganizatorAdmin(admin.ModelAdmin): | ||||||
|  | 	search_fields = ['osoba__jmeno', 'osoba__prijmeni', 'osoba__prezdivka'] | ||||||
| 			 | 			 | ||||||
|  | @admin.register(m.Resitel) | ||||||
|  | class ResitelAdmin(admin.ModelAdmin): | ||||||
|  | 	search_fields = ['osoba__jmeno', 'osoba__prijmeni', 'osoba__prezdivka'] | ||||||
|  | 	ordering = ('osoba__jmeno','osoba__prijmeni') | ||||||
| 
 | 
 | ||||||
| @admin.register(m.Problem) | @admin.register(m.Problem) | ||||||
| class ProblemAdmin(PolymorphicParentModelAdmin): | class ProblemAdmin(PolymorphicParentModelAdmin): | ||||||
|  | @ -53,36 +62,81 @@ class ProblemAdmin(PolymorphicParentModelAdmin): | ||||||
| 		m.Uloha, | 		m.Uloha, | ||||||
| 		m.Konfera, | 		m.Konfera, | ||||||
| 		] | 		] | ||||||
|  | 	# Pokud chceme orezavat na aktualni rocnik, musime do modelu pridat odkaz na rocnik. Zatim bere vse. | ||||||
|  | 	search_fields = ['nazev'] | ||||||
|  | 
 | ||||||
|  | # V ProblemAdmin to nejde, protoze se to nepropise do deti | ||||||
|  | class ProblemAdminMixin(object): | ||||||
|  | 	show_in_index = True | ||||||
|  | 	autocomplete_fields = ['nadproblem','autor','garant'] | ||||||
|  | 	filter_horizontal = ['opravovatele'] | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| @admin.register(m.Tema) | @admin.register(m.Tema) | ||||||
| class TemaAdmin(PolymorphicChildModelAdmin): | class TemaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin): | ||||||
| 	base_model = m.Tema | 	base_model = m.Tema | ||||||
| 	show_in_index = True |  | ||||||
| 
 | 
 | ||||||
| @admin.register(m.Clanek) | @admin.register(m.Clanek) | ||||||
| class ClanekAdmin(PolymorphicChildModelAdmin): | class ClanekAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin): | ||||||
| 	base_model = m.Clanek | 	base_model = m.Clanek | ||||||
| 	show_in_index = True |  | ||||||
| 
 | 
 | ||||||
| @admin.register(m.Uloha) | @admin.register(m.Uloha) | ||||||
| class UlohaAdmin(PolymorphicChildModelAdmin): | class UlohaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin): | ||||||
| 	base_model = m.Uloha | 	base_model = m.Uloha | ||||||
| 	show_in_index = True |  | ||||||
| 
 | 
 | ||||||
| @admin.register(m.Konfera) | @admin.register(m.Konfera) | ||||||
| class KonferaAdmin(PolymorphicChildModelAdmin): | class KonferaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin): | ||||||
| 	base_model = m.Konfera | 	base_model = m.Konfera | ||||||
| 	show_in_index = True | 
 | ||||||
| 
 | 
 | ||||||
| class TextAdminInline(admin.TabularInline): | class TextAdminInline(admin.TabularInline): | ||||||
| 	model = m.Text | 	model = m.Text | ||||||
|  | 	formfield_overrides = { | ||||||
|  | 		models.TextField: {'widget': widgets.TextInput} | ||||||
|  | 	} | ||||||
| 	exclude = ['text_zkraceny_set','text_zkraceny'] | 	exclude = ['text_zkraceny_set','text_zkraceny'] | ||||||
|  | 
 | ||||||
| admin.site.register(m.Text) | admin.site.register(m.Text) | ||||||
| 
 | 
 | ||||||
| class ResitelInline(admin.TabularInline): | class ResitelInline(admin.TabularInline): | ||||||
| 	model = m.Resitel | 	model = m.Resitel | ||||||
| 	extra = 1 | 	extra = 1 | ||||||
| admin.site.register(m.Resitel) | 
 | ||||||
|  | class SoustredeniUcastniciInline(admin.TabularInline): | ||||||
|  | 	model = m.Soustredeni_Ucastnici | ||||||
|  | 	extra = 1 | ||||||
|  | 	fields = ['resitel','poznamka'] | ||||||
|  | 	autocomplete_fields = ['resitel'] | ||||||
|  | 	ordering = ['resitel__osoba__jmeno', 'resitel__osoba__prijmeni'] | ||||||
|  | 	formfield_overrides = { | ||||||
|  | 		models.TextField: {'widget': widgets.TextInput} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	def get_queryset(self,request): | ||||||
|  | 		qs = super().get_queryset(request) | ||||||
|  | 		return qs.select_related('resitel','soustredeni') | ||||||
|  | 
 | ||||||
|  | class SoustredeniOrganizatoriInline(admin.TabularInline): | ||||||
|  | 	model = m.Soustredeni.organizatori.through | ||||||
|  | 	extra = 1 | ||||||
|  | 	fields = ['organizator','poznamka'] | ||||||
|  | 	autocomplete_fields = ['organizator'] | ||||||
|  | 	ordering = ['organizator__osoba__jmeno','organizator__prijmeni'] | ||||||
|  | 	formfield_overrides = { | ||||||
|  | 		models.TextField: {'widget': widgets.TextInput} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	def get_queryset(self,request): | ||||||
|  | 		qs = super().get_queryset(request) | ||||||
|  | 		return qs.select_related('organizator', 'soustredeni') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @admin.register(m.Soustredeni) | ||||||
|  | class SoustredeniAdmin(admin.ModelAdmin): | ||||||
|  | 	model = m.Soustredeni | ||||||
|  | 	inline_type = 'tabular' | ||||||
|  | 	inlines = [SoustredeniUcastniciInline, SoustredeniOrganizatoriInline] | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class PrilohaReseniInline(admin.TabularInline): | class PrilohaReseniInline(admin.TabularInline): | ||||||
| 	model = m.PrilohaReseni | 	model = m.PrilohaReseni | ||||||
|  | @ -92,6 +146,7 @@ admin.site.register(m.PrilohaReseni) | ||||||
| class Reseni_ResiteleInline(admin.TabularInline): | class Reseni_ResiteleInline(admin.TabularInline): | ||||||
| 	model = m.Reseni_Resitele | 	model = m.Reseni_Resitele | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| @admin.register(m.Reseni) | @admin.register(m.Reseni) | ||||||
| class ReseniAdmin(ReverseModelAdmin): | class ReseniAdmin(ReverseModelAdmin): | ||||||
| 	base_model = m.Reseni | 	base_model = m.Reseni | ||||||
|  | @ -106,7 +161,6 @@ admin.site.register(m.Hodnoceni) | ||||||
| admin.site.register(m.Pohadka) | admin.site.register(m.Pohadka) | ||||||
| admin.site.register(m.Obrazek) | admin.site.register(m.Obrazek) | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| # Polymorfismus pro stromy | # Polymorfismus pro stromy | ||||||
| # TODO: Inlines podle https://django-polymorphic.readthedocs.io/en/stable/admin.html | # TODO: Inlines podle https://django-polymorphic.readthedocs.io/en/stable/admin.html | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ from django import forms | ||||||
| from dal import autocomplete | from dal import autocomplete | ||||||
| from django.core.exceptions import ObjectDoesNotExist | from django.core.exceptions import ObjectDoesNotExist | ||||||
| from django.contrib.auth.models import User | from django.contrib.auth.models import User | ||||||
|  | from django.forms import formset_factory | ||||||
| from django.forms.models import inlineformset_factory | from django.forms.models import inlineformset_factory | ||||||
| 
 | 
 | ||||||
| from .models import Skola, Resitel, Osoba, Problem | from .models import Skola, Resitel, Osoba, Problem | ||||||
|  | @ -184,7 +185,7 @@ class ProfileEditForm(forms.Form): | ||||||
| 		max_value=date.today().year+8, | 		max_value=date.today().year+8, | ||||||
| 		required=True) | 		required=True) | ||||||
| 	zasilat = forms.ChoiceField(label='Kam zasílat čísla a řešení',choices = Resitel.ZASILAT_CHOICES, required=True) | 	zasilat = forms.ChoiceField(label='Kam zasílat čísla a řešení',choices = Resitel.ZASILAT_CHOICES, required=True) | ||||||
| 	zasilat_cislo_emailem = forms.BooleanField(label='Chci dostávat email s upozorněním na vydání nového čísla', required=True) | 	zasilat_cislo_emailem = forms.BooleanField(label='Chci dostávat email s upozorněním na vydání nového čísla', required=False) | ||||||
| 
 | 
 | ||||||
| 	spam = forms.BooleanField(label='Souhlasím se zasíláním materiálů od MFF UK', required=False) | 	spam = forms.BooleanField(label='Souhlasím se zasíláním materiálů od MFF UK', required=False) | ||||||
| #	def clean_username(self): | #	def clean_username(self): | ||||||
|  | @ -301,3 +302,91 @@ class NahrajObrazekKTreeNoduForm(forms.ModelForm): | ||||||
| 		model = m.Obrazek | 		model = m.Obrazek | ||||||
| 		fields = ('na_web',) | 		fields = ('na_web',) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | class JednoHodnoceniForm(forms.ModelForm): | ||||||
|  | 	class Meta: | ||||||
|  | 		model = m.Hodnoceni | ||||||
|  | 		fields = ('problem', 'body', 'cislo_body') | ||||||
|  | 		widgets = { | ||||||
|  | 			'problem': autocomplete.ModelSelect2( | ||||||
|  | 				url='autocomplete_problem_odevzdatelny',   # FIXME: Dovolit i starší? | ||||||
|  | 				) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | OhodnoceniReseniFormSet = formset_factory(JednoHodnoceniForm, | ||||||
|  | 		extra = 0, | ||||||
|  | 		) | ||||||
|  | 
 | ||||||
|  | # FIXME: Ideálně by mělo být součástí třídy níž, ale neumím to udělat | ||||||
|  | DATE_FORMAT = '%Y-%m-%d' | ||||||
|  | 
 | ||||||
|  | class OdevzdavatkoTabulkaFiltrForm(forms.Form): | ||||||
|  | 	"""Form pro filtrování přehledové odevzdávátkové tabulky | ||||||
|  | 
 | ||||||
|  | 	Inspirováno https://kam.mff.cuni.cz/mffzoom/""" | ||||||
|  | 
 | ||||||
|  | 	# Věci definované níž se importují i ve views pro odevzdávátko (Inspirováno https://docs.djangoproject.com/en/3.1/ref/models/fields/#field-choices) | ||||||
|  | 
 | ||||||
|  | 	RESITELE_RELEVANTNI = 'relevantni' | ||||||
|  | 	RESITELE_LETOSNI = 'letosni' | ||||||
|  | 	RESITELE_CHOICES = [ | ||||||
|  | 		(RESITELE_RELEVANTNI, 'Relevantní řešitelé'), # I.e. nezobrazovat prázdné řádky tabulky | ||||||
|  | 		(RESITELE_LETOSNI, 'Všichni letošní'), | ||||||
|  | 		# Možná: všechny vč. historických? | ||||||
|  | 		] | ||||||
|  | 
 | ||||||
|  | 	PROBLEMY_MOJE = 'moje' | ||||||
|  | 	PROBLEMY_LETOSNI = 'letosni' | ||||||
|  | 	PROBLEMY_CHOICES = [ | ||||||
|  | 		(PROBLEMY_MOJE, 'Moje problémy'), # Letošní problémy, které mají v sobě nebo v nadproblémech přiřazeného daného orga | ||||||
|  | 		(PROBLEMY_LETOSNI, 'Všechny letošní'), | ||||||
|  | 		# TODO: *hlavní problémy, možná všechny... | ||||||
|  | 		# XXX: Chtělo by to i "aktuálně zadané... | ||||||
|  | 		] | ||||||
|  | 
 | ||||||
|  | 	# TODO: Typy problémů (problémy, úlohy, ostatní, všechny)? Jen některá řešení (obodovaná/neobodovaná, víc řešitelů, ...)? | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 	def gen_terminy(): | ||||||
|  | 		import datetime | ||||||
|  | 		from time import strftime | ||||||
|  | 
 | ||||||
|  | 		aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik | ||||||
|  | 		aktualni_cislo = m.Nastaveni.get_solo().aktualni_cislo | ||||||
|  | 
 | ||||||
|  | 		result = [] | ||||||
|  | 
 | ||||||
|  | 		for cislo in m.Cislo.objects.filter( | ||||||
|  | 				rocnik=aktualni_rocnik, | ||||||
|  | 				poradi__lte=aktualni_cislo.poradi, | ||||||
|  | 				).reverse():	# Standardně se řadí od nejnovějšího čísla | ||||||
|  | 			# Předem je mi líto kohokoliv, kdo tyhle řádky bude číst... | ||||||
|  | 			if cislo.datum_vydani is not None and cislo.datum_vydani <= datetime.date.today(): | ||||||
|  | 				result.append(( | ||||||
|  | 					strftime(DATE_FORMAT, cislo.datum_vydani.timetuple()), | ||||||
|  | 					f"Vydání {cislo.poradi}. čísla")) | ||||||
|  | 			if cislo.datum_preddeadline is not None and cislo.datum_preddeadline <= datetime.date.today(): | ||||||
|  | 				result.append(( | ||||||
|  | 					strftime(DATE_FORMAT, cislo.datum_preddeadline.timetuple()), | ||||||
|  | 					f"Předdeadline {cislo.poradi}. čísla")) | ||||||
|  | 			if cislo.datum_deadline_soustredeni is not None and cislo.datum_deadline_soustredeni <= datetime.date.today(): | ||||||
|  | 				result.append(( | ||||||
|  | 					strftime(DATE_FORMAT, cislo.datum_deadline_soustredeni.timetuple()), | ||||||
|  | 					f"Sous. deadline {cislo.poradi}. čísla")) | ||||||
|  | 			if cislo.datum_deadline is not None and cislo.datum_deadline <= datetime.date.today(): | ||||||
|  | 				result.append(( | ||||||
|  | 					strftime(DATE_FORMAT, cislo.datum_deadline.timetuple()), | ||||||
|  | 					f"Finální deadline {cislo.poradi}. čísla")) | ||||||
|  | 		result.append(( | ||||||
|  | 			strftime(DATE_FORMAT, datetime.date.today().timetuple()), f"Dnes")) | ||||||
|  | 
 | ||||||
|  | 		return result | ||||||
|  | 
 | ||||||
|  | 	# NOTE: Initial definuji pro jednotlivé fieldy, aby to bylo tady a nebylo potřeba to řešit ve views... | ||||||
|  | 	resitele = forms.ChoiceField(choices=RESITELE_CHOICES, initial=RESITELE_RELEVANTNI) | ||||||
|  | 	problemy = forms.ChoiceField(choices=PROBLEMY_CHOICES, initial=PROBLEMY_MOJE) | ||||||
|  | 	 | ||||||
|  | 	# choices jako parametr Select widgetu neumí brát callable, jen iterable, takže si pro jednoduchost můžu rovnou uložit výsledek sem... | ||||||
|  | 	terminy = gen_terminy() | ||||||
|  | 	reseni_od = forms.DateField(input_formats=[DATE_FORMAT], widget=forms.Select(choices=terminy), initial=terminy[-2]) | ||||||
|  | 	reseni_do = forms.DateField(input_formats=[DATE_FORMAT], widget=forms.Select(choices=terminy), initial=terminy[-1]) | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ from django.contrib.contenttypes.models import ContentType | ||||||
| from django.utils.text import get_valid_filename | from django.utils.text import get_valid_filename | ||||||
| from imagekit.models import ImageSpecField, ProcessedImageField | from imagekit.models import ImageSpecField, ProcessedImageField | ||||||
| from imagekit.processors import ResizeToFit, Transpose | from imagekit.processors import ResizeToFit, Transpose | ||||||
|  | from django.utils.functional import cached_property | ||||||
| 
 | 
 | ||||||
| from django_countries.fields import CountryField | from django_countries.fields import CountryField | ||||||
| from solo.models import SingletonModel | from solo.models import SingletonModel | ||||||
|  | @ -26,11 +27,14 @@ from taggit.managers import TaggableManager | ||||||
| from reversion import revisions as reversion | from reversion import revisions as reversion | ||||||
| 
 | 
 | ||||||
| from seminar.utils import roman, FirstTagParser # Pro získání úryvku z TextNode | from seminar.utils import roman, FirstTagParser # Pro získání úryvku z TextNode | ||||||
|  | from seminar import treelib | ||||||
| 
 | 
 | ||||||
| from unidecode import unidecode # Používám pro získání ID odkazu (ještě je to někde po někom zakomentované) | from unidecode import unidecode # Používám pro získání ID odkazu (ještě je to někde po někom zakomentované) | ||||||
| 
 | 
 | ||||||
| from polymorphic.models import PolymorphicModel | from polymorphic.models import PolymorphicModel | ||||||
| 
 | 
 | ||||||
|  | from django.core.mail import EmailMessage | ||||||
|  | from seminar.utils import aktivniResitele | ||||||
| 
 | 
 | ||||||
| class SeminarModelBase(models.Model): | class SeminarModelBase(models.Model): | ||||||
| 
 | 
 | ||||||
|  | @ -624,9 +628,43 @@ class Cislo(SeminarModelBase): | ||||||
| 			return None | 			return None | ||||||
| 		return c | 		return c | ||||||
| 
 | 
 | ||||||
|  | 	def __init__(self, *args, **kwargs): | ||||||
|  | 		super().__init__(*args, **kwargs) | ||||||
|  | 		self.__original_verejne = self.verejne_db | ||||||
|  | 
 | ||||||
|  | 	def posli_cislo_mailem(self): | ||||||
|  | 		# parametry e-mailu | ||||||
|  | 		odkaz = self.get_absolute_url() | ||||||
|  | 
 | ||||||
|  | 		poslat_z_mailu = 'zadani@mam.mff.cuni.cz' | ||||||
|  | 		predmet = 'Vyšlo číslo {}'.format(self.kod()) | ||||||
|  | 		text_mailu = 'Ahoj,\n' \ | ||||||
|  | 			   'na adrese {} najdete nejnovější číslo.\n' \ | ||||||
|  | 			   'Vaše M&M\n'.format(odkaz) | ||||||
|  | 
 | ||||||
|  | 		# Prijemci e-mailu | ||||||
|  | 		emaily = map(lambda r: r.osoba.email, filter(lambda r: r.zasilat_cislo_emailem, aktivniResitele(self))) | ||||||
|  | 
 | ||||||
|  | 		if not settings.POSLI_MAILOVOU_NOTIFIKACI: | ||||||
|  | 			print("Poslal bych upozornění na tyto adresy: ", " ".join(emaily)) | ||||||
|  | 			return | ||||||
|  | 
 | ||||||
|  | 		email = EmailMessage( | ||||||
|  | 			subject=predmet, | ||||||
|  | 			body=text_mailu, | ||||||
|  | 			from_email=poslat_z_mailu, | ||||||
|  | 			bcc=list(emaily) | ||||||
|  | 			#bcc = příjemci skryté kopie | ||||||
|  | 		) | ||||||
|  | 
 | ||||||
|  | 		email.send() | ||||||
|  | 
 | ||||||
| 	def save(self, *args, **kwargs): | 	def save(self, *args, **kwargs): | ||||||
| 		super().save(*args, **kwargs) | 		super().save(*args, **kwargs) | ||||||
| 		self.vygeneruj_nahled() | 		self.vygeneruj_nahled() | ||||||
|  | 		# Při zveřejnění pošle mail | ||||||
|  | 		if self.verejne_db and not self.__original_verejne: | ||||||
|  | 			self.posli_cislo_mailem() | ||||||
| 		# *Node.save() aktualizuje název *Nodu. | 		# *Node.save() aktualizuje název *Nodu. | ||||||
| 		try: | 		try: | ||||||
| 			self.cislonode.save() | 			self.cislonode.save() | ||||||
|  | @ -818,10 +856,11 @@ class Problem(SeminarModelBase,PolymorphicModel): | ||||||
| 		return self.nazev | 		return self.nazev | ||||||
| 
 | 
 | ||||||
| 	# Implicitini implementace, jednotlivé dědící třídy si přepíšou | 	# Implicitini implementace, jednotlivé dědící třídy si přepíšou | ||||||
|  | 	@cached_property | ||||||
| 	def kod_v_rocniku(self): | 	def kod_v_rocniku(self): | ||||||
| 		if self.stav == 'zadany': | 		if self.stav == 'zadany': | ||||||
| 			if self.nadproblem: | 			if self.nadproblem: | ||||||
| 				return self.nadproblem.kod_v_rocniku()+".{}".format(self.kod) | 				return self.nadproblem.kod_v_rocniku+".{}".format(self.kod) | ||||||
| 			return str(self.kod) | 			return str(self.kod) | ||||||
| 		return '<Není zadaný>' | 		return '<Není zadaný>' | ||||||
| 
 | 
 | ||||||
|  | @ -829,17 +868,26 @@ class Problem(SeminarModelBase,PolymorphicModel): | ||||||
| 		# aktuálně podle stavu problému | 		# aktuálně podle stavu problému | ||||||
| 		# FIXME pro některé problémy možná chceme override | 		# FIXME pro některé problémy možná chceme override | ||||||
| 		# FIXME vrací veřejnost čistě problému, nezávisle na čísle, ve kterém je.  | 		# FIXME vrací veřejnost čistě problému, nezávisle na čísle, ve kterém je.  | ||||||
| 		# Je to tak správně? | 		# Je to tak správně? Podle aktuální představy ano. | ||||||
| 		stav_verejny = False | 		stav_verejny = False | ||||||
| 		if self.stav == 'zadany' or self.stav == 'vyreseny': | 		if self.stav == 'zadany' or self.stav == 'vyreseny': | ||||||
| 			stav_verejny = True | 			stav_verejny = True | ||||||
|  | 			print("stav_verejny: {}".format(stav_verejny))		 | ||||||
|  | 
 | ||||||
|  | 		cislo_verejne = False | ||||||
|  | 		cislonode = self.cislo_node() | ||||||
|  | 		if cislonode is None: | ||||||
|  | 			# problém nemá vlastní node, veřejnost posuzujeme jen podle stavu | ||||||
|  | 			print("empty node")		 | ||||||
| 			return stav_verejny | 			return stav_verejny | ||||||
| 		 | 		else:	 | ||||||
| 		#cislo_verejne = False | 			cislo_zadani = cislonode.cislo | ||||||
| 		#if (self.cislo_zadani and self.cislo_zadani.verejne()): | 			if (cislo_zadani and cislo_zadani.verejne()): | ||||||
| 		#	cislo_verejne = True | 				print("cislo: {}".format(cislo_zadani)) | ||||||
| 		 | 				cislo_verejne = True | ||||||
| 		#return (stav_verejny and cislo_verejne) | 			print("stav_verejny: {}".format(stav_verejny))		 | ||||||
|  | 			print("cislo_verejne: {}".format(cislo_verejne))		 | ||||||
|  | 			return (stav_verejny and cislo_verejne) | ||||||
| 	verejne.boolean = True | 	verejne.boolean = True | ||||||
| 
 | 
 | ||||||
| 	def verejne_url(self): | 	def verejne_url(self): | ||||||
|  | @ -875,16 +923,17 @@ class Tema(Problem): | ||||||
| 	tema_typ = models.CharField('Typ tématu', max_length=16, choices=TEMA_CHOICES,  | 	tema_typ = models.CharField('Typ tématu', max_length=16, choices=TEMA_CHOICES,  | ||||||
| 		blank=False, default=TEMA_TEMA) | 		blank=False, default=TEMA_TEMA) | ||||||
| 
 | 
 | ||||||
| 	rocnik = models.ForeignKey(Rocnik, verbose_name='ročník',blank=True, null=True, | 	rocnik = models.ForeignKey(Rocnik, verbose_name='ročník',related_name='temata',blank=True, null=True, | ||||||
| 		on_delete=models.PROTECT) | 		on_delete=models.PROTECT) | ||||||
| 
 | 
 | ||||||
| 	abstrakt = models.TextField('Abstrakt na rozcestník', blank=True) | 	abstrakt = models.TextField('Abstrakt na rozcestník', blank=True) | ||||||
| 	obrazek = models.ImageField('Obrázek na rozcestník', null=True) | 	obrazek = models.ImageField('Obrázek na rozcestník', null=True) | ||||||
| 
 | 
 | ||||||
|  | 	@cached_property | ||||||
| 	def kod_v_rocniku(self): | 	def kod_v_rocniku(self): | ||||||
| 		if self.stav == 'zadany': | 		if self.stav == 'zadany': | ||||||
| 			if self.nadproblem: | 			if self.nadproblem: | ||||||
| 				return self.nadproblem.kod_v_rocniku()+".t{}".format(self.kod) | 				return self.nadproblem.kod_v_rocniku+".t{}".format(self.kod) | ||||||
| 			return "t{}".format(self.kod) | 			return "t{}".format(self.kod) | ||||||
| 		return '<Není zadaný>' | 		return '<Není zadaný>' | ||||||
| 
 | 
 | ||||||
|  | @ -894,6 +943,16 @@ class Tema(Problem): | ||||||
| 		for tvcn in self.temavcislenode_set.all(): | 		for tvcn in self.temavcislenode_set.all(): | ||||||
| 			tvcn.save() | 			tvcn.save() | ||||||
| 
 | 
 | ||||||
|  | 	def cislo_node(self): | ||||||
|  | 		tema_node_set = self.temavcislenode_set.all() | ||||||
|  | 		tema_cisla_vyskyt = [] | ||||||
|  | 		for tn in tema_node_set: | ||||||
|  | 			tema_cisla_vyskyt.append( | ||||||
|  | 				treelib.get_upper_node_of_type(tn, CisloNode).cislo) | ||||||
|  | 		tema_cisla_vyskyt.sort(key=lambda x:x.datum_vydani) | ||||||
|  | 		prvni_zadani = tema_cisla_vyskyt[0] | ||||||
|  | 		return prvni_zadani.cislonode | ||||||
|  | 
 | ||||||
| class Clanek(Problem): | class Clanek(Problem): | ||||||
| 	class Meta: | 	class Meta: | ||||||
| 		db_table = 'seminar_clanky' | 		db_table = 'seminar_clanky' | ||||||
|  | @ -907,10 +966,13 @@ class Clanek(Problem): | ||||||
| 		if self.stav == 'zadany': | 		if self.stav == 'zadany': | ||||||
| # Nemělo by být potřeba | # Nemělo by být potřeba | ||||||
| #			if self.nadproblem: | #			if self.nadproblem: | ||||||
| #				return self.nadproblem.kod_v_rocniku()+".c{}".format(self.kod) | #				return self.nadproblem.kod_v_rocniku+".c{}".format(self.kod) | ||||||
| 			return "c{}".format(self.kod) | 			return "c{}".format(self.kod) | ||||||
| 		return '<Není zadaný>' | 		return '<Není zadaný>' | ||||||
| 	 | 	 | ||||||
|  | 	def node(self): | ||||||
|  | 		return None | ||||||
|  | 
 | ||||||
| class Text(SeminarModelBase): | class Text(SeminarModelBase): | ||||||
| 	class Meta: | 	class Meta: | ||||||
| 		db_table = 'seminar_texty' | 		db_table = 'seminar_texty' | ||||||
|  | @ -961,11 +1023,12 @@ class Uloha(Problem): | ||||||
| 	# UlohaZadaniNode | 	# UlohaZadaniNode | ||||||
| 	# UlohaVzorakNode | 	# UlohaVzorakNode | ||||||
| 
 | 
 | ||||||
|  | 	@cached_property | ||||||
| 	def kod_v_rocniku(self): | 	def kod_v_rocniku(self): | ||||||
| 		if self.stav == 'zadany': | 		if self.stav == 'zadany': | ||||||
| 			name="{}.u{}".format(self.cislo_zadani.poradi,self.kod) | 			name="{}.u{}".format(self.cislo_zadani.poradi,self.kod) | ||||||
| 			if self.nadproblem: | 			if self.nadproblem: | ||||||
| 				return self.nadproblem.kod_v_rocniku()+name | 				return self.nadproblem.kod_v_rocniku+name | ||||||
| 			return name | 			return name | ||||||
| 		return '<Není zadaný>' | 		return '<Není zadaný>' | ||||||
| 
 | 
 | ||||||
|  | @ -983,6 +1046,9 @@ class Uloha(Problem): | ||||||
| 			# Neexistující *Node nemá smysl aktualizovat. | 			# Neexistující *Node nemá smysl aktualizovat. | ||||||
| 			pass | 			pass | ||||||
| 
 | 
 | ||||||
|  | 	def cislo_node(self): | ||||||
|  | 		zadani_node = self.ulohazadaninode  | ||||||
|  | 		return treelib.get_upper_node_of_type(zadani_node, CisloNode) | ||||||
| 
 | 
 | ||||||
| @reversion.register(ignore_duplicates=True) | @reversion.register(ignore_duplicates=True) | ||||||
| class Reseni(SeminarModelBase): | class Reseni(SeminarModelBase): | ||||||
|  | @ -1273,6 +1339,8 @@ class Konfera(Problem): | ||||||
| 	def __str__(self): | 	def __str__(self): | ||||||
| 		return "{}: ({})".format(self.nazev, self.soustredeni) | 		return "{}: ({})".format(self.nazev, self.soustredeni) | ||||||
| 
 | 
 | ||||||
|  | 	def cislo_node(self): | ||||||
|  | 		return None | ||||||
| 
 | 
 | ||||||
| # Vazebna tabulka. Mozna se generuje automaticky. | # Vazebna tabulka. Mozna se generuje automaticky. | ||||||
| @reversion.register(ignore_duplicates=True) | @reversion.register(ignore_duplicates=True) | ||||||
|  |  | ||||||
							
								
								
									
										
											BIN
										
									
								
								seminar/static/images/no-picture.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								seminar/static/images/no-picture.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 86 KiB | 
|  | @ -1,12 +1,13 @@ | ||||||
| {% extends "base.html" %} | {% extends "base.html" %} | ||||||
| 
 | 
 | ||||||
| {% block content %} | {% block content %} | ||||||
| <div> | 
 | ||||||
|   <h2> |   <h2> | ||||||
|     {% block nadpis1a %}{% block nadpis1b %} |     {% block nadpis1a %}{% block nadpis1b %} | ||||||
|       Archiv čísel |       Archiv čísel | ||||||
|     {% endblock %}{% endblock %} |     {% endblock %}{% endblock %} | ||||||
|   </h2> |   </h2> | ||||||
|  |   <div class="seznam_archiv"> | ||||||
| 
 | 
 | ||||||
|     {% for rocnik, url_png in object_list.items %} |     {% for rocnik, url_png in object_list.items %} | ||||||
| 
 | 
 | ||||||
|  | @ -54,6 +55,7 @@ | ||||||
|     {% empty %} |     {% empty %} | ||||||
|       Nejsou žádné ročníky |       Nejsou žádné ročníky | ||||||
|     {% endfor %} |     {% endfor %} | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| </div> |  | ||||||
| {% endblock content %} | {% endblock content %} | ||||||
|  |  | ||||||
|  | @ -9,11 +9,7 @@ | ||||||
|       Číslo {{ cislo }} |       Číslo {{ cislo }} | ||||||
|     {% endblock %}{% endblock %} |     {% endblock %}{% endblock %} | ||||||
|   </h1> |   </h1> | ||||||
| 
 |   <a href='{{ cislo.rocnik.verejne_url }}'>Zpět na ročník {{ cislo.rocnik }}</a> | ||||||
|   {% if cislo.pdf %} |  | ||||||
|     <p><a href='{{ cislo.pdf.url }}'>Číslo v pdf</a> |  | ||||||
|   {% endif %} |  | ||||||
|   <p><a href='{{ cislo.rocnik.verejne_url }}'>Ročník {{ cislo.rocnik }}</a> |  | ||||||
| 
 | 
 | ||||||
|   {% if v_cisle_zadane %} |   {% if v_cisle_zadane %} | ||||||
|     <h2>Zadané problémy</h2> |     <h2>Zadané problémy</h2> | ||||||
|  | @ -50,12 +46,26 @@ | ||||||
|       </div> |       </div> | ||||||
|   {% endif %} |   {% endif %} | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  |   {% if cislo.titulka_nahled %} | ||||||
|  |  <hr> | ||||||
|  |  <div id="obrazek_cisla_archiv"> | ||||||
|  | 		    <a href="{{ cislo.pdf.url }}"><img src="{{ cislo.titulka_nahled.url }}" alt=Titulní strana {{ cislo.poradi }}. čísla></a> | ||||||
|  |   {% elif cislo.pdf %} | ||||||
|  |     <a href='{{ cislo.pdf.url }}'>Číslo v pdf</a>   | ||||||
|  |   </div> | ||||||
|  |   <hr>  | ||||||
|  |   {% endif %} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | {% comment %} | ||||||
|   <script id="vuedata" type="application/json">{"treenode":{{cislo.cislonode.id}}}</script> |   <script id="vuedata" type="application/json">{"treenode":{{cislo.cislonode.id}}}</script> | ||||||
|   <div id="app"> |   <div id="app"> | ||||||
| 	  <app></app> | 	  <app></app> | ||||||
|   </div> |   </div> | ||||||
|   {% render_bundle 'chunk-vendors' %} |   {% render_bundle 'chunk-vendors' %} | ||||||
|   {% render_bundle 'vue_app_01' %} |   {% render_bundle 'vue_app_01' %} | ||||||
|  | {% endcomment %} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|   {% if cislo.verejna_vysledkovka %} |   {% if cislo.verejna_vysledkovka %} | ||||||
|  | @ -74,9 +84,24 @@ | ||||||
|         <th class='border-r'># |         <th class='border-r'># | ||||||
|         <th class='border-r'>Jméno |         <th class='border-r'>Jméno | ||||||
|         {% for p in problemy %} |         {% for p in problemy %} | ||||||
|         <th class='border-r'><a href="{{ p.verejne_url }}">{{ p.kod_v_rocniku }}</a> |         <th class='border-r' id="problem{{ forloop.counter }}"><a href="{{ p.verejne_url }}">{{ p.kod_v_rocniku }}</a> | ||||||
|  | 
 | ||||||
|  |             {# TODELETE #} | ||||||
|  |                 {% for podproblemy in podproblemy_iter.next %} | ||||||
|  |                     <th class='border-r podproblem{{ forloop.parentloop.counter }}'><a href="{{ podproblemy.verejne_url }}">{{ podproblemy.kod_v_rocniku }}</a> | ||||||
|  |                 {% endfor %} | ||||||
|  |             {# TODELETE #} | ||||||
|  | 
 | ||||||
|         {% endfor %} |         {% endfor %} | ||||||
|         {% if ostatni %}<th class='border-r'>Ostatní {% endif %} |         {% if ostatni %}<th class='border-r'>Ostatní {% endif %} | ||||||
|  | 
 | ||||||
|  |             {# TODELETE #} | ||||||
|  |                 {% for podproblemy in podproblemy_iter.next %} | ||||||
|  |                     <th class='border-r podproblem{{ problemy.len }}'><a href="{{ podproblemy.verejne_url }}">{{ podproblemy.kod_v_rocniku }}</a> | ||||||
|  |                 {% endfor %} | ||||||
|  |             {# TODELETE #} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|         <th class='border-r'>Za číslo |         <th class='border-r'>Za číslo | ||||||
|         <th class='border-r'>Za ročník |         <th class='border-r'>Za ročník | ||||||
|         <th class='border-r'>Odjakživa |         <th class='border-r'>Odjakživa | ||||||
|  | @ -90,6 +115,13 @@ | ||||||
|             {{ rv.resitel.osoba.plne_jmeno }} |             {{ rv.resitel.osoba.plne_jmeno }} | ||||||
|         {% for b in rv.body_problemy_sezn %} |         {% for b in rv.body_problemy_sezn %} | ||||||
|         <td class='border-r'>{{ b }} |         <td class='border-r'>{{ b }} | ||||||
|  | 
 | ||||||
|  |             {# TODELETE #} | ||||||
|  |                 {% for body_podproblemu in rv.body_podproblemy_iter.next %} | ||||||
|  |                     <td class='border-r podproblem{{ forloop.parentloop.counter }}'>{{ body_podproblemu }} | ||||||
|  |                 {% endfor %} | ||||||
|  |             {# TODELETE #} | ||||||
|  | 
 | ||||||
|         {% endfor %} |         {% endfor %} | ||||||
|         <td class='border-r'>{{ rv.body_cislo }} |         <td class='border-r'>{{ rv.body_cislo }} | ||||||
|         <td class='border-r'><b>{{ rv.body_rocnik }}</b> |         <td class='border-r'><b>{{ rv.body_rocnik }}</b> | ||||||
|  | @ -97,6 +129,23 @@ | ||||||
|       </tr> |       </tr> | ||||||
|     {% endfor %} |     {% endfor %} | ||||||
|     </table> |     </table> | ||||||
|  | 
 | ||||||
|  |       {# TODELETE #} | ||||||
|  |         <script> | ||||||
|  |           {% for p in problemy %} | ||||||
|  |             $(".podproblem{{ forloop.counter }}").css("display", "none") | ||||||
|  |             $("#problem{{ forloop.counter }}")[0].addEventListener('mouseover', podproblem{{ forloop.counter }}) | ||||||
|  |             $("#problem{{ forloop.counter }}")[0].addEventListener('mouseout', podproblem{{ forloop.counter }}end) | ||||||
|  |             function podproblem{{ forloop.counter }}(event) { | ||||||
|  |                 $(".podproblem{{ forloop.counter }}").css("display", "") | ||||||
|  |             } | ||||||
|  |             function podproblem{{ forloop.counter }}end(event) { | ||||||
|  |                 $(".podproblem{{ forloop.counter }}").css("display", "none") | ||||||
|  |             } | ||||||
|  |           {% endfor %} | ||||||
|  |         </script> | ||||||
|  |       {# TODELETE #} | ||||||
|  | 
 | ||||||
|   {% endif %} |   {% endif %} | ||||||
| 
 | 
 | ||||||
|   {% if not cislo.verejna_vysledkovka and user.je_org %} |   {% if not cislo.verejna_vysledkovka and user.je_org %} | ||||||
|  |  | ||||||
|  | @ -8,15 +8,17 @@ | ||||||
|     {% endblock %}{% endblock %} |     {% endblock %}{% endblock %} | ||||||
|   </h2> |   </h2> | ||||||
| 
 | 
 | ||||||
|   {% if temata_v_rocniku %} |   {% if rocnik.temata %} | ||||||
|     <h2>Témata</h2> |     <h2>Témata</h2> | ||||||
|     <ul> |     <ul> | ||||||
|     {% for tema in temata_v_rocniku %} |     {% for tema in rocnik.temata.all %} | ||||||
|       <li>{% if tema.text_zadani %}<a href="{{ tema.verejne_url }}">{% endif %}{{ tema.kod_v_rocniku }}: {{ tema.nazev }}{% if tema.text_zadani %}</a>{% endif %} |       <li>{#<a href="{{ tema.verejne_url }}">#}{{ tema.nazev }}{#</a>#} | ||||||
|     {% endfor %} |     {% endfor %} | ||||||
|     </ul> |     </ul> | ||||||
|   {% endif %} |   {% endif %} | ||||||
| 
 | 
 | ||||||
|  | <h2>Čísla</h2> | ||||||
|  | 
 | ||||||
| <div class="cisla-v-rocniku"> | <div class="cisla-v-rocniku"> | ||||||
|   {% for c in rocnik.verejna_cisla %} |   {% for c in rocnik.verejna_cisla %} | ||||||
|     <div class="cislo_pole"> |     <div class="cislo_pole"> | ||||||
|  | @ -32,7 +34,7 @@ | ||||||
|         {% if c.titulka_nahled %} |         {% if c.titulka_nahled %} | ||||||
|         <img src="{{ c.titulka_nahled.url }}" alt="{{ c.kod }}" height=180px> |         <img src="{{ c.titulka_nahled.url }}" alt="{{ c.kod }}" height=180px> | ||||||
|         {% else %} |         {% else %} | ||||||
|         <img src="" alt="no image" height=180px> |         {% load static %} <img src="{% static 'images/no-picture.png' %}" height=180px alt="no-picture"> | ||||||
|         {% endif %} |         {% endif %} | ||||||
|       </div> |       </div> | ||||||
| 
 | 
 | ||||||
|  | @ -61,6 +63,7 @@ | ||||||
|   {% endfor %} |   {% endfor %} | ||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|   {% if vysledkovka %} |   {% if vysledkovka %} | ||||||
|     {% if user.je_org %} |     {% if user.je_org %} | ||||||
|       <div class='mam-org-only'> |       <div class='mam-org-only'> | ||||||
|  |  | ||||||
|  | @ -8,10 +8,10 @@ | ||||||
|   </h1> |   </h1> | ||||||
| 
 | 
 | ||||||
|   {% for rocnik, temata in rocniky.items %} |   {% for rocnik, temata in rocniky.items %} | ||||||
|     <h2>Ročník {{ rocnik }}</h2> |     <h2><a href="{{ rocnik.verejne_url }}">Ročník {{ rocnik }}</a></h2> | ||||||
|     <ul> |     <ul> | ||||||
|     {% for tema in temata %} |     {% for tema in temata %} | ||||||
|     <li><a href="{{ tema.verejne_url }}"> {{ tema.kod_v_rocniku }}: {{ tema.nazev }} </a></li> |     <li>{# <a href="{{ tema.verejne_url }}"> #}{{ tema.nazev }}{# </a> #}</li> | ||||||
|     {% endfor %} |     {% endfor %} | ||||||
|     </ul> |     </ul> | ||||||
|   {% endfor %} |   {% endfor %} | ||||||
|  |  | ||||||
|  | @ -9,15 +9,28 @@ | ||||||
| </h1> | </h1> | ||||||
| 
 | 
 | ||||||
| {% for clanek in object_list %} | {% for clanek in object_list %} | ||||||
| {% with clanek.cislo.rocnik.rocnik as rocnik %} | 	{% with clanek.cislo.rocnik.rocnik as rocnik %} | ||||||
| 		{% ifchanged rocnik %} | 		{% ifchanged rocnik %} | ||||||
| 			{% if not forloop.first %}</ul>{% endif %} | 			{% if not forloop.first %}</ul>{% endif %} | ||||||
| 			<h2>{{ rocnik }}. ročník</h2> | 			<h2>{{ rocnik }}. ročník</h2> | ||||||
| 			<ul> | 			<ul> | ||||||
| 		{% endifchanged %} | 		{% endifchanged %} | ||||||
| 		<li> | 		<li> | ||||||
|         <a href="{{ clanek.verejne_url }}">{{ clanek.nazev }}</a> | 			{% if clanek.cislo.pdf %} | ||||||
| {% endwith %} | 			<a href="{{ clanek.cislo.pdf.url }}"> | ||||||
|  | 				{{ clanek.nazev }} | ||||||
|  | 				({% for r in clanek.reseni_set.first.resitele.all %}{{r}}{% if not forloop.last %}, {% endif %}{% endfor %}) | ||||||
|  | 			</a> | ||||||
|  | 			 | ||||||
|  | 			{% else %} | ||||||
|  | 				{{ clanek.nazev }}  | ||||||
|  | 				{% for r in clanek.reseni_set.first.resitele.all %} | ||||||
|  | 					{{r}}, | ||||||
|  | 				{% endfor %} | ||||||
|  | 				(vyšlo v čísle {{clanek.cislo}}) | ||||||
|  | 			{% endif %} | ||||||
|  | 		</li> | ||||||
|  | 	{% endwith %} | ||||||
| {% endfor %} | {% endfor %} | ||||||
| </ul> | </ul> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,6 +2,59 @@ | ||||||
| 
 | 
 | ||||||
| {% block content %} | {% block content %} | ||||||
| 
 | 
 | ||||||
|  | {# FIXME: Necopypastovat! Tohle je zkopírované ze static/seminar/dynamic_formsets.js #} | ||||||
|  | <script type='text/javascript'> | ||||||
|  | // Credit https://medium.com/all-about-django/adding-forms-dynamically-to-a-django-formset-375f1090c2b0 | ||||||
|  | function updateElementIndex(el, prefix, ndx) { | ||||||
|  | 	var id_regex = new RegExp('(' + prefix + '-\\d+)'); | ||||||
|  | 	var replacement = prefix + '-' + ndx; | ||||||
|  | 	if ($(el).attr("for")) { | ||||||
|  | 		$(el).attr("for", $(el).attr("for").replace(id_regex, replacement)); | ||||||
|  | 	} | ||||||
|  | 	if (el.id) { | ||||||
|  | 		el.id = el.id.replace(id_regex, replacement); | ||||||
|  | 	} | ||||||
|  | 	if (el.name) { | ||||||
|  | 		el.name = el.name.replace(id_regex, replacement); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Credit https://medium.com/all-about-django/adding-forms-dynamically-to-a-django-formset-375f1090c2b0 | ||||||
|  | function deleteForm(prefix, btn) { | ||||||
|  |     var total = parseInt($('#id_' + prefix + '-TOTAL_FORMS').val()); | ||||||
|  |     if (total >= 1){ | ||||||
|  |         btn.closest('tr').remove(); | ||||||
|  |         var forms = $('.hodnoceni'); | ||||||
|  |         $('#id_' + prefix + '-TOTAL_FORMS').val(forms.length); | ||||||
|  |         for (var i=0, formCount=forms.length; i<formCount; i++) { | ||||||
|  |             $(forms.get(i)).find(':input').each(function() { | ||||||
|  |                 updateElementIndex(this, prefix, i); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Credit: https://simpleit.rocks/python/django/dynamic-add-form-with-add-button-in-django-modelformset-template/ | ||||||
|  | $(document).ready(function(){ | ||||||
|  | 	$('#pridat_hodnoceni').click(function() { | ||||||
|  | 		var form_idx = $('#id_form-TOTAL_FORMS').val(); | ||||||
|  | 		var new_form = $('#empty_form').html().replace(/__prefix__/g, form_idx); | ||||||
|  | 		$('#form_set').append(new_form); | ||||||
|  | 		// Newly created form has not the binding between remove button and remove function | ||||||
|  | 		// We need to add it manually | ||||||
|  | 		$('.smazat_hodnoceni').click(function(){ | ||||||
|  | 			deleteForm("form",this); | ||||||
|  | 		}); | ||||||
|  | 		$('#id_form-TOTAL_FORMS').val(parseInt(form_idx) + 1); | ||||||
|  | 	}); | ||||||
|  | 	$('.smazat_hodnoceni').click(function(){ | ||||||
|  | 		deleteForm("form",this); | ||||||
|  | 	}); | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| <p>Řešené problémy: {{ object.problem.all | join:", " }}</p> | <p>Řešené problémy: {{ object.problem.all | join:", " }}</p> | ||||||
| 
 | 
 | ||||||
| <p>Řešitelé: {{ object.resitele.all | join:", " }}</p> | <p>Řešitelé: {{ object.resitele.all | join:", " }}</p> | ||||||
|  | @ -27,21 +80,33 @@ | ||||||
| {% endif %} | {% endif %} | ||||||
| 
 | 
 | ||||||
| {# Hodnocení: #} | {# Hodnocení: #} | ||||||
| {# FIXME: Udělat jako formulář #} |  | ||||||
| <h3>Hodnocení:</h3> | <h3>Hodnocení:</h3> | ||||||
| {% if object.hodnoceni_set.all %} | <form method=post><table> | ||||||
| <table> | {% csrf_token %} | ||||||
|  | {{ form.management_form }} | ||||||
|  | <table id="form_set"> | ||||||
| <tr><th>Problém</th><th>Body</th><th>Číslo pro body</th></tr> | <tr><th>Problém</th><th>Body</th><th>Číslo pro body</th></tr> | ||||||
| {% for h in object.hodnoceni_set.all %} | {% for subform in form %} | ||||||
| <tr> | 	<tr class="hodnoceni"> | ||||||
| 	<td>{{ h.problem }}</a></td> | 		<td>{{ subform.problem }}</td> | ||||||
| 	<td>{{ h.body }}</td> | 		<td>{{ subform.body }}</td> | ||||||
| 	<td>{{ h.cislo_body }}</td></tr> | 		<td>{{ subform.cislo_body }}</td> | ||||||
|  | 		<td><input type=button class="smazat_hodnoceni" value="Smazat" id="id_{{subform.prefix}}-jsremove"></td> | ||||||
|  | 	</tr> | ||||||
| {% endfor %} | {% endfor %} | ||||||
| </table> | </table> | ||||||
| {% else %} |  | ||||||
| <p>Ještě nebylo hodnoceno</p> |  | ||||||
| {% endif %} |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | <input type=button id="pridat_hodnoceni" value="Přidat hodnocení"> | ||||||
|  | <input type=submit></form> | ||||||
|  | 
 | ||||||
|  | <table id="empty_form" style="display: none;"> | ||||||
|  | 	<tr class="hodnoceni"> | ||||||
|  | 		<td>{{ form.empty_form.problem }}</td> | ||||||
|  | 		<td>{{ form.empty_form.body }}</td> | ||||||
|  | 		<td>{{ form.empty_form.cislo_body }}</td> | ||||||
|  | 		<td><input type=button class="smazat_hodnoceni" value="Smazat" id="id_{{form.empty_form.prefix}}-jsremove"></td> | ||||||
|  | 	</tr> | ||||||
|  | </table> | ||||||
|  | 
 | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
|  | @ -4,6 +4,14 @@ | ||||||
| 
 | 
 | ||||||
| {% block content %} | {% block content %} | ||||||
| 
 | 
 | ||||||
|  | <form method=get action=.> | ||||||
|  | {{ filtr.resitele }} | ||||||
|  | {{ filtr.problemy }} | ||||||
|  | Od: {{ filtr.reseni_od }} | ||||||
|  | Do: {{ filtr.reseni_do }} | ||||||
|  | <input type=submit value="→"> | ||||||
|  | </form> | ||||||
|  | 
 | ||||||
| <table> | <table> | ||||||
| 	<tr> | 	<tr> | ||||||
| 		<td></td> {# Prázdná buňka v levém horním rohu #} | 		<td></td> {# Prázdná buňka v levém horním rohu #} | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| {% extends "seminar/zadani/base.html" %} | {% extends "base.html" %} | ||||||
| {% load staticfiles %} | {% load staticfiles %} | ||||||
| {% block script %} | {% block script %} | ||||||
|     <!--script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script!--> |     <!--script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script!--> | ||||||
|  |  | ||||||
|  | @ -20,13 +20,14 @@ | ||||||
| <h2><strong>Tvorba čísla</strong></h2> | <h2><strong>Tvorba čísla</strong></h2> | ||||||
| 
 | 
 | ||||||
| <ul> | <ul> | ||||||
| 	<li><a href="/admin/seminar/problemnavrh/add/"><strong>přidat téma</strong></a></li> | 	<li><a href="/admin/seminar/problem/add/"><strong>přidat téma</strong></a></li> | ||||||
| 	<li><strong>korektury</strong> | 	<li><strong>korektury</strong> | ||||||
| 	<ul> | 	<ul> | ||||||
| 		<li><a href="/korektury/">korekturování</a></li> | 		<li><a href="/korektury/">korekturování</a></li> | ||||||
| 		<li><a href="/admin/korektury/korekturovanepdf/add/">přidat pdf k opravám</a></li> | 		<li><a href="/admin/korektury/korekturovanepdf/add/">přidat pdf k opravám</a></li> | ||||||
| 	</ul> | 	</ul> | ||||||
| 	</li> | 	</li> | ||||||
|  | 	<li><a href="{% url 'odevzdavatko_tabulka' %}"><strong>zadávání bodů</strong></a></li> | ||||||
| 	<li><a href='{{ posledni_cislo_url }}'><strong>poslední vydané číslo </strong></a></li> | 	<li><a href='{{ posledni_cislo_url }}'><strong>poslední vydané číslo </strong></a></li> | ||||||
| </ul> | </ul> | ||||||
| <hr /> | <hr /> | ||||||
|  | @ -58,6 +59,7 @@ | ||||||
| <h2><strong>Soustředění</strong></h2> | <h2><strong>Soustředění</strong></h2> | ||||||
| 
 | 
 | ||||||
| <ul> | <ul> | ||||||
|  | 	<li><a href="/admin/seminar/soustredeni/add/">přidat soustředění</a></li> | ||||||
| 	<li><strong>přednášky</strong> | 	<li><strong>přednášky</strong> | ||||||
| 
 | 
 | ||||||
| 	<ul> | 	<ul> | ||||||
|  |  | ||||||
|  | @ -2,8 +2,6 @@ | ||||||
| {% load staticfiles %} | {% load staticfiles %} | ||||||
| 
 | 
 | ||||||
| {% block script %} | {% block script %} | ||||||
|     <!--script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script!--> |  | ||||||
|     {{form.media}} |  | ||||||
|     <script src="{% static 'seminar/prihlaska.js' %}"></script> |     <script src="{% static 'seminar/prihlaska.js' %}"></script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,8 +1,6 @@ | ||||||
| {% extends "base.html" %} | {% extends "base.html" %} | ||||||
| {% load staticfiles %} | {% load staticfiles %} | ||||||
| {% block script %} | {% block script %} | ||||||
|     <!--script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script!--> |  | ||||||
|     {{form.media}} |  | ||||||
|     <script src="{% static 'seminar/dynamic_formsets.js' %}"></script> |     <script src="{% static 'seminar/dynamic_formsets.js' %}"></script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,8 +2,6 @@ | ||||||
| {% load staticfiles %} | {% load staticfiles %} | ||||||
| 
 | 
 | ||||||
| {% block script %} | {% block script %} | ||||||
|     <!--script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script!--> |  | ||||||
|     {{form.media}} |  | ||||||
|     <script src="{% static 'seminar/prihlaska.js' %}"></script> |     <script src="{% static 'seminar/prihlaska.js' %}"></script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -24,7 +24,7 @@ | ||||||
| <div class="tema_pole"> | <div class="tema_pole"> | ||||||
| 
 | 
 | ||||||
|   <h3> |   <h3> | ||||||
|     <a href='{{ rocnik.verejne_url }}'>Téma {{ tematko.nazev }}</a> |     <a href='{{ tematko.verejne_url }}'>Téma {{ tematko.nazev }}</a> | ||||||
|   </h3> |   </h3> | ||||||
| 
 | 
 | ||||||
|   <div class="flip-card" id="tema-rozcestnik"> |   <div class="flip-card" id="tema-rozcestnik"> | ||||||
|  |  | ||||||
|  | @ -1,3 +1,12 @@ | ||||||
|  | <script> | ||||||
|  | function preddeadline() { | ||||||
|  |   alert("Řešení, která nám přijdou do tohoto deadlinu, se pokusíme opravit co nejdříve, abyste měli ještě šanci si je ještě opravit před definitivním deadlinem čísla."); | ||||||
|  | } | ||||||
|  | function sousdeadline() { | ||||||
|  |   alert("Body za řešení, která nám přijdou do tohoto deadlinu, se ještě započítají pro účast na připravovaném soustředění."); | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
| {% extends 'base.html' %} | {% extends 'base.html' %} | ||||||
| 
 | 
 | ||||||
| {% load humanize %} | {% load humanize %} | ||||||
|  | @ -7,10 +16,22 @@ | ||||||
| {% block content %} | {% block content %} | ||||||
| 
 | 
 | ||||||
| {% if nejblizsi_deadline %} | {% if nejblizsi_deadline %} | ||||||
|  | <hr> | ||||||
| <div class="odpocet"> | <div class="odpocet"> | ||||||
| 	<p><b><big>Do konce <a href="/zadani/aktualni/">odeslání řešení</a> {% if typ_deadline == 'soustredeni' %}(pro účast na soustředění) {% elif typ_deadline == 'preddeadline' %}(pro otištění došlých řešení) {% endif %}zbývá: | 	<p><b><big>Do | ||||||
|  |   {% if typ_deadline == 'soustredeni' %} | ||||||
|  |   <a href="" onClick="sousdeadline()" | ||||||
|  |      title="Body za řešení, která nám přijdou do tohoto deadlinu, se ještě započítají pro účast na připravovaném soustředění."> | ||||||
|  |      deadlinu</a> odeslání <a href="/zadani/aktualni/">řešení | ||||||
|  |   </a> pro účast na soustředění | ||||||
|  | 
 | ||||||
|  |   {% elif typ_deadline == 'preddeadline' %} <a href="" onClick="preddeadline()" | ||||||
|  |   title="Řešení, která nám přijdou do tohoto deadlinu, se pokusíme opravit co nejdříve, abyste měli ještě šanci si je ještě opravit před definitivním deadlinem čísla.">1. deadlinu</a> aktuálního <a href="/zadani/aktualni/">čísla</a>  | ||||||
|  |   {% else %} deadlinu aktuálního <a href="/zadani/aktualni/">čísla</a>  | ||||||
|  |   {% endif %}zbývá: | ||||||
|       {{nejblizsi_deadline|timeuntil}}</big></b></p> |       {{nejblizsi_deadline|timeuntil}}</big></b></p> | ||||||
| </div> | </div> | ||||||
|  | <hr> | ||||||
| {% endif %} | {% endif %} | ||||||
| 
 | 
 | ||||||
| <div class=titulnistrana> | <div class=titulnistrana> | ||||||
|  | @ -29,7 +50,7 @@ | ||||||
|   <div> |   <div> | ||||||
|     M&M je korespondenční seminář. Vydáváme časopis a v něm zajímavé podněty k přemýšlení. Ty na ně můžeš reagovat, |     M&M je korespondenční seminář. Vydáváme časopis a v něm zajímavé podněty k přemýšlení. Ty na ně můžeš reagovat, | ||||||
|     experimentovat a objevovat s námi fascinující zákoutí matiky, fyziky a informatiky. |     experimentovat a objevovat s námi fascinující zákoutí matiky, fyziky a informatiky. | ||||||
|     <a href="auth/registrace"> <div class="button"> Zaregistruj se! </div> </a> {# FIXME odkaz #} |     <a href="prihlaska/?"> <div class="button"> Zaregistruj se! </div> </a> | ||||||
|     M&M je taky soutěž. Za svá řešení dostaneš body a můžeš vyhrát zajímavé ceny, dostat se |     M&M je taky soutěž. Za svá řešení dostaneš body a můžeš vyhrát zajímavé ceny, dostat se | ||||||
|     na Matfyz bez přijímaček a především, můžeš s námi jet na skvělé soustředění. |     na Matfyz bez přijímaček a především, můžeš s námi jet na skvělé soustředění. | ||||||
|     <a href="cojemam/odmeny"> <div class="button"> Co můžeš vyhrát? </div> </a> {# FIXME odkaz #} |     <a href="cojemam/odmeny"> <div class="button"> Co můžeš vyhrát? </div> </a> {# FIXME odkaz #} | ||||||
|  |  | ||||||
|  | @ -5,11 +5,11 @@ | ||||||
| 
 | 
 | ||||||
|   <h1> |   <h1> | ||||||
|     {% block nadpis1a %}{% block nadpis1b %} |     {% block nadpis1a %}{% block nadpis1b %} | ||||||
|       Průběžné výsledky {{ vysledkovka.rocnik }}. ročníku |       Průběžné výsledky {{ rocnik.rocnik }}. ročníku | ||||||
|     {% endblock %}{% endblock %} |     {% endblock %}{% endblock %} | ||||||
|   </h1> |   </h1> | ||||||
| 
 | 
 | ||||||
|   {% if vysledkovka %} |   {% if radky_vysledkovky %} | ||||||
|     {% include "seminar/vysledkovka_rocnik.html" %} |     {% include "seminar/vysledkovka_rocnik.html" %} | ||||||
|   {% else %} |   {% else %} | ||||||
|     <p>V tomto ročníku zatím žádné výsledky nejsou.</p> |     <p>V tomto ročníku zatím žádné výsledky nejsou.</p> | ||||||
|  | @ -23,7 +23,7 @@ | ||||||
|   {% if user.je_org and vysledkovka_s_neverejnymi %} |   {% if user.je_org and vysledkovka_s_neverejnymi %} | ||||||
|     <div class='mam-org-only'> |     <div class='mam-org-only'> | ||||||
|     <h1>Výsledky včetně neveřejných</h1> |     <h1>Výsledky včetně neveřejných</h1> | ||||||
|     {% with vysledkovka_s_neverejnymi as vysledkovka %} |     {% with vysledkovka_s_neverejnymi as radky_vysledkovky %} | ||||||
|       {% include "seminar/vysledkovka_rocnik.html" %} |       {% include "seminar/vysledkovka_rocnik.html" %} | ||||||
|     {% endwith %} |     {% endwith %} | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|  | @ -5,79 +5,47 @@ | ||||||
| {% endblock %}{% endblock %} | {% endblock %}{% endblock %} | ||||||
| 
 | 
 | ||||||
| {% block content %} | {% block content %} | ||||||
| <div> | <div class="stranka_aktualni_zadani"> | ||||||
|    |    | ||||||
| {% with nastaveni.aktualni_cislo as ac %} | {% with nastaveni.aktualni_cislo as ac %} | ||||||
|   |   | ||||||
| {# Zobrazovani neverejnych zadani jen organizatorum #} | {# Zobrazovani neverejnych zadani jen organizatorum #} | ||||||
| {% if user.je_org or verejne %} | {% if user.je_org or verejne %} | ||||||
| {% if user.je_org and not verejne %}<div class="mam-org-only">{% endif %} | 	{% if user.je_org and not verejne %}<div class="mam-org-only">{% endif %} | ||||||
|  | 
 | ||||||
|  | 	<hr> | ||||||
|  | 	<div class="zadani_termin"> | ||||||
|  | 	Termíny pro odeslání řešení {{ac.poradi}}. série:<br> | ||||||
| 	 | 	 | ||||||
|   {% if ac.zadane_problemy.all %} |  | ||||||
| 	{% if ac.datum_deadline_soustredeni %}	 | 	{% if ac.datum_deadline_soustredeni %}	 | ||||||
|    	  <div class="zadani_azad_termin"> | 			<span class="datum">{{ac.datum_deadline_soustredeni}}</span> pro účast na soustředění<br> | ||||||
| 	  	  Termín odeslání {{ac.cislo}}. série pro účast na soustředění: |  | ||||||
|         {{ac.datum_deadline_soustredeni}} |  | ||||||
|   	  </div> |  | ||||||
|     {% endif %} |  | ||||||
|   {% endif %} |  | ||||||
|   {% if ac.zadane_problemy.all %} |  | ||||||
| 	  <div class="zadani_azad_termin"> |  | ||||||
| 		  Termín odeslání {{ac.cislo}}. série: {{ac.datum_deadline}} |  | ||||||
| 	  </div> |  | ||||||
| 	{% endif %} | 	{% endif %} | ||||||
| 
 | 
 | ||||||
|  | 	{% if ac.datum_preddeadline %} | ||||||
|  | 			<span class="datum">{{ac.datum_preddeadline}}</span> pro otištění v dalším čísle<br> | ||||||
|  | 	{% endif %} | ||||||
|  | 
 | ||||||
|  | 	{% if ac.datum_deadline %} | ||||||
|  | 			<span class="datum">{{ac.datum_deadline}}</span> definitivní deadline<br> | ||||||
|  | 	{% endif %} | ||||||
|  | 
 | ||||||
|  | 	</div> | ||||||
|  | 	<hr> | ||||||
|  | 
 | ||||||
|  | 	{% if ac.titulka_nahled and ac.pdf %} | ||||||
|  | 		<a href="{{ac.pdf.url}}"><img id="azad_obrazek" src="{{ac.titulka_nahled.url}}" alt=Titulní strana {{ac.poradi}}. čísla></a> | ||||||
|  | 	{% endif %} | ||||||
| 
 | 
 | ||||||
| 	{% if ac.pdf %} | 	{% if ac.pdf %} | ||||||
| 		<h3>Aktuální témata najdete v <a href="{{ac.pdf.url}}">aktuálním čísle v PDF</a>.</h3> | 		<h3>Aktuální témata najdete v <a href="{{ac.pdf.url}}">aktuálním čísle v PDF</a>.</h3> | ||||||
| 	{% endif %} | 	{% endif %} | ||||||
| 		<!--Toto jsem zakomentoval, aby se tam nezobrazovala temata, ale text, že vše najdou pouze v PDF--> |  | ||||||
| 		{% if False %}	 |  | ||||||
| 			{% for sada in jednorazove_problemy %} |  | ||||||
| 				{# podnadpisy, kdyz neni zakomentuje se nadpis #} |  | ||||||
| 				{% if not sada %}<!--{% endif %} |  | ||||||
| 				<h2>{% cycle 'Úlohy' 'Seriál' %}</h2> |  | ||||||
| 				{% if not sada %}-->{% endif %} |  | ||||||
| 				{# publikace jednotlivych zadani #} |  | ||||||
| 				{% for problem in sada %} |  | ||||||
| 					{% for tag in problem.zamereni.names %} |  | ||||||
| 						 <a name="zam_{{tag}}"></a> |  | ||||||
| 					{% endfor %} |  | ||||||
| 
 | 
 | ||||||
| 					{# TODO použít {{problem.kod_v_rocniku}} ? vrací 4.u1 místo 4.1 #} | 	{% if user.je_org and not verejne%}</div>{% endif %} | ||||||
| 					<h3>{{problem.cislo_zadani.cislo}}.{{problem.kod}} {{problem.nazev}} {{ problem.body_v_zavorce }}</h3> |  | ||||||
| 					{% autoescape off %}{{problem.text_zadani}}{% endautoescape %} |  | ||||||
| 					<hr> |  | ||||||
| 				{% endfor %} |  | ||||||
| 			{% empty %} |  | ||||||
| 				Aktuálně nejsou zadané žádné úlohy k řešení. |  | ||||||
| 			{% endfor %} |  | ||||||
| 		{% endif %} |  | ||||||
| 
 | 
 | ||||||
| {% if user.je_org and not verejne%}</div>{% endif %} |  | ||||||
| {% else %} | {% else %} | ||||||
| 	<h2>Aktuálně nejsou zveřejněny žádné úlohy</h2> | 	<h2>Aktuálně nejsou zveřejněny žádné úlohy</h2> | ||||||
| {% endif %} | {% endif %} | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| 	{% if False %}	 |  | ||||||
|     <h2>Témata</h2> |  | ||||||
|       <ul> |  | ||||||
|       {% for problem in temata %} |  | ||||||
|         {# TODO použít {{problem.kod_v_rocniku}} ? vrací t4 místo 4 #} |  | ||||||
| 	<li> |  | ||||||
|         <a href="{{problem.verejne_url}}">Téma {{problem.kod}}: {{problem.nazev}}</a> |  | ||||||
| 	</li> |  | ||||||
|       {% empty %} |  | ||||||
| 				{% if ac.pdf %} |  | ||||||
| 					<p>Aktuální témata najdete v <a href="{{ac.pdf.url}}">aktuálním čísle v PDF</a>.</p> |  | ||||||
| 				{% else %} |  | ||||||
| 					<p>Aktuálně nemáme žádná témata.</p> |  | ||||||
| 				{% endif %} |  | ||||||
|       {% endfor %} |  | ||||||
|       </ul> |  | ||||||
| 	{% endif %} |  | ||||||
| 
 |  | ||||||
| {% endwith %} | {% endwith %} | ||||||
| 
 | 
 | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
|  | @ -581,7 +581,7 @@ def gen_temata(rnd, rocniky, rocnik_cisla, organizatori): | ||||||
| 		rocnik_temata.append(letosni_temata) | 		rocnik_temata.append(letosni_temata) | ||||||
| 	return rocnik_temata | 	return rocnik_temata | ||||||
| 
 | 
 | ||||||
| def gen_ulohy_tematu(rnd, organizatori, tema, kod, cislo, cislo_se_vzorakem): | def gen_ulohy_tematu(rnd, organizatori, resitele, tema, kod, cislo, cislo_se_vzorakem): | ||||||
| 	""" Generování úlohy k danému tématu. """ | 	""" Generování úlohy k danému tématu. """ | ||||||
| 	 | 	 | ||||||
| 	# Proměnné pro náhodné generování názvů a zadání. | 	# Proměnné pro náhodné generování názvů a zadání. | ||||||
|  | @ -627,10 +627,14 @@ def gen_ulohy_tematu(rnd, organizatori, tema, kod, cislo, cislo_se_vzorakem): | ||||||
| 	uloha_zadani = UlohaZadaniNode.objects.create(uloha=uloha, first_child = zad, root=tema.temavcislenode_set.first().root) | 	uloha_zadani = UlohaZadaniNode.objects.create(uloha=uloha, first_child = zad, root=tema.temavcislenode_set.first().root) | ||||||
| 	uloha.ulohazadaninode = uloha_zadani | 	uloha.ulohazadaninode = uloha_zadani | ||||||
| 
 | 
 | ||||||
|  | 	# Generování řešení a hodnocení k úloze | ||||||
|  | 	gen_reseni_ulohy(rnd, [cislo], uloha, len(resitele)//4, 1, | ||||||
|  | 					 resitele, resitele) | ||||||
|  | 
 | ||||||
| 	return uloha, uloha_zadani | 	return uloha, uloha_zadani | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori): | def gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori, resitele): | ||||||
| 	logger.info('Generuji úlohy k tématům...') | 	logger.info('Generuji úlohy k tématům...') | ||||||
| 
 | 
 | ||||||
| 	# Ke každému ročníku si vezmeme příslušná čísla a témata | 	# Ke každému ročníku si vezmeme příslušná čísla a témata | ||||||
|  | @ -663,7 +667,7 @@ def gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori) | ||||||
| 					 | 					 | ||||||
| 				# Generujeme 1 až 4 úložky k tomuto témátku do tohoto čísla. | 				# Generujeme 1 až 4 úložky k tomuto témátku do tohoto čísla. | ||||||
| 				for kod in range(1, rnd.randint(1, 4)): | 				for kod in range(1, rnd.randint(1, 4)): | ||||||
| 					u, uz = gen_ulohy_tematu(rnd, organizatori, tema, kod,  | 					u, uz = gen_ulohy_tematu(rnd, organizatori, resitele, tema, kod, | ||||||
| 						cislo, cislo_se_vzorakem) | 						cislo, cislo_se_vzorakem) | ||||||
| 
 | 
 | ||||||
| 					insert_last_child(tema_node, uz) | 					insert_last_child(tema_node, uz) | ||||||
|  | @ -860,7 +864,7 @@ def create_test_data(size = 6, rnd = None): | ||||||
| 		"MFI", 8) | 		"MFI", 8) | ||||||
| 
 | 
 | ||||||
| 	# generování úloh k tématům ve všech číslech | 	# generování úloh k tématům ve všech číslech | ||||||
| 	gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori) | 	gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori, resitele) | ||||||
| 
 | 
 | ||||||
| 	#generování soustředění | 	#generování soustředění | ||||||
| 	soustredeni = gen_soustredeni(rnd, resitele, organizatori) | 	soustredeni = gen_soustredeni(rnd, resitele, organizatori) | ||||||
|  |  | ||||||
|  | @ -217,6 +217,17 @@ def get_prev_node_of_type(node, type): | ||||||
| 			return current | 			return current | ||||||
| 	return None | 	return None | ||||||
| 
 | 
 | ||||||
|  | def get_upper_node_of_type(node, type): | ||||||
|  | 	# vrací první vyšší node daného typu (ignoruje sourozence) | ||||||
|  | 	if node is None: | ||||||
|  | 		return | ||||||
|  | 	current = node | ||||||
|  | 	while get_parent(current) is not None: | ||||||
|  | 		current = get_parent(current) | ||||||
|  | 		if isinstance(current, type): | ||||||
|  | 			return current | ||||||
|  | 	return None | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # Exception, kterou některé metody při špatném použití mohou házet | # Exception, kterou některé metody při špatném použití mohou házet | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| from django.urls import path, include, re_path | from django.urls import path, include, re_path | ||||||
| from django.contrib.auth.decorators import login_required | from django.contrib.auth.decorators import login_required | ||||||
| from . import views, export | from . import views, export | ||||||
| from .utils import org_required, resitel_required | from .utils import org_required, resitel_required, viewMethodSwitch | ||||||
| from django.views.generic.base import RedirectView | from django.views.generic.base import RedirectView | ||||||
| 
 | 
 | ||||||
| urlpatterns = [ | urlpatterns = [ | ||||||
|  | @ -9,25 +9,25 @@ urlpatterns = [ | ||||||
| #	path('<int:rocnik>/t<int:tematko>/', views.TematkoView), | #	path('<int:rocnik>/t<int:tematko>/', views.TematkoView), | ||||||
| 
 | 
 | ||||||
| 	# Organizatori | 	# Organizatori | ||||||
| 	path('co-je-MaM/organizatori/', views.CojemamOrganizatoriView.as_view(), name='organizatori'), | 	path('o-nas/organizatori/', views.CojemamOrganizatoriView.as_view(), name='organizatori'), | ||||||
| 	path('co-je-MaM/organizatori/organizovali/', views.CojemamOrganizatoriStariView.as_view(), name='stari_organizatori'), | 	path('o-nas/organizatori/organizovali/', views.CojemamOrganizatoriStariView.as_view(), name='stari_organizatori'), | ||||||
| 
 | 
 | ||||||
| 	# Archiv | 	# Archiv | ||||||
| 	path('archiv/rocniky/', views.ArchivView.as_view(), name="seninar_archiv_rocniky"), | 	path('archiv/rocniky/', views.ArchivView.as_view(), name="seminar_archiv_rocniky"), | ||||||
| 	path('archiv/temata/', views.ArchivTemataView.as_view(), name="seninar_archiv_temata"), | 	path('archiv/temata/', views.ArchivTemataView.as_view(), name="seminar_archiv_temata"), | ||||||
| 
 | 
 | ||||||
| 	path('rocnik/<int:rocnik>/', views.RocnikView.as_view(), name='seminar_rocnik'), | 	path('rocnik/<int:rocnik>/', views.RocnikView.as_view(), name='seminar_rocnik'), | ||||||
| 	path('cislo/<int:rocnik>.<str:cislo>/', views.CisloView.as_view(), name='seminar_cislo'), | 	path('cislo/<int:rocnik>.<str:cislo>/', views.CisloView.as_view(), name='seminar_cislo'), | ||||||
| 	path('problem/<int:pk>/', views.ProblemView.as_view(), name='seminar_problem'), | 	path('problem/<int:pk>/', views.ProblemView.as_view(), name='seminar_problem'), | ||||||
| 	path('treenode/<int:pk>/', views.TreeNodeView.as_view(), name='seminar_treenode'), | 	#path('treenode/<int:pk>/', views.TreeNodeView.as_view(), name='seminar_treenode'), | ||||||
| 	path('treenode/<int:pk>/json/', views.TreeNodeJSONView.as_view(), name='seminar_treenode_json'), | 	#path('treenode/<int:pk>/json/', views.TreeNodeJSONView.as_view(), name='seminar_treenode_json'), | ||||||
| 	path('treenode/text/<int:pk>/', views.TextWebView.as_view(), name='seminar_textnode_web'), | 	#path('treenode/text/<int:pk>/', views.TextWebView.as_view(), name='seminar_textnode_web'), | ||||||
| 	path('treenode/editor/pridat/<str:co>/<int:pk>/<str:kam>/', views.TreeNodePridatView.as_view(), name='treenode_pridat'), | 	#path('treenode/editor/pridat/<str:co>/<int:pk>/<str:kam>/', views.TreeNodePridatView.as_view(), name='treenode_pridat'), | ||||||
| 	path('treenode/editor/smazat/<int:pk>/', views.TreeNodeSmazatView.as_view(), name='treenode_smazat'), | 	#path('treenode/editor/smazat/<int:pk>/', views.TreeNodeSmazatView.as_view(), name='treenode_smazat'), | ||||||
| 	path('treenode/editor/odvesitpryc/<int:pk>/', views.TreeNodeOdvesitPrycView.as_view(), name='treenode_odvesitpryc'), | 	#path('treenode/editor/odvesitpryc/<int:pk>/', views.TreeNodeOdvesitPrycView.as_view(), name='treenode_odvesitpryc'), | ||||||
| 	path('treenode/editor/podvesit/<int:pk>/<str:kam>/', views.TreeNodePodvesitView.as_view(), name='treenode_podvesit'), | 	#path('treenode/editor/podvesit/<int:pk>/<str:kam>/', views.TreeNodePodvesitView.as_view(), name='treenode_podvesit'), | ||||||
| 	path('treenode/editor/prohodit/<int:pk>/', views.TreeNodeProhoditView.as_view(), name='treenode_prohodit'), | 	#path('treenode/editor/prohodit/<int:pk>/', views.TreeNodeProhoditView.as_view(), name='treenode_prohodit'), | ||||||
| 	path('treenode/sirotcinec/', views.SirotcinecView.as_view(), name='seminar_treenode_sirotcinec'), | 	#path('treenode/sirotcinec/', views.SirotcinecView.as_view(), name='seminar_treenode_sirotcinec'), | ||||||
| 	#path('problem/(?P<pk>\d+)/(?P<prispevek>\d+)/', views.PrispevekView.as_view(), name='seminar_problem_prispevek'), | 	#path('problem/(?P<pk>\d+)/(?P<prispevek>\d+)/', views.PrispevekView.as_view(), name='seminar_problem_prispevek'), | ||||||
| 
 | 
 | ||||||
| 	# Soustredeni | 	# Soustredeni | ||||||
|  | @ -57,13 +57,14 @@ urlpatterns = [ | ||||||
| 	), | 	), | ||||||
| 
 | 
 | ||||||
| 	# Zadani | 	# Zadani | ||||||
| 	path('zadani/aktualni/', views.AktualniZadaniView.as_view(), name='seminar_aktualni_zadani'), | #	path('zadani/aktualni/', views.AktualniZadaniView.as_view(), name='seminar_aktualni_zadani'), | ||||||
| 	path('zadani/temata/', views.ZadaniTemataView, name='seminar_temata'), | 	path('aktualni/zadani/', views.AktualniZadaniView, name='seminar_aktualni_zadani'), | ||||||
| 	#path('zadani/vysledkova-listina/', views.ZadaniAktualniVysledkovkaView, name='seminar_vysledky'), | 	#path('aktualni/temata/', views.ZadaniTemataView, name='seminar_temata'), | ||||||
|  | 	path('aktualni/vysledkova-listina/', views.ZadaniAktualniVysledkovkaView, name='seminar_aktualni_vysledky'), | ||||||
| 	path('stare-novinky/', views.StareNovinkyView.as_view(), name='stare_novinky'), | 	path('stare-novinky/', views.StareNovinkyView.as_view(), name='stare_novinky'), | ||||||
| 
 | 
 | ||||||
| 	# Clanky | 	# Clanky | ||||||
| 	path('clanky/resitel/', views.ClankyResitelView.as_view(), name='clanky_resitel'), | 	path('archiv/clanky/', views.ClankyResitelView.as_view(), name='clanky_resitel'), | ||||||
| 	#path('clanky/org/', views.ClankyOrganizatorView.as_view(), name='clanky_organizator'), | 	#path('clanky/org/', views.ClankyOrganizatorView.as_view(), name='clanky_organizator'), | ||||||
| 
 | 
 | ||||||
| 	# Aesop | 	# Aesop | ||||||
|  | @ -123,11 +124,6 @@ urlpatterns = [ | ||||||
| 		org_required(views.soustredeniObalkyView), | 		org_required(views.soustredeniObalkyView), | ||||||
| 		name='seminar_soustredeni_obalky' | 		name='seminar_soustredeni_obalky' | ||||||
| 	), | 	), | ||||||
| 	path( |  | ||||||
| 		'org/vloz_body/<int:tema>/', |  | ||||||
| 		org_required(views.VlozBodyView.as_view()), |  | ||||||
| 		name='seminar_org_vlozbody' |  | ||||||
| 	), |  | ||||||
| 	# příprava na nestatický orgorozcestník | 	# příprava na nestatický orgorozcestník | ||||||
| 	path( | 	path( | ||||||
| 		'org/rozcestnik/', | 		'org/rozcestnik/', | ||||||
|  | @ -136,16 +132,16 @@ urlpatterns = [ | ||||||
| 	), | 	), | ||||||
| 
 | 
 | ||||||
| 	path('prihlaska/',views.prihlaskaView, name='seminar_prihlaska'), | 	path('prihlaska/',views.prihlaskaView, name='seminar_prihlaska'), | ||||||
| 	path('login/', views.LoginView.as_view(), name='login'), | 	path('prihlasit/', views.LoginView.as_view(), name='login'), | ||||||
| 	path('logout/', views.LogoutView.as_view(), name='logout'), | 	path('odhlasit/', views.LogoutView.as_view(), name='logout'), | ||||||
| 	path('resitel/', resitel_required(views.ResitelView.as_view()), name='seminar_resitel'), | 	path('resitel/', resitel_required(views.ResitelView.as_view()), name='seminar_resitel'), | ||||||
| 	path('reset_password/', views.PasswordResetView.as_view(), name='reset_password'), | 	path('reset-hesla/', views.PasswordResetView.as_view(), name='reset_password'), | ||||||
| 	path('change_password/', views.PasswordChangeView.as_view(), name='change_password'), | 	path('zmena-hesla/', views.PasswordChangeView.as_view(), name='change_password'), | ||||||
| 	path('reset_password_done/', views.PasswordResetDoneView.as_view(), name='reset_password_done'), | 	path('reset-hesla/2/', views.PasswordResetDoneView.as_view(), name='reset_password_done'), | ||||||
| 	path('reset_password_confirm/<uidb64>/<token>/', views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'), | 	path('reset-hesla/potvrzeni/<uidb64>/<token>/', views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'), | ||||||
| 	path('reset_password_complete/', views.PasswordResetCompleteView.as_view(), name='reset_password_complete'), | 	path('reset-hesla/hotovo/', views.PasswordResetCompleteView.as_view(), name='reset_password_complete'), | ||||||
| 	path( | 	path( | ||||||
| 		'resitel_edit', | 		'resitel/osobni-udaje/', | ||||||
| 		login_required(views.resitelEditView, login_url='/login/'), | 		login_required(views.resitelEditView, login_url='/login/'), | ||||||
| 		name='seminar_resitel_edit' | 		name='seminar_resitel_edit' | ||||||
| 	), | 	), | ||||||
|  | @ -154,9 +150,9 @@ urlpatterns = [ | ||||||
| 	path('profil/', views.profilView, name='profil'), | 	path('profil/', views.profilView, name='profil'), | ||||||
| 
 | 
 | ||||||
| 	# Autocomplete | 	# Autocomplete | ||||||
| 	path('autocomplete/skola/',views.SkolaAutocomplete.as_view(), name='autocomplete_skola'), | 	path('api/autocomplete/skola/',views.SkolaAutocomplete.as_view(), name='autocomplete_skola'), | ||||||
| 	path('autocomplete/resitel/', org_required(views.ResitelAutocomplete.as_view()), name='autocomplete_resitel'), | 	path('api/autocomplete/resitel/', org_required(views.ResitelAutocomplete.as_view()), name='autocomplete_resitel'), | ||||||
| 	path('autocomplete/problem/odevzdatelny',views.OdevzdatelnyProblemAutocomplete.as_view(), name='autocomplete_problem_odevzdatelny'), | 	path('api/autocomplete/problem/odevzdatelny',views.OdevzdatelnyProblemAutocomplete.as_view(), name='autocomplete_problem_odevzdatelny'), | ||||||
| 
 | 
 | ||||||
| 	path('temp/add_solution', org_required(views.AddSolutionView.as_view()), name='seminar_vloz_reseni'), | 	path('temp/add_solution', org_required(views.AddSolutionView.as_view()), name='seminar_vloz_reseni'), | ||||||
| 	path('temp/nahraj_reseni', resitel_required(views.NahrajReseniView.as_view()), name='seminar_nahraj_reseni'), | 	path('temp/nahraj_reseni', resitel_required(views.NahrajReseniView.as_view()), name='seminar_nahraj_reseni'), | ||||||
|  | @ -174,8 +170,7 @@ urlpatterns = [ | ||||||
| 
 | 
 | ||||||
| 	path('temp/reseni/', org_required(views.TabulkaOdevzdanychReseniView.as_view()), name='odevzdavatko_tabulka'), | 	path('temp/reseni/', org_required(views.TabulkaOdevzdanychReseniView.as_view()), name='odevzdavatko_tabulka'), | ||||||
| 	path('temp/reseni/<int:problem>/<int:resitel>/', org_required(views.ReseniProblemuView.as_view()), name='odevzdavatko_reseni_resitele_k_problemu'), | 	path('temp/reseni/<int:problem>/<int:resitel>/', org_required(views.ReseniProblemuView.as_view()), name='odevzdavatko_reseni_resitele_k_problemu'), | ||||||
| 	path('temp/reseni/<int:pk>', org_required(views.DetailReseniView.as_view()), name='odevzdavatko_detail_reseni'), | 	path('temp/reseni/<int:pk>', org_required(viewMethodSwitch(get=views.DetailReseniView.as_view(), post=views.hodnoceniReseniView)), name='odevzdavatko_detail_reseni'), | ||||||
| 	path('temp/reseni/all', org_required(views.SeznamReseniView.as_view())), | 	path('temp/reseni/all', org_required(views.SeznamReseniView.as_view())), | ||||||
| 	path('temp/reseni/akt', org_required(views.SeznamAktualnichReseniView.as_view())), | 	path('temp/reseni/akt', org_required(views.SeznamAktualnichReseniView.as_view())), | ||||||
| 
 |  | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ import datetime | ||||||
| from django.contrib.auth import get_user_model | from django.contrib.auth import get_user_model | ||||||
| from django.contrib.auth.decorators import permission_required | from django.contrib.auth.decorators import permission_required | ||||||
| from html.parser import HTMLParser | from html.parser import HTMLParser | ||||||
|  | from django import views as DjangoViews | ||||||
| 
 | 
 | ||||||
| from django.contrib.auth.models import AnonymousUser | from django.contrib.auth.models import AnonymousUser | ||||||
| from django.contrib.contenttypes.models import ContentType | from django.contrib.contenttypes.models import ContentType | ||||||
|  | @ -16,10 +17,11 @@ import seminar.treelib as t | ||||||
| org_required = permission_required('auth.org', raise_exception=True) | org_required = permission_required('auth.org', raise_exception=True) | ||||||
| resitel_required = permission_required('auth.resitel', raise_exception=True) | resitel_required = permission_required('auth.resitel', raise_exception=True) | ||||||
| User = get_user_model() | User = get_user_model() | ||||||
| User.je_org = lambda self: self.has_perm('auth.org') | # Není to úplně hezké, ale budeme doufat, že to je funkční... | ||||||
| User.je_resitel = lambda self: self.has_perm('auth.resitel') | User.je_org = property(lambda self: self.has_perm('auth.org')) | ||||||
| AnonymousUser.je_org = lambda self: False | User.je_resitel = property(lambda self: self.has_perm('auth.resitel')) | ||||||
| AnonymousUser.je_resitel = lambda self: False | AnonymousUser.je_org = False | ||||||
|  | AnonymousUser.je_resitel = False | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class FirstTagParser(HTMLParser): | class FirstTagParser(HTMLParser): | ||||||
|  | @ -191,3 +193,92 @@ def aktivniResitele(cislo, pouze_letosni=False): | ||||||
| 	else: | 	else: | ||||||
| 		# spojíme querysety s řešiteli loni a letos do daného čísla | 		# spojíme querysety s řešiteli loni a letos do daného čísla | ||||||
| 		return (resi_v_rocniku(loni) | resi_v_rocniku(letos, cislo)).distinct() | 		return (resi_v_rocniku(loni) | resi_v_rocniku(letos, cislo)).distinct() | ||||||
|  | 
 | ||||||
|  | def viewMethodSwitch(get, post): | ||||||
|  | 	""" | ||||||
|  | 	Vrátí view, který zavolá různé jiné views podle toho, kterou metodou je zavolán. | ||||||
|  | 
 | ||||||
|  | 	Inspirováno https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#an-alternative-better-solution, jen jsem to udělal genericky. | ||||||
|  | 
 | ||||||
|  | 	Parametry: | ||||||
|  | 		post	view pro metodu POST | ||||||
|  | 		get	view pro metodu GET | ||||||
|  | 	 | ||||||
|  | 	V obou případech se míní už view jakožto funkce, takže u class-based views se už má použít .as_view() | ||||||
|  | 
 | ||||||
|  | 	TODO: Podpora i pro metodu HEAD? A možná i pro FILES? | ||||||
|  | 	""" | ||||||
|  | 
 | ||||||
|  | 	theGetView = get | ||||||
|  | 	thePostView = post | ||||||
|  | 
 | ||||||
|  | 	class NewView(DjangoViews.View): | ||||||
|  | 		def get(self, request, *args, **kwargs): | ||||||
|  | 			return theGetView(request, *args, **kwargs) | ||||||
|  | 		def post(self, request, *args, **kwargs): | ||||||
|  | 			return thePostView(request, *args, **kwargs) | ||||||
|  | 	 | ||||||
|  | 	return NewView.as_view() | ||||||
|  | 
 | ||||||
|  | def cisla_rocniku(rocnik, jen_verejne=True): | ||||||
|  | 	""" | ||||||
|  | 	Vrátí všechna čísla daného ročníku. | ||||||
|  | 	Parametry: | ||||||
|  | 		rocnik (Rocnik): ročník semináře | ||||||
|  | 		jen_verejne (bool): zda se mají vrátit jen veřejná, nebo všechna čísla | ||||||
|  | 	Vrátí: | ||||||
|  | 		seznam objektů typu Cislo | ||||||
|  | 	"""	 | ||||||
|  | 	if jen_verejne: | ||||||
|  | 		return rocnik.verejna_cisla() | ||||||
|  | 	else: | ||||||
|  | 		return rocnik.cisla.all() | ||||||
|  | 
 | ||||||
|  | def hlavni_problem(problem): | ||||||
|  | 	""" Pro daný problém vrátí jeho nejvyšší nadproblém.""" | ||||||
|  | 	while not(problem.nadproblem == None): | ||||||
|  | 		problem = problem.nadproblem | ||||||
|  | 	return problem | ||||||
|  | 
 | ||||||
|  | def problemy_rocniku(rocnik, jen_verejne=True): | ||||||
|  | 	return  m.Problem.objects.filter(hodnoceni__in = m.Hodnoceni.objects.filter(cislo_body__in = cisla_rocniku(rocnik, jen_verejne))).distinct().select_related('nadproblem').select_related('nadproblem__nadproblem') | ||||||
|  | 
 | ||||||
|  | def problemy_cisla(cislo): | ||||||
|  | 	""" Vrátí seznam všech problémů s body v daném čísle. """ | ||||||
|  | 	return m.Problem.objects.filter(hodnoceni__in = m.Hodnoceni.objects.filter(cislo_body = cislo)).distinct().select_related('nadproblem').select_related('nadproblem__nadproblem') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def hlavni_problemy_f(problemy=None): | ||||||
|  | 	""" Vrátí seznam všech problémů, které již nemají nadproblém. """ | ||||||
|  | 	# hlavní problémy čísla  | ||||||
|  | 	# (mají vlastní sloupeček ve výsledkovce, nemají nadproblém) | ||||||
|  | 	hlavni_problemy = set() | ||||||
|  | 	for p in problemy: | ||||||
|  | 		hlavni_problemy.add(hlavni_problem(p)) | ||||||
|  | 		 | ||||||
|  | 	# zunikátnění | ||||||
|  | 	hlavni_problemy = list(hlavni_problemy) | ||||||
|  | 	hlavni_problemy.sort(key=lambda k: k.kod_v_rocniku) # setřídit podle t1, t2, c3, ... | ||||||
|  | 	 | ||||||
|  | 	return hlavni_problemy | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def podproblemy_v_cislu(cislo, problemy=None, hlavni_problemy=None): | ||||||
|  | 	""" Vrátí seznam všech problémů s body v daném čísle v poli 'indexovaném' tématy. """ | ||||||
|  | 	if problemy is None: | ||||||
|  | 		problemy = problemy_cisla(cislo) | ||||||
|  | 	if hlavni_problemy is None: | ||||||
|  | 		hlavni_problemy = hlavni_problemy_f(problemy) | ||||||
|  | 
 | ||||||
|  | 	podproblemy = dict((hp.id, []) for hp in hlavni_problemy) | ||||||
|  | 	hlavni_problemy = set(hlavni_problemy) | ||||||
|  | 	podproblemy[-1] = [] | ||||||
|  | 
 | ||||||
|  | 	for problem in problemy: | ||||||
|  | 		h_problem = hlavni_problem(problem) | ||||||
|  | 		if h_problem in hlavni_problemy: | ||||||
|  | 			podproblemy[h_problem.id].append(problem) | ||||||
|  | 		else: | ||||||
|  | 			podproblemy[-1].append(problem) | ||||||
|  | 
 | ||||||
|  | 	return podproblemy | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| from dal import autocomplete | from dal import autocomplete | ||||||
| from django.shortcuts import get_object_or_404 | from django.shortcuts import get_object_or_404 | ||||||
|  | from django.db.models import Q | ||||||
| 
 | 
 | ||||||
| import seminar.models as m | import seminar.models as m | ||||||
| from .helpers import LoginRequiredAjaxMixin | from .helpers import LoginRequiredAjaxMixin | ||||||
|  |  | ||||||
|  | @ -1,12 +1,22 @@ | ||||||
| from django.views.generic import ListView, DetailView | from django.views.generic import ListView, DetailView, FormView | ||||||
| from django.views.generic.base import TemplateView | from django.views.generic.list import MultipleObjectTemplateResponseMixin,MultipleObjectMixin | ||||||
|  | from django.views.generic.base import View | ||||||
|  | from django.views.generic.detail import SingleObjectMixin | ||||||
|  | from django.shortcuts import redirect, get_object_or_404 | ||||||
|  | from django.urls import reverse | ||||||
|  | from django.db import transaction | ||||||
| 
 | 
 | ||||||
| from dataclasses import dataclass | from dataclasses import dataclass | ||||||
| import datetime | import datetime | ||||||
|  | import logging | ||||||
| 
 | 
 | ||||||
| import seminar.models as m | import seminar.models as m | ||||||
|  | import seminar.forms as f | ||||||
|  | from seminar.forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm | ||||||
| from seminar.utils import aktivniResitele, resi_v_rocniku | from seminar.utils import aktivniResitele, resi_v_rocniku | ||||||
| 
 | 
 | ||||||
|  | logger = logging.getLogger(__name__) | ||||||
|  | 
 | ||||||
| # Co chceme? | # Co chceme? | ||||||
| # - "Tabulku" aktuální řešitelé x zveřejněné problémy, v buňkách počet řešení | # - "Tabulku" aktuální řešitelé x zveřejněné problémy, v buňkách počet řešení | ||||||
| # 	- TabulkaOdevzdanychReseniView | # 	- TabulkaOdevzdanychReseniView | ||||||
|  | @ -30,26 +40,60 @@ class TabulkaOdevzdanychReseniView(ListView): | ||||||
| 	template_name = 'seminar/odevzdavatko/tabulka.html' | 	template_name = 'seminar/odevzdavatko/tabulka.html' | ||||||
| 	model = m.Hodnoceni | 	model = m.Hodnoceni | ||||||
| 
 | 
 | ||||||
| 	def get_queryset(self): | 	def inicializuj_osy_tabulky(self): | ||||||
| 		# FIXME: Tenhle blok nemůže být přímo ve třídě, protože před vyrobením databáze neexistuje Nastavení. | 		"""Vyrobí prvotní querysety pro sloupce a řádky, tj. seznam všech řešitelů a problémů""" | ||||||
| 		self.akt_rocnik = m.Nastaveni.get_solo().aktualni_rocnik	# .get_solo() vrátí tu jedinou instanci, asi... | 		# FIXME: jméno metody není vypovídající... | ||||||
| 		self.resitele = resi_v_rocniku(self.akt_rocnik) | 		# NOTE: Tenhle blok nemůže být přímo ve třídě, protože před vyrobením databáze neexistují ty objekty (?). TODO: Otestovat | ||||||
| 		# NOTE: Protože řešení odkazuje přímo na Problém a QuerySet na Hodnocení je nepolymorfní, musíme porovnávat taky s nepolymorfními Problémy. | 		# TODO: Prefetches, Select related, ... | ||||||
| 		self.zadane_problemy = m.Problem.objects.filter(stav=m.Problem.STAV_ZADANY).non_polymorphic() | 		self.resitele = m.Resitel.objects.all() | ||||||
|  | 		self.problemy = m.Problem.objects.all() | ||||||
|  | 		self.reseni = m.Reseni.objects.all() | ||||||
| 
 | 
 | ||||||
|  | 		form = FiltrForm(self.request.GET) | ||||||
|  | 		if form.is_valid(): | ||||||
|  | 			fcd = form.cleaned_data | ||||||
|  | 			resitele = fcd["resitele"] | ||||||
|  | 			problemy = fcd["problemy"] | ||||||
|  | 			reseni_od = fcd["reseni_od"] | ||||||
|  | 			reseni_do = fcd["reseni_do"] | ||||||
|  | 		else: | ||||||
|  | 			resitele = FiltrForm.get_initial_for_field(FormFiltr.resitele, "resitele") | ||||||
|  | 			problemy = FiltrForm.get_initial_for_field(FormFiltr.problemy, "problemy") | ||||||
|  | 			resitele_od = FiltrForm.get_initial_for_field(FormFiltr.resitele_od, "resitele_od") | ||||||
|  | 			resitele_do = FiltrForm.get_initial_for_field(FormFiltr.resitele_do, "resitele_do") | ||||||
|  | 			 | ||||||
|  | 
 | ||||||
|  | 		# Filtrujeme! | ||||||
|  | 		aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik	# .get_solo() vrátí tu jedinou instanci | ||||||
|  | 		if resitele == FiltrForm.RESITELE_RELEVANTNI: | ||||||
|  | 			logger.warning("Někdo chtěl v tabulce jen relevantní řešitele a měl smůlu :-(") | ||||||
|  | 			resitele = FiltrForm.RESITELE_LETOSNI # Fall-through | ||||||
|  | 		elif resitele == FiltrForm.RESITELE_LETOSNI: | ||||||
|  | 			self.resitele = resi_v_rocniku(aktualni_rocnik) | ||||||
|  | 
 | ||||||
|  | 		if problemy == FiltrForm.PROBLEMY_MOJE: | ||||||
|  | 			org = m.Organizator.objects.get(osoba__user=self.request.user) | ||||||
|  | 			from django.db.models import Q | ||||||
|  | 			self.problemy = self.problemy.filter(Q(autor=org)|Q(garant=org)|Q(opravovatele=org), stav=m.Problem.STAV_ZADANY) | ||||||
|  | 		elif problemy == FiltrForm.PROBLEMY_LETOSNI: | ||||||
|  | 			self.problemy = self.problemy.filter(stav=m.Problem.STAV_ZADANY) | ||||||
|  | 			#self.problemy = list(filter(lambda problem: problem.rocnik() == aktualni_rocnik, self.problemy)) # DB HOG? # FIXME: některé problémy nemají ročník.... | ||||||
|  | 		# NOTE: Protože řešení odkazuje přímo na Problém a QuerySet na Hodnocení je nepolymorfní, musíme porovnávat taky s nepolymorfními Problémy. | ||||||
|  | 		self.problemy = self.problemy.non_polymorphic() | ||||||
|  | 
 | ||||||
|  | 		self.reseni = self.reseni.filter(cas_doruceni__date__gte=reseni_od, cas_doruceni__date__lte=reseni_do) | ||||||
|  | 
 | ||||||
|  | 	def get_queryset(self): | ||||||
|  | 		self.inicializuj_osy_tabulky() | ||||||
| 		qs = super().get_queryset() | 		qs = super().get_queryset() | ||||||
| 		qs = qs.filter(problem__in=self.zadane_problemy).select_related('reseni', 'problem').prefetch_related('reseni__resitele__osoba') | 		qs = qs.filter(problem__in=self.problemy, reseni__in=self.reseni, reseni__resitele__in=self.resitele).select_related('reseni', 'problem').prefetch_related('reseni__resitele__osoba') | ||||||
| 		return qs | 		return qs | ||||||
| 
 | 
 | ||||||
| 	def get_context_data(self, *args, **kwargs): | 	def get_context_data(self, *args, **kwargs): | ||||||
| 		# FIXME: Tenhle blok nemůže být přímo ve třídě, protože před vyrobením databáze neexistuje Nastavení. | 		# self.resitele, self.reseni a self.problemy jsou již nastavené | ||||||
| 		self.akt_rocnik = m.Nastaveni.get_solo().aktualni_rocnik	# .get_solo() vrátí tu jedinou instanci, asi... |  | ||||||
| 		self.resitele = resi_v_rocniku(self.akt_rocnik) |  | ||||||
| 		# NOTE: Protože řešení odkazuje přímo na Problém a QuerySet na Hodnocení je nepolymorfní, musíme porovnávat taky s nepolymorfními Problémy. |  | ||||||
| 		self.zadane_problemy = m.Problem.objects.filter(stav=m.Problem.STAV_ZADANY).non_polymorphic() |  | ||||||
| 
 | 
 | ||||||
| 		ctx = super().get_context_data(*args, **kwargs) | 		ctx = super().get_context_data(*args, **kwargs) | ||||||
| 		ctx['problemy'] = self.zadane_problemy | 		ctx['problemy'] = self.problemy | ||||||
| 		ctx['resitele'] = self.resitele | 		ctx['resitele'] = self.resitele | ||||||
| 		tabulka = dict() | 		tabulka = dict() | ||||||
| 
 | 
 | ||||||
|  | @ -76,7 +120,7 @@ class TabulkaOdevzdanychReseniView(ListView): | ||||||
| 		hodnoty = [] | 		hodnoty = [] | ||||||
| 		for resitel in self.resitele: | 		for resitel in self.resitele: | ||||||
| 			resiteluv_radek = [] | 			resiteluv_radek = [] | ||||||
| 			for problem in self.zadane_problemy: | 			for problem in self.problemy: | ||||||
| 				if problem in tabulka and resitel in tabulka[problem]: | 				if problem in tabulka and resitel in tabulka[problem]: | ||||||
| 					resiteluv_radek.append(tabulka[problem][resitel]) | 					resiteluv_radek.append(tabulka[problem][resitel]) | ||||||
| 				else: | 				else: | ||||||
|  | @ -84,9 +128,14 @@ class TabulkaOdevzdanychReseniView(ListView): | ||||||
| 			hodnoty.append(resiteluv_radek) | 			hodnoty.append(resiteluv_radek) | ||||||
| 		ctx['radky'] = list(zip(self.resitele, hodnoty)) | 		ctx['radky'] = list(zip(self.resitele, hodnoty)) | ||||||
| 
 | 
 | ||||||
|  | 		ctx['filtr'] = FiltrForm(initial=self.request.GET) | ||||||
|  | 		# Pro použití hacku na automatické {{form.media}} v template: | ||||||
|  | 		ctx['form'] = ctx['filtr'] | ||||||
|  | 
 | ||||||
| 		return ctx | 		return ctx | ||||||
| 
 | 
 | ||||||
| class ReseniProblemuView(ListView): | # Velmi silně inspirováno zdrojáky, FIXME: Nedá se to udělat smysluplněji? | ||||||
|  | class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixin, View): | ||||||
| 	model = m.Reseni | 	model = m.Reseni | ||||||
| 	template_name = 'seminar/odevzdavatko/seznam.html' | 	template_name = 'seminar/odevzdavatko/seznam.html' | ||||||
| 	 | 	 | ||||||
|  | @ -107,12 +156,73 @@ class ReseniProblemuView(ListView): | ||||||
| 			) | 			) | ||||||
| 		return qs | 		return qs | ||||||
| 	 | 	 | ||||||
|  | 	def get(self, request, *args, **kwargs): | ||||||
|  | 		self.object_list = self.get_queryset() | ||||||
|  | 		if self.object_list.count() == 1: | ||||||
|  | 			jedine_reseni = self.object_list.first() | ||||||
|  | 			return redirect(reverse("odevzdavatko_detail_reseni", kwargs={"pk": jedine_reseni.id})) | ||||||
|  | 		context = self.get_context_data() | ||||||
|  | 		return self.render_to_response(context) | ||||||
| 	# Kontext automaticky? | 	# Kontext automaticky? | ||||||
| 
 | 
 | ||||||
|  | ## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex | ||||||
| class DetailReseniView(DetailView): | class DetailReseniView(DetailView): | ||||||
| 	model = m.Reseni | 	model = m.Reseni | ||||||
| 	template_name = 'seminar/odevzdavatko/detail.html' | 	template_name = 'seminar/odevzdavatko/detail.html' | ||||||
| 	# To je všechno? Najde se to podle pk... | 	 | ||||||
|  | 	def aktualni_hodnoceni(self): | ||||||
|  | 		reseni = get_object_or_404(m.Reseni, id=self.kwargs['pk']) | ||||||
|  | 		result = [] # Slovníky s klíči problem, body, cislo_body -- initial data pro f.OhodnoceniReseniFormSet | ||||||
|  | 		for hodn in m.Hodnoceni.objects.filter(reseni=reseni): | ||||||
|  | 			result.append( | ||||||
|  | 				{"problem": hodn.problem,  | ||||||
|  | 				"body": hodn.body, | ||||||
|  | 				"cislo_body": hodn.cislo_body, | ||||||
|  | 				}) | ||||||
|  | 		return result | ||||||
|  | 
 | ||||||
|  | 	def get_context_data(self, **kw): | ||||||
|  | 		ctx = super().get_context_data(**kw) | ||||||
|  | 		ctx['form'] = f.OhodnoceniReseniFormSet( | ||||||
|  | 				initial = self.aktualni_hodnoceni() | ||||||
|  | 				) | ||||||
|  | 		return ctx | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def hodnoceniReseniView(request, pk, *args, **kwargs): | ||||||
|  | 	reseni = get_object_or_404(m.Reseni, pk=pk) | ||||||
|  | 	template_name = 'seminar/odevzdavatko/detail.html' | ||||||
|  | 	form_class = f.OhodnoceniReseniFormSet | ||||||
|  | 	success_url = reverse('odevzdavatko_detail_reseni', kwargs={'pk': pk}) | ||||||
|  | 
 | ||||||
|  | 	# FIXME: Použit initial i tady a nebastlit hodnocení tak nízkoúrovňově | ||||||
|  | 	# Also: https://docs.djangoproject.com/en/2.2/topics/forms/modelforms/#django.forms.ModelForm | ||||||
|  | 	formset = f.OhodnoceniReseniFormSet(request.POST) | ||||||
|  | 	# TODO: Napsat validaci formuláře a formsetu | ||||||
|  | 	# TODO: Implementovat větev, kdy formulář validní není. | ||||||
|  | 	if formset.is_valid(): | ||||||
|  | 		with transaction.atomic(): | ||||||
|  | 			# Smažeme všechna dosavadní hodnocení tohoto řešení | ||||||
|  | 			qs = m.Hodnoceni.objects.filter(reseni=reseni) | ||||||
|  | 			logger.info(f"Will delete {qs.count()} objects: {qs}") | ||||||
|  | 			qs.delete() | ||||||
|  | 			 | ||||||
|  | 			# Vyrobíme nová podle formsetu | ||||||
|  | 			for form in formset: | ||||||
|  | 				problem = form.cleaned_data['problem'] | ||||||
|  | 				body = form.cleaned_data['body'] | ||||||
|  | 				cislo_body = form.cleaned_data['cislo_body'] | ||||||
|  | 				hodnoceni = m.Hodnoceni( | ||||||
|  | 					problem=problem, | ||||||
|  | 					body=body, | ||||||
|  | 					cislo_body=cislo_body, | ||||||
|  | 					reseni=reseni, | ||||||
|  | 					) | ||||||
|  | 				logger.info(f"Creating Hodnoceni: {hodnoceni}") | ||||||
|  | 				hodnoceni.save() | ||||||
|  | 
 | ||||||
|  | 	return redirect(success_url) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| # Přehled všech řešení kvůli debugování | # Přehled všech řešení kvůli debugování | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -29,6 +29,7 @@ from seminar.forms import PrihlaskaForm, LoginForm, ProfileEditForm | ||||||
| import seminar.forms as f | import seminar.forms as f | ||||||
| import seminar.templatetags.treenodes as tnltt | import seminar.templatetags.treenodes as tnltt | ||||||
| import seminar.views.views_rest as vr | import seminar.views.views_rest as vr | ||||||
|  | from seminar.views.vysledkovka import vysledkovka_rocniku, vysledkovka_cisla, body_resitelu | ||||||
| 
 | 
 | ||||||
| from datetime import timedelta, date, datetime, MAXYEAR | from datetime import timedelta, date, datetime, MAXYEAR | ||||||
| from django.utils import timezone | from django.utils import timezone | ||||||
|  | @ -48,7 +49,7 @@ import csv | ||||||
| import logging | import logging | ||||||
| import time | import time | ||||||
| 
 | 
 | ||||||
| from seminar.utils import aktivniResitele, resi_v_rocniku | from seminar.utils import aktivniResitele, resi_v_rocniku, problemy_rocniku, cisla_rocniku, hlavni_problemy_f | ||||||
| 
 | 
 | ||||||
| # ze starého modelu | # ze starého modelu | ||||||
| #def verejna_temata(rocnik): | #def verejna_temata(rocnik): | ||||||
|  | @ -63,20 +64,6 @@ from seminar.utils import aktivniResitele, resi_v_rocniku | ||||||
| def get_problemy_k_tematu(tema): | def get_problemy_k_tematu(tema): | ||||||
| 	return Problem.objects.filter(nadproblem = tema) | 	return Problem.objects.filter(nadproblem = tema) | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| class VlozBodyView(generic.ListView): |  | ||||||
| 	template_name = 'seminar/org/vloz_body.html' |  | ||||||
| 
 |  | ||||||
| 	def get_queryset(self): |  | ||||||
| 		self.tema = get_object_or_404(Problem,id=self.kwargs['tema']) |  | ||||||
| 		print(self.tema) |  | ||||||
| 		self.problemy = Problem.objects.filter(nadproblem = self.tema) |  | ||||||
| 		print(self.problemy) |  | ||||||
| 		self.reseni = Reseni.objects.filter(problem__in=self.problemy)	 |  | ||||||
| 		print(self.reseni) |  | ||||||
| 		return self.reseni |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class ObalkovaniView(generic.ListView): | class ObalkovaniView(generic.ListView): | ||||||
| 	template_name = 'seminar/org/obalkovani.html' | 	template_name = 'seminar/org/obalkovani.html' | ||||||
| 
 | 
 | ||||||
|  | @ -198,7 +185,7 @@ class TNLData(object): | ||||||
| 			return [cls.from_treenode(treenode)] | 			return [cls.from_treenode(treenode)] | ||||||
| 		else: | 		else: | ||||||
| 			found = [] | 			found = [] | ||||||
| 			for tn in all_children(treenode): | 			for tn in treelib.all_children(treenode): | ||||||
| 				result = cls.filter_treenode(tn, predicate) | 				result = cls.filter_treenode(tn, predicate) | ||||||
| 				# Result by v tuhle chvíli měl být seznam TNLDat odpovídající treenodům, jež matchnuly predikát. | 				# Result by v tuhle chvíli měl být seznam TNLDat odpovídající treenodům, jež matchnuly predikát. | ||||||
| 				for tnl in result: | 				for tnl in result: | ||||||
|  | @ -397,8 +384,8 @@ class ProblemView(generic.DetailView): | ||||||
| 		return context | 		return context | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class AktualniZadaniView(generic.TemplateView): | #class AktualniZadaniView(generic.TemplateView): | ||||||
| 	template_name = 'seminar/treenode.html' | #	template_name = 'seminar/treenode.html' | ||||||
| 
 | 
 | ||||||
| # TODO Co chceme vlastně zobrazovat na této stránce? Zatím je zde aktuální číslo, ale může tu být cokoli jiného... | # TODO Co chceme vlastně zobrazovat na této stránce? Zatím je zde aktuální číslo, ale může tu být cokoli jiného... | ||||||
| #class AktualniZadaniView(TreeNodeView): | #class AktualniZadaniView(TreeNodeView): | ||||||
|  | @ -413,21 +400,15 @@ class AktualniZadaniView(generic.TemplateView): | ||||||
| #		context['verejne'] = verejne	 | #		context['verejne'] = verejne	 | ||||||
| #		return context | #		return context | ||||||
| 
 | 
 | ||||||
| #def AktualniZadaniView(request): | def AktualniZadaniView(request): | ||||||
| #	nastaveni = get_object_or_404(Nastaveni) | 	nastaveni = get_object_or_404(Nastaveni) | ||||||
| #	verejne = nastaveni.aktualni_cislo.verejne() | 	verejne = nastaveni.aktualni_cislo.verejne() | ||||||
| #	problemy = Problem.objects.filter(cislo_zadani=nastaveni.aktualni_cislo).filter(stav = 'zadany') | 	return render(request, 'seminar/zadani/AktualniZadani.html', | ||||||
| #	ulohy = problemy.filter(typ = 'uloha').order_by('kod') | 			{'nastaveni': nastaveni, | ||||||
| #	serialy = problemy.filter(typ = 'serial').order_by('kod') | 			 'verejne': verejne, | ||||||
| #	jednorazove_problemy = [ulohy, serialy] | 				}, | ||||||
| #	return render(request, 'seminar/zadani/AktualniZadani.html', | 			) | ||||||
| #			{'nastaveni': nastaveni, | 
 | ||||||
| #			 'jednorazove_problemy': jednorazove_problemy, |  | ||||||
| #			 'temata': verejna_temata(nastaveni.aktualni_rocnik), |  | ||||||
| #			 'verejne': verejne, |  | ||||||
| #				}, |  | ||||||
| #			) |  | ||||||
| # |  | ||||||
| def ZadaniTemataView(request): | def ZadaniTemataView(request): | ||||||
| 	nastaveni = get_object_or_404(Nastaveni) | 	nastaveni = get_object_or_404(Nastaveni) | ||||||
| 	verejne = nastaveni.aktualni_cislo.verejne() | 	verejne = nastaveni.aktualni_cislo.verejne() | ||||||
|  | @ -506,29 +487,32 @@ def ZadaniTemataView(request): | ||||||
| #	return render(request, 'seminar/tematka/rozcestnik.html', {"tematka": tematka, "rocnik" : nastaveni.aktualni_rocnik().rocnik}) | #	return render(request, 'seminar/tematka/rozcestnik.html', {"tematka": tematka, "rocnik" : nastaveni.aktualni_rocnik().rocnik}) | ||||||
| #	 | #	 | ||||||
| 
 | 
 | ||||||
| #def ZadaniAktualniVysledkovkaView(request): | def ZadaniAktualniVysledkovkaView(request): | ||||||
| #	nastaveni = get_object_or_404(Nastaveni) | 	nastaveni = get_object_or_404(Nastaveni) | ||||||
| #	# Aktualni verejna vysledkovka | 	# Aktualni verejna vysledkovka | ||||||
| #	vysledkovka = vysledkovka_rocniku(nastaveni.aktualni_rocnik) | 	vysledkovka = vysledkovka_rocniku(nastaveni.aktualni_rocnik) | ||||||
| #	# kdyz neni verejna vysledkovka, tak zobraz starou | 	cisla = cisla_rocniku(nastaveni.aktualni_rocnik) | ||||||
| #	if not vysledkovka: | 	# kdyz neni verejna vysledkovka, tak zobraz starou | ||||||
| #		try: | 	if not vysledkovka: | ||||||
| #			minuly_rocnik = Rocnik.objects.get( | 		try: | ||||||
| #				prvni_rok=(nastaveni.aktualni_rocnik.prvni_rok-1)) | 			minuly_rocnik = Rocnik.objects.get( | ||||||
| #			vysledkovka = vysledkovka_rocniku(minuly_rocnik) | 				prvni_rok=(nastaveni.aktualni_rocnik.prvni_rok-1)) | ||||||
| #		except ObjectDoesNotExist: | 			vysledkovka = vysledkovka_rocniku(minuly_rocnik) | ||||||
| #			pass | 			cisla = cisla_rocniku(minuly_rocnik) | ||||||
| #	# vysledkovka s neverejnyma vysledkama | 		except ObjectDoesNotExist: | ||||||
| #	vysledkovka_s_neverejnymi = vysledkovka_rocniku(nastaveni.aktualni_rocnik, jen_verejne=False) | 			pass | ||||||
| #	return render( | 	# vysledkovka s neverejnyma vysledkama | ||||||
| #		request, | 	vysledkovka_s_neverejnymi = vysledkovka_rocniku(nastaveni.aktualni_rocnik, jen_verejne=False) | ||||||
| #		'seminar/zadani/AktualniVysledkovka.html', | 	return render( | ||||||
| #		{ | 		request, | ||||||
| #			'nastaveni': nastaveni, | 		'seminar/zadani/AktualniVysledkovka.html', | ||||||
| #			'vysledkovka': vysledkovka, | 		{ | ||||||
| #			'vysledkovka_s_neverejnymi': vysledkovka_s_neverejnymi, | 			'nastaveni': nastaveni, | ||||||
| #		} | 			'radky_vysledkovky': vysledkovka, | ||||||
| #	) | 			'cisla': cisla, | ||||||
|  | 			'vysledkovka_s_neverejnymi': vysledkovka_s_neverejnymi, | ||||||
|  | 		} | ||||||
|  | 	) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| ### Titulni strana | ### Titulni strana | ||||||
|  | @ -568,6 +552,8 @@ class TitulniStranaView(generic.ListView): | ||||||
| 
 | 
 | ||||||
| 		try: | 		try: | ||||||
| 			nejblizsi_deadline = sorted(filter(lambda dl: dl[0] is not None and dl[0] >= date.today(), [deadline_soustredeni, preddeadline, deadline]))[0] | 			nejblizsi_deadline = sorted(filter(lambda dl: dl[0] is not None and dl[0] >= date.today(), [deadline_soustredeni, preddeadline, deadline]))[0] | ||||||
|  | 			if nejblizsi_deadline[0] == deadline_soustredeni[0]: | ||||||
|  | 				nejblizsi_deadline = deadline_soustredeni | ||||||
| 		except IndexError: | 		except IndexError: | ||||||
| 			nejblizsi_deadline = (None, None) # neni zadna aktualni deadline | 			nejblizsi_deadline = (None, None) # neni zadna aktualni deadline | ||||||
| 
 | 
 | ||||||
|  | @ -649,235 +635,8 @@ class ArchivView(generic.ListView): | ||||||
| 		 | 		 | ||||||
| 		return context | 		return context | ||||||
| 
 | 
 | ||||||
| ### Výsledky |  | ||||||
| 
 |  | ||||||
| def sloupec_s_poradim(setrizene_body): |  | ||||||
| 	""" |  | ||||||
| 	Ze seznamu obsahujícího sestupně setřízené body řešitelů za daný ročník  |  | ||||||
| 	vytvoří seznam s pořadími (včetně 3.-5. a pak 2 volná místa atp.), |  | ||||||
| 	podle toho, jak jdou za sebou ve výsledkovce. |  | ||||||
| 	Parametr: |  | ||||||
| 		setrizene_body (seznam integerů): sestupně setřízená čísla |  | ||||||
| 
 |  | ||||||
| 	Výstup: |  | ||||||
| 		sloupec_s_poradim (seznam stringů) |  | ||||||
| 	""" |  | ||||||
| 
 |  | ||||||
| 	# ze seznamu obsahujícího setřízené body spočítáme sloupec s pořadím	 |  | ||||||
| 	aktualni_poradi = 1 |  | ||||||
| 	sloupec_s_poradim = [] |  | ||||||
| 
 |  | ||||||
| 	# seskupíme seznam všech bodů podle hodnot |  | ||||||
| 	for index in range(0, len(setrizene_body)): |  | ||||||
| 		# pokud je pořadí větší než číslo řádku, tak jsme vypsali větší rozsah a chceme |  | ||||||
| 		# vypsat už jen prázdné místo, než dojdeme na správný řádek |  | ||||||
| 		if (index + 1) < aktualni_poradi: |  | ||||||
| 			sloupec_s_poradim.append("") |  | ||||||
| 			continue |  | ||||||
| 		velikost_skupiny = 0 |  | ||||||
| 		# zjistíme počet po sobě jdoucích stejných hodnot |  | ||||||
| 		while setrizene_body[index] == setrizene_body[index + velikost_skupiny]: |  | ||||||
| 			velikost_skupiny = velikost_skupiny + 1 |  | ||||||
| 			# na konci musíme ošetřit přetečení seznamu |  | ||||||
| 			if (index + velikost_skupiny) > len(setrizene_body) - 1: |  | ||||||
| 				break |  | ||||||
| 		# pokud je velikost skupiny 1, vypíšu pořadí |  | ||||||
| 		if velikost_skupiny == 1: |  | ||||||
| 			sloupec_s_poradim.append("{}.".format(aktualni_poradi)) |  | ||||||
| 		# pokud je skupina větší, vypíšu rozsah |  | ||||||
| 		else: |  | ||||||
| 			sloupec_s_poradim.append("{}.–{}.".format(aktualni_poradi,  |  | ||||||
| 						aktualni_poradi+velikost_skupiny-1)) |  | ||||||
| 		# zvětšíme aktuální pořadí o tolik, kolik pozic bylo přeskočeno |  | ||||||
| 		aktualni_poradi = aktualni_poradi + velikost_skupiny |  | ||||||
| 	return sloupec_s_poradim |  | ||||||
| 
 |  | ||||||
| def cisla_rocniku(rocnik, jen_verejne=True): |  | ||||||
| 	""" |  | ||||||
| 	Vrátí všechna čísla daného ročníku. |  | ||||||
| 	Parametry: |  | ||||||
| 		rocnik (Rocnik): ročník semináře |  | ||||||
| 		jen_verejne (bool): zda se mají vrátit jen veřejná, nebo všechna čísla |  | ||||||
| 	Vrátí: |  | ||||||
| 		seznam objektů typu Cislo |  | ||||||
| 	"""	 |  | ||||||
| 	if jen_verejne: |  | ||||||
| 		return rocnik.verejna_cisla() |  | ||||||
| 	else: |  | ||||||
| 		return rocnik.cisla.all() |  | ||||||
| 
 |  | ||||||
| def hlavni_problem(problem): |  | ||||||
| 	""" Pro daný problém vrátí jeho nejvyšší nadproblém.""" |  | ||||||
| 	while not(problem.nadproblem == None): |  | ||||||
| 		problem = problem.nadproblem |  | ||||||
| 	return problem |  | ||||||
| 
 |  | ||||||
| def hlavni_problemy_rocniku(rocnik, jen_verejne=True): |  | ||||||
| 	""" Pro zadaný ročník vrátí hlavní problémy ročníku, |  | ||||||
| 	tj. ty, které už nemají nadproblém.""" |  | ||||||
| 	hlavni_problemy = [] |  | ||||||
| 	for cislo in cisla_rocniku(rocnik, jen_verejne): |  | ||||||
| 		for problem in hlavni_problemy_cisla(cislo): |  | ||||||
| 			hlavni_problemy.append(problem) |  | ||||||
| 	hlavni_problemy_set = set(hlavni_problemy) |  | ||||||
| 	hlavni_problemy = list(hlavni_problemy_set) |  | ||||||
| 	hlavni_problemy.sort(key=lambda k:k.kod_v_rocniku()) # setřídit podle pořadí |  | ||||||
| 	 |  | ||||||
| 	return hlavni_problemy |  | ||||||
| 
 |  | ||||||
| def hlavni_problemy_cisla(cislo): |  | ||||||
| 	""" Vrátí seznam všech problémů s body v daném čísle, které již nemají nadproblém. """ |  | ||||||
| 	hodnoceni = cislo.hodnoceni.select_related('problem', 'reseni').all()	 |  | ||||||
| 	# hodnocení, která se vážou k danému číslu |  | ||||||
| 	 |  | ||||||
| 	reseni = [h.reseni for h in hodnoceni] |  | ||||||
| 	problemy = [h.problem for h in hodnoceni] |  | ||||||
| 	problemy_set = set(problemy)	# chceme každý problém unikátně, |  | ||||||
| 	problemy = (list(problemy_set)) # převedení na množinu a zpět to zaručí |  | ||||||
| 
 |  | ||||||
| 	# hlavní problémy čísla  |  | ||||||
| 	# (mají vlastní sloupeček ve výsledkovce, nemají nadproblém) |  | ||||||
| 	hlavni_problemy = [] |  | ||||||
| 	for p in problemy: |  | ||||||
| 		hlavni_problemy.append(hlavni_problem(p)) |  | ||||||
| 		 |  | ||||||
| 	# zunikátnění |  | ||||||
| 	hlavni_problemy_set = set(hlavni_problemy) |  | ||||||
| 	hlavni_problemy = list(hlavni_problemy_set) |  | ||||||
| 	hlavni_problemy.sort(key=lambda k: k.kod_v_rocniku()) # setřídit podle t1, t2, c3, ... |  | ||||||
| 	 |  | ||||||
| 	return hlavni_problemy |  | ||||||
| 
 |  | ||||||
| def body_resitelu(resitele, za, odjakziva=True): |  | ||||||
| 	""" Funkce počítající počty bodů pro zadané řešitele,  |  | ||||||
| 	buď odjakživa do daného ročníku/čísla anebo za daný ročník/číslo. |  | ||||||
| 	Parametry: |  | ||||||
| 		resitele (seznam obsahující položky typu Resitel): aktivní řešitelé |  | ||||||
| 		za (Rocnik/Cislo): za co se mají počítat body  |  | ||||||
| 			(generování starších výsledkovek) |  | ||||||
| 		odjakziva (bool): zda se mají počítat body odjakživa, nebo jen za číslo/ročník |  | ||||||
| 			zadané v "za" |  | ||||||
| 	Výstup: |  | ||||||
| 		slovník (Resitel.id):body |  | ||||||
| 	"""		 |  | ||||||
| 	resitele_id = [r.id for r in resitele] |  | ||||||
| 	# Zjistíme, typ objektu v parametru "za" |  | ||||||
| 	if isinstance(za, Rocnik): |  | ||||||
| 		cislo = None |  | ||||||
| 		rocnik = za |  | ||||||
| 		rok = rocnik.prvni_rok |  | ||||||
| 	elif isinstance(za, Cislo): |  | ||||||
| 		cislo = za |  | ||||||
| 		rocnik = None |  | ||||||
| 		rok = cislo.rocnik.prvni_rok |  | ||||||
| 	else: |  | ||||||
| 		assert True, "body_resitelu: za není ani číslo ani ročník."	 |  | ||||||
| 	 | 	 | ||||||
| 
 | 
 | ||||||
| 	# Kvůli rychlosti používáme sčítáme body už v databázi, viz  |  | ||||||
| 	# https://docs.djangoproject.com/en/3.0/topics/db/aggregation/,  |  | ||||||
| 	# sekce Filtering on annotations (protože potřebujeme filtrovat výsledky |  | ||||||
| 	# jen do nějakého data, abychom uměli správně nagenerovat výsledkovky i  |  | ||||||
| 	# za historická čísla. |  | ||||||
| 
 |  | ||||||
| 	# Níže se vytváří dotaz na součet bodů za správně vyfiltrovaná hodnocení,  |  | ||||||
| 	# který se použije ve výsledném dotazu. |  | ||||||
| 	if cislo and odjakziva: # Body se sčítají odjakživa do zadaného čísla. |  | ||||||
| 		# Vyfiltrujeme všechna hodnocení, která jsou buď ze starších ročníků,  |  | ||||||
| 		# anebo ze stejného ročníku, jak je zadané číslo, tam ale sčítáme jen |  | ||||||
| 		# pro čísla s pořadím nejvýše stejným, jako má zadané číslo. |  | ||||||
| 		body_k_zapocteni = Sum('reseni__hodnoceni__body',  |  | ||||||
| 			filter=( Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok__lt=rok) | |  | ||||||
| 				 Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok=rok, |  | ||||||
| 				   reseni__hodnoceni__cislo_body__poradi__lte=cislo.poradi) )) |  | ||||||
| 	elif cislo and not odjakziva: # Body se sčítají za dané číslo. |  | ||||||
| 		body_k_zapocteni = Sum('reseni__hodnoceni__body', |  | ||||||
| 			filter=( Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok=rok, |  | ||||||
| 					reseni__hodnoceni__cislo_body__poradi__lte=cislo.poradi) )) |  | ||||||
| 	elif rocnik and odjakziva: # Spočítáme body za starší ročníky až do zadaného včetně.	 |  | ||||||
| 		body_k_zapocteni = Sum('reseni__hodnoceni__body',  |  | ||||||
| 			filter= Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok__lte=rok)) |  | ||||||
| 	elif rocnik and not odjakziva: # Spočítáme body za daný ročník. |  | ||||||
| 		body_k_zapocteni = Sum('reseni__hodnoceni__body', |  | ||||||
| 			filter= Q(reseni__hodnoceni__cislo_body__rocnik=rocnik)) |  | ||||||
| 	else:  |  | ||||||
| 		assert True, "body_resitelu: Neplatná kombinace za a odjakživa." |  | ||||||
| 
 |  | ||||||
| 	# Následující řádek přidá ke každému řešiteli údaj ".body" se součtem jejich bodů |  | ||||||
| 	resitele_s_body = Resitel.objects.filter(id__in=resitele_id).annotate( |  | ||||||
| 		body=body_k_zapocteni) |  | ||||||
| 	# Teď jen z QuerySetu řešitelů anotovaných body vygenerujeme slovník  |  | ||||||
| 	# indexovaný řešitelským id obsahující body.  |  | ||||||
| 	# Pokud jsou body None, nahradíme za 0. |  | ||||||
| 	slovnik = {int(res.id) : (res.body if res.body else 0) for res in resitele_s_body} |  | ||||||
| 	return slovnik |  | ||||||
| 
 |  | ||||||
| class RadekVysledkovkyRocniku(object): |  | ||||||
| 	""" Obsahuje věci, které se hodí vědět při konstruování výsledkovky. |  | ||||||
| 	Umožňuje snazší práci v templatu (lepší, než seznam).""" |  | ||||||
| 
 |  | ||||||
| 	def __init__(self, poradi, resitel, body_cisla_sezn, body_rocnik, body_odjakziva, rok): |  | ||||||
| 		self.poradi = poradi |  | ||||||
| 		self.resitel = resitel |  | ||||||
| 		self.rocnik_resitele = resitel.rocnik(rok) |  | ||||||
| 		self.body_rocnik = body_rocnik |  | ||||||
| 		self.body_celkem_odjakziva = body_odjakziva |  | ||||||
| 		self.body_cisla_sezn = body_cisla_sezn |  | ||||||
| 		self.titul = resitel.get_titul(body_odjakziva) |  | ||||||
| 
 |  | ||||||
| def setrid_resitele_a_body(slov_resitel_body): |  | ||||||
| 	setrizeni_resitele_id = [dvojice[0] for dvojice in slov_resitel_body] |  | ||||||
| 	setrizeni_resitele = [Resitel.objects.get(id=i) for i in setrizeni_resitele_id] |  | ||||||
| 	setrizene_body = [dvojice[1] for dvojice in slov_resitel_body] |  | ||||||
| 	return setrizeni_resitele_id, setrizeni_resitele, setrizene_body |  | ||||||
| 
 |  | ||||||
| def vysledkovka_rocniku(rocnik, jen_verejne=True): |  | ||||||
| 	""" Přebírá ročník (např. context["rocnik"]) a vrací výsledkovou listinu ve |  | ||||||
| 	formě vhodné pro šablonu "seminar/vysledkovka_rocniku.html" |  | ||||||
| 	""" |  | ||||||
| 	 |  | ||||||
| 	## TODO možná chytřeji vybírat aktivní řešitele |  | ||||||
| 	# aktivní řešitelé - chceme letos něco poslal, TODO později vyfiltrujeme ty, kdo mají  |  | ||||||
| 	# u alespoň jedné hodnoty něco jiného než NULL |  | ||||||
| 	aktivni_resitele = list(resi_v_rocniku(rocnik)) |  | ||||||
| 	cisla = cisla_rocniku(rocnik, jen_verejne) |  | ||||||
| 	body_cisla_slov = {} |  | ||||||
| 	for cislo in cisla: |  | ||||||
| 		# získáme body za číslo |  | ||||||
| 		_, cislobody = secti_body_za_cislo(cislo, aktivni_resitele) |  | ||||||
| 		body_cisla_slov[cislo.id] = cislobody |  | ||||||
| 
 |  | ||||||
| 	# získáme body za ročník, seznam obsahuje dvojice (řešitel_id, body) setřízené sestupně |  | ||||||
| 	resitel_rocnikbody_sezn = secti_body_za_rocnik(rocnik, aktivni_resitele) |  | ||||||
| 
 |  | ||||||
| 	# setřídíme řešitele podle počtu bodů a získáme seznam s body od nejvyšších po nenižší |  | ||||||
| 	setrizeni_resitele_id, setrizeni_resitele, setrizene_body = setrid_resitele_a_body(resitel_rocnikbody_sezn) |  | ||||||
| 	poradi = sloupec_s_poradim(setrizene_body) |  | ||||||
| 
 |  | ||||||
| 	# získáme body odjakživa |  | ||||||
| 	resitel_odjakzivabody_slov = body_resitelu(aktivni_resitele, rocnik) |  | ||||||
| 
 |  | ||||||
| 	# vytvoříme jednotlivé sloupce výsledkovky |  | ||||||
| 	radky_vysledkovky = [] |  | ||||||
| 	i = 0 |  | ||||||
| 	for ar_id in setrizeni_resitele_id: |  | ||||||
| 		# seznam počtu bodů daného řešitele pro jednotlivá čísla |  | ||||||
| 		body_cisla_sezn = [] |  | ||||||
| 		for cislo in cisla: |  | ||||||
| 			body_cisla_sezn.append(body_cisla_slov[cislo.id][ar_id]) |  | ||||||
| 
 |  | ||||||
| 		# vytáhneme informace pro daného řešitele |  | ||||||
| 		radek = RadekVysledkovkyRocniku( |  | ||||||
| 			poradi[i], # pořadí |  | ||||||
| 			Resitel.objects.get(id=ar_id), # řešitel (z id) |  | ||||||
| 			body_cisla_sezn, # seznam bodů za čísla |  | ||||||
| 			setrizene_body[i], # body za ročník (spočítané výše s pořadím) |  | ||||||
| 			resitel_odjakzivabody_slov[ar_id], # body odjakživa |  | ||||||
| 			rocnik) # ročník semináře pro získání ročníku řešitele |  | ||||||
| 		radky_vysledkovky.append(radek) |  | ||||||
| 		i += 1 |  | ||||||
| 
 |  | ||||||
| 	return radky_vysledkovky |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class RocnikView(generic.DetailView): | class RocnikView(generic.DetailView): | ||||||
|  | @ -888,29 +647,25 @@ class RocnikView(generic.DetailView): | ||||||
| 	def get_object(self, queryset=None): | 	def get_object(self, queryset=None): | ||||||
| 		if queryset is None: | 		if queryset is None: | ||||||
| 			queryset = self.get_queryset() | 			queryset = self.get_queryset() | ||||||
| 		rocnik_arg = self.kwargs.get('rocnik') |  | ||||||
| 		queryset = queryset.filter(rocnik=rocnik_arg) |  | ||||||
| 
 | 
 | ||||||
| 		try: | 		return get_object_or_404(queryset,rocnik=self.kwargs.get('rocnik')) | ||||||
| 			obj = queryset.get() |  | ||||||
| 		except queryset.model.DoesNotExist: |  | ||||||
| 			raise Http404(_("No %(verbose_name)s found matching the query") % |  | ||||||
| 						{'verbose_name': queryset.model._meta.verbose_name}) |  | ||||||
| 		return obj |  | ||||||
| 
 | 
 | ||||||
| 	def get_context_data(self, **kwargs): | 	def get_context_data(self, **kwargs): | ||||||
|  | 		start = time.time() | ||||||
| 		context = super(RocnikView, self).get_context_data(**kwargs) | 		context = super(RocnikView, self).get_context_data(**kwargs) | ||||||
| 
 | 
 | ||||||
| 		# vysledkovka = True zajistí vykreslení, | 		# vysledkovka = True zajistí vykreslení, | ||||||
| 		# zkontrolovat, kdy se má a nemá vykreslovat | 		# zkontrolovat, kdy se má a nemá vykreslovat | ||||||
| 		context['vysledkovka'] = True | 		context['vysledkovka'] = True | ||||||
|  | 		if self.request.user.je_org: | ||||||
| 			context['cisla_s_neverejnymi'] = cisla_rocniku(context["rocnik"], jen_verejne=False) | 			context['cisla_s_neverejnymi'] = cisla_rocniku(context["rocnik"], jen_verejne=False) | ||||||
|  | 			context['radky_vysledkovky_s_neverejnymi'] = vysledkovka_rocniku(context["rocnik"], jen_verejne=False) | ||||||
|  | 			context['hlavni_problemy_v_rocniku_s_neverejnymi'] = hlavni_problemy_f(problemy_rocniku(context["rocnik"], jen_verejne=False)) | ||||||
| 		context['cisla'] = cisla_rocniku(context["rocnik"]) | 		context['cisla'] = cisla_rocniku(context["rocnik"]) | ||||||
| 		context['radky_vysledkovky'] = vysledkovka_rocniku(context["rocnik"]) | 		context['radky_vysledkovky'] = vysledkovka_rocniku(context["rocnik"]) | ||||||
| 		context['radky_vysledkovky_s_neverejnymi'] = vysledkovka_rocniku( | 		context['hlavni_problemy_v_rocniku'] = hlavni_problemy_f(problemy_rocniku(context["rocnik"])) | ||||||
| 			context["rocnik"], jen_verejne=False) | 		end = time.time() | ||||||
| 		context['hlavni_problemy_v_rocniku'] = hlavni_problemy_rocniku(context["rocnik"]) | 		print("Kontext:", end-start) | ||||||
| 		context['hlavni_problemy_v_rocniku_s_neverejnymi'] = hlavni_problemy_rocniku(context["rocnik"], jen_verejne=False) |  | ||||||
| 
 | 
 | ||||||
| 		return context | 		return context | ||||||
| 
 | 
 | ||||||
|  | @ -940,179 +695,6 @@ class ProblemView(generic.DetailView): | ||||||
| 		return context | 		return context | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class RadekVysledkovkyCisla(object): |  | ||||||
| 	"""Obsahuje věci, které se hodí vědět při konstruování výsledkovky. |  | ||||||
| 	Umožňuje snazší práci v templatu (lepší, než seznam).""" |  | ||||||
| 
 |  | ||||||
| 	def __init__(self, poradi, resitel, body_problemy_sezn,  |  | ||||||
| 				body_cislo, body_rocnik, body_odjakziva, rok): |  | ||||||
| 		self.resitel = resitel |  | ||||||
| 		self.rocnik_resitele = resitel.rocnik(rok) |  | ||||||
| 		self.body_cislo = body_cislo |  | ||||||
| 		self.body_rocnik = body_rocnik |  | ||||||
| 		self.body_celkem_odjakziva = body_odjakziva |  | ||||||
| 		self.poradi = poradi |  | ||||||
| 		self.body_problemy_sezn = body_problemy_sezn |  | ||||||
| 		self.titul = resitel.get_titul(body_odjakziva) |  | ||||||
| 	 |  | ||||||
| 
 |  | ||||||
| def pricti_body(slovnik, resitel, body): |  | ||||||
| 	""" Přiřazuje danému řešiteli body do slovníku. """ |  | ||||||
| 	# testujeme na None (""), pokud je to první řešení  |  | ||||||
| 	# daného řešitele, předěláme na 0 |  | ||||||
| 	# (v dalším kroku přičteme reálný počet bodů), |  | ||||||
| 	# rozlišujeme tím mezi 0 a neodevzdaným řešením |  | ||||||
| 	if slovnik[resitel.id] == "": |  | ||||||
| 		slovnik[resitel.id] = 0 |  | ||||||
| 	 |  | ||||||
| 	slovnik[resitel.id] += body |  | ||||||
| 
 |  | ||||||
| def secti_body_za_rocnik(za, aktivni_resitele): |  | ||||||
| 	""" Spočítá body za ročník (celý nebo do daného čísla),  |  | ||||||
| 		setřídí je sestupně a vrátí jako seznam. |  | ||||||
| 	Parametry: |  | ||||||
| 		za (typu Rocnik nebo Cislo)	spočítá za ročník, nebo za ročník až do  |  | ||||||
| 						daného čísla |  | ||||||
| 	""" |  | ||||||
| 	# spočítáme všem řešitelům jejich body za ročník (False => ne odjakživa) |  | ||||||
| 	resitel_rocnikbody_slov = body_resitelu(aktivni_resitele, za, False) |  | ||||||
| 	# zeptáme se na dvojice (řešitel, body) za ročník a setřídíme sestupně |  | ||||||
| 	resitel_rocnikbody_sezn = sorted(resitel_rocnikbody_slov.items(), |  | ||||||
| 					key = lambda x: x[1], reverse = True) |  | ||||||
| 	return resitel_rocnikbody_sezn |  | ||||||
| 
 |  | ||||||
| def secti_body_za_cislo(cislo, aktivni_resitele, hlavni_problemy=None): |  | ||||||
| 	""" Spočítá u řešitelů body za číslo a za jednotlivé hlavní problémy (témata).""" |  | ||||||
| 	# TODO setřídit hlavní problémy čísla podle id, ať jsou ve stejném pořadí pokaždé |  | ||||||
| 	# pro každý hlavní problém zavedeme slovník s body za daný hlavní problém  |  | ||||||
| 	# pro jednotlivé řešitele (slovník slovníků hlavních problémů) |  | ||||||
| 	if hlavni_problemy is None: |  | ||||||
| 		hlavni_problemy = hlavni_problemy_cisla(cislo) |  | ||||||
| 
 |  | ||||||
| 	def ne_clanek_ne_konfera(problem): |  | ||||||
| 		return not(isinstance(problem.get_real_instance(), m.Clanek) or isinstance(problem.get_real_instance(), m.Konfera)) |  | ||||||
| 
 |  | ||||||
| 	temata_a_spol = list(filter(ne_clanek_ne_konfera, hlavni_problemy)) |  | ||||||
| 
 |  | ||||||
| 	def cosi(problem): |  | ||||||
| 		return problem.id |  | ||||||
| 
 |  | ||||||
| 	hlavni_problemy_slovnik = {} |  | ||||||
| 	for hp in temata_a_spol: |  | ||||||
| 		hlavni_problemy_slovnik[hp.id] = {} |  | ||||||
| 
 |  | ||||||
| 	hlavni_problemy_slovnik[-1] = {} |  | ||||||
| 
 |  | ||||||
| 	# zakládání prázdných záznamů pro řešitele |  | ||||||
| 	cislobody = {} |  | ||||||
| 	for ar in aktivni_resitele: |  | ||||||
| 		# řešitele převedeme na řetězec pomocí unikátního id |  | ||||||
| 		cislobody[ar.id] = "" |  | ||||||
| 		for hp in temata_a_spol: |  | ||||||
| 			slovnik = hlavni_problemy_slovnik[hp.id] |  | ||||||
| 			slovnik[ar.id] = "" |  | ||||||
| 
 |  | ||||||
| 		hlavni_problemy_slovnik[-1][ar.id] = "" |  | ||||||
| 	 |  | ||||||
| 	# vezmeme všechna řešení s body do daného čísla |  | ||||||
| 	reseni_do_cisla = Reseni.objects.prefetch_related('problem', 'resitele',  |  | ||||||
| 				'hodnoceni_set').filter(hodnoceni__cislo_body=cislo) |  | ||||||
| 	 |  | ||||||
| 	# projdeme všechna řešení do čísla a přičteme body každému řešiteli do celkových |  | ||||||
| 	# bodů i do bodů za problém |  | ||||||
| 	for reseni in reseni_do_cisla: |  | ||||||
| 		 |  | ||||||
| 		# řešení může řešit více problémů |  | ||||||
| 		for prob in list(reseni.problem.all()): |  | ||||||
| 			nadproblem = hlavni_problem(prob) |  | ||||||
| 			if ne_clanek_ne_konfera(nadproblem): |  | ||||||
| 				nadproblem_slovnik = hlavni_problemy_slovnik[nadproblem.id] |  | ||||||
| 			else: |  | ||||||
| 				nadproblem_slovnik = hlavni_problemy_slovnik[-1] |  | ||||||
| 			 |  | ||||||
| 			# a mít více hodnocení |  | ||||||
| 			for hodn in list(reseni.hodnoceni_set.all()): |  | ||||||
| 				body = hodn.body	 |  | ||||||
| 			 |  | ||||||
| 				# a mít více řešitelů |  | ||||||
| 				for resitel in list(reseni.resitele.all()): |  | ||||||
| 					if resitel not in aktivni_resitele: |  | ||||||
| 						print("Skipping {}".format(resitel.id)) |  | ||||||
| 						continue |  | ||||||
| 					pricti_body(cislobody, resitel, body) |  | ||||||
| 					pricti_body(nadproblem_slovnik, resitel, body) |  | ||||||
| 	return hlavni_problemy_slovnik, cislobody |  | ||||||
| 
 |  | ||||||
| def vysledkovka_cisla(cislo, context=None): |  | ||||||
| 	if context is None: |  | ||||||
| 		context = {} |  | ||||||
| 	 |  | ||||||
| 	hlavni_problemy = hlavni_problemy_cisla(cislo) |  | ||||||
| 	## TODO možná chytřeji vybírat aktivní řešitele |  | ||||||
| 	# aktivní řešitelé - chceme letos něco poslal, TODO později vyfiltrujeme ty, kdo mají  |  | ||||||
| 	# u alespoň jedné hodnoty něco jiného než NULL |  | ||||||
| 	aktivni_resitele = list(aktivniResitele(cislo)) |  | ||||||
| 
 |  | ||||||
| 	# získáme body za číslo |  | ||||||
| 	hlavni_problemy_slovnik, cislobody = secti_body_za_cislo(cislo, aktivni_resitele, hlavni_problemy) |  | ||||||
| 
 |  | ||||||
| 	# získáme body za ročník, seznam obsahuje dvojice (řešitel_id, body) setřízené sestupně |  | ||||||
| 	resitel_rocnikbody_sezn = secti_body_za_rocnik(cislo, aktivni_resitele) |  | ||||||
| 
 |  | ||||||
| 	# získáme body odjakživa |  | ||||||
| 	resitel_odjakzivabody_slov = body_resitelu(aktivni_resitele, cislo) |  | ||||||
| 
 |  | ||||||
| 	# řešitelé setřídění podle bodů za číslo sestupně |  | ||||||
| 	setrizeni_resitele_id = [dvojice[0] for dvojice in resitel_rocnikbody_sezn] |  | ||||||
| 	setrizeni_resitele = [Resitel.objects.get(id=i) for i in setrizeni_resitele_id] |  | ||||||
| 			 |  | ||||||
| 	# spočítáme pořadí řešitelů |  | ||||||
| 	setrizeni_resitele_body = [dvojice[1] for dvojice in resitel_rocnikbody_sezn] |  | ||||||
| 	poradi = sloupec_s_poradim(setrizeni_resitele_body) |  | ||||||
| 
 |  | ||||||
| 	# vytvoříme jednotlivé sloupce výsledkovky |  | ||||||
| 	radky_vysledkovky = [] |  | ||||||
| 	i = 0 |  | ||||||
| 
 |  | ||||||
| 	def ne_clanek_ne_konfera(problem): |  | ||||||
| 		return not(isinstance(problem.get_real_instance(), m.Clanek) or isinstance(problem.get_real_instance(), m.Konfera)) |  | ||||||
| 
 |  | ||||||
| 	temata_a_spol = list(filter(ne_clanek_ne_konfera, hlavni_problemy)) |  | ||||||
| 
 |  | ||||||
| 	# def not_empty(value): |  | ||||||
| 	# 	return value != '' |  | ||||||
| 	# |  | ||||||
| 	# je_nejake_ostatni = any(filter(not_empty, hlavni_problemy_slovnik[-1].values())) > 0 |  | ||||||
| 
 |  | ||||||
| 	je_nejake_ostatni = len(hlavni_problemy) - len(temata_a_spol) > 0 |  | ||||||
| 
 |  | ||||||
| 	for ar_id in setrizeni_resitele_id: |  | ||||||
| 		# získáme seznam bodů za problémy pro daného řešitele |  | ||||||
| 		problemy = [] 		 |  | ||||||
| 		for hp in temata_a_spol: |  | ||||||
| 			problemy.append(hlavni_problemy_slovnik[hp.id][ar_id]) |  | ||||||
| 		if je_nejake_ostatni: |  | ||||||
| 			problemy.append(hlavni_problemy_slovnik[-1][ar_id]) |  | ||||||
| 		# vytáhneme informace pro daného řešitele |  | ||||||
| 		radek = RadekVysledkovkyCisla( |  | ||||||
| 			poradi[i], # pořadí |  | ||||||
| 			Resitel.objects.get(id=ar_id), # řešitel (z id) |  | ||||||
| 			problemy, # seznam bodů za hlavní problémy čísla |  | ||||||
| 			cislobody[ar_id], # body za číslo |  | ||||||
| 			setrizeni_resitele_body[i], # body za ročník (spočítané výše s pořadím) |  | ||||||
| 			resitel_odjakzivabody_slov[ar_id], # body odjakživa |  | ||||||
| 			cislo.rocnik) # ročník semináře pro zjištění ročníku řešitele |  | ||||||
| 		radky_vysledkovky.append(radek) |  | ||||||
| 		i += 1 |  | ||||||
| 
 |  | ||||||
| 	# vytahané informace předáváme do kontextu |  | ||||||
| 	context['cislo'] = cislo |  | ||||||
| 	context['radky_vysledkovky'] = radky_vysledkovky |  | ||||||
| 	context['problemy'] = temata_a_spol |  | ||||||
| 	context['ostatni'] = je_nejake_ostatni |  | ||||||
| 	#context['v_cisle_zadane'] = TODO |  | ||||||
| 	#context['resene_problemy'] = resene_problemy |  | ||||||
| 	return context |  | ||||||
| 
 | 
 | ||||||
| class CisloView(generic.DetailView): | class CisloView(generic.DetailView): | ||||||
| 	# FIXME zobrazování témátek a vůbec, teď je tam jen odkaz na číslo v pdf | 	# FIXME zobrazování témátek a vůbec, teď je tam jen odkaz na číslo v pdf | ||||||
|  | @ -1386,7 +968,7 @@ class ClankyResitelView(generic.ListView): | ||||||
| 
 | 
 | ||||||
| 	# FIXME: QuerySet není pole! | 	# FIXME: QuerySet není pole! | ||||||
| 	def get_queryset(self): | 	def get_queryset(self): | ||||||
| 		clanky = Clanek.objects.filter(stav=Problem.STAV_ZADANY).select_related('cislo__rocnik').order_by('-cislo__rocnik__rocnik') | 		clanky = Clanek.objects.filter(stav=Problem.STAV_VYRESENY).select_related('cislo__rocnik').order_by('-cislo__rocnik__rocnik') | ||||||
| 		queryset = [] | 		queryset = [] | ||||||
| 		skupiny_clanku = group_by_rocnik(clanky) | 		skupiny_clanku = group_by_rocnik(clanky) | ||||||
| 		for skupina in skupiny_clanku: | 		for skupina in skupiny_clanku: | ||||||
|  | @ -1476,36 +1058,6 @@ class NahrajReseniView(LoginRequiredMixin, CreateView): | ||||||
| 
 | 
 | ||||||
| 		return HttpResponseRedirect(self.get_success_url()) | 		return HttpResponseRedirect(self.get_success_url()) | ||||||
| 
 | 
 | ||||||
| def resetPasswordView(request): |  | ||||||
| 	pass |  | ||||||
| 
 |  | ||||||
| def loginView(request): |  | ||||||
| 	if request.method == 'POST': |  | ||||||
| 		form = LoginForm(request.POST) |  | ||||||
| 		if form.is_valid(): |  | ||||||
| 			user = authenticate(request,  |  | ||||||
| 				username=form.cleaned_data['username'], |  | ||||||
| 				password=form.cleaned_data['password']) |  | ||||||
| 			print(form.cleaned_data) |  | ||||||
| 			if user is not None: |  | ||||||
| 				login(request,user) |  | ||||||
| 				return HttpResponseRedirect('/') |  | ||||||
| 			else: |  | ||||||
| 				return render(request,  |  | ||||||
| 					'seminar/profil/login.html', |  | ||||||
| 					{'form': form, 'login_error': 'Neplatné jméno nebo heslo'}) |  | ||||||
| 
 |  | ||||||
| 	else: |  | ||||||
| 		form = LoginForm() |  | ||||||
| 	return render(request, 'seminar/profil/login.html', {'form': form}) |  | ||||||
| 
 |  | ||||||
| def logoutView(request): |  | ||||||
| 	form = LoginForm() |  | ||||||
| 	if request.user.is_authenticated: |  | ||||||
| 		logout(request) |  | ||||||
| 		return render(request, 'seminar/profil/login.html', {'form': form, 'login_error': 'Byli jste úspěšně odhlášeni'}) |  | ||||||
| 	return render(request, 'seminar/profil/login.html', {'form': form}) |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| def prihlaska_log_gdpr_safe(logger, gdpr_logger, msg, form_data): | def prihlaska_log_gdpr_safe(logger, gdpr_logger, msg, form_data): | ||||||
| 	msg = "{}, form_hash:{}".format(msg,hash(frozenset(form_data.items))) | 	msg = "{}, form_hash:{}".format(msg,hash(frozenset(form_data.items))) | ||||||
|  | @ -1518,13 +1070,17 @@ def resitelEditView(request): | ||||||
| 	## Načtení objektů Osoba a Resitel patřících k aktuálně přihlášenému uživateli | 	## Načtení objektů Osoba a Resitel patřících k aktuálně přihlášenému uživateli | ||||||
| 	u = request.user | 	u = request.user | ||||||
| 	osoba_edit = Osoba.objects.get(user=u) | 	osoba_edit = Osoba.objects.get(user=u) | ||||||
|  | 	if hasattr(osoba_edit,'resitel'): | ||||||
| 		resitel_edit = osoba_edit.resitel | 		resitel_edit = osoba_edit.resitel | ||||||
|  | 	else: | ||||||
|  | 		resitel_edit = None | ||||||
| 	user_edit = osoba_edit.user | 	user_edit = osoba_edit.user | ||||||
| 	## Vytvoření slovníku, kterým předvyplním formulář  | 	## Vytvoření slovníku, kterým předvyplním formulář  | ||||||
| 	prefill_1=model_to_dict(user_edit) | 	prefill_1=model_to_dict(user_edit) | ||||||
|  | 	if resitel_edit: | ||||||
| 		prefill_2=model_to_dict(resitel_edit) | 		prefill_2=model_to_dict(resitel_edit) | ||||||
| 	prefill_3=model_to_dict(osoba_edit) |  | ||||||
| 		prefill_1.update(prefill_2) | 		prefill_1.update(prefill_2) | ||||||
|  | 	prefill_3=model_to_dict(osoba_edit) | ||||||
| 	prefill_1.update(prefill_3) | 	prefill_1.update(prefill_3) | ||||||
| 	form = ProfileEditForm(initial=prefill_1) | 	form = ProfileEditForm(initial=prefill_1) | ||||||
| 	## Změna údajů a jejich uložení | 	## Změna údajů a jejich uložení | ||||||
|  | @ -1550,6 +1106,7 @@ def resitelEditView(request): | ||||||
| 				## Neznámá země | 				## Neznámá země | ||||||
| 				msg = "Unknown country {}".format(fcd['stat_text']) | 				msg = "Unknown country {}".format(fcd['stat_text']) | ||||||
| 
 | 
 | ||||||
|  | 			if resitel_edit: | ||||||
| 				## Změny v řešiteli | 				## Změny v řešiteli | ||||||
| 				resitel_edit.skola = fcd['skola'] | 				resitel_edit.skola = fcd['skola'] | ||||||
| 				resitel_edit.rok_maturity = fcd['rok_maturity'] | 				resitel_edit.rok_maturity = fcd['rok_maturity'] | ||||||
|  | @ -1649,7 +1206,7 @@ class LoginView(auth_views.LoginView): | ||||||
| 	# Přesměrovací URL má být v kontextu: | 	# Přesměrovací URL má být v kontextu: | ||||||
| 	def get_context_data(self, **kwargs): | 	def get_context_data(self, **kwargs): | ||||||
| 		ctx = super().get_context_data(**kwargs) | 		ctx = super().get_context_data(**kwargs) | ||||||
| 		ctx['next'] = reverse('titulni_strana') | 		ctx['next'] = reverse('profil') | ||||||
| 		return ctx | 		return ctx | ||||||
| 
 | 
 | ||||||
| class LogoutView(auth_views.LogoutView): | class LogoutView(auth_views.LogoutView): | ||||||
|  |  | ||||||
							
								
								
									
										456
									
								
								seminar/views/vysledkovka.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										456
									
								
								seminar/views/vysledkovka.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,456 @@ | ||||||
|  | import seminar.models as m | ||||||
|  | from django.db.models import Q, Sum, Count | ||||||
|  | from seminar.utils import aktivniResitele, resi_v_rocniku, cisla_rocniku, hlavni_problem, hlavni_problemy_f, problemy_cisla, podproblemy_v_cislu | ||||||
|  | import time | ||||||
|  | ### Výsledky | ||||||
|  | 
 | ||||||
|  | def sloupec_s_poradim(setrizene_body): | ||||||
|  | 	""" | ||||||
|  | 	Ze seznamu obsahujícího sestupně setřízené body řešitelů za daný ročník  | ||||||
|  | 	vytvoří seznam s pořadími (včetně 3.-5. a pak 2 volná místa atp.), | ||||||
|  | 	podle toho, jak jdou za sebou ve výsledkovce. | ||||||
|  | 	Parametr: | ||||||
|  | 		setrizene_body (seznam integerů): sestupně setřízená čísla | ||||||
|  | 
 | ||||||
|  | 	Výstup: | ||||||
|  | 		sloupec_s_poradim (seznam stringů) | ||||||
|  | 	""" | ||||||
|  | 
 | ||||||
|  | 	# ze seznamu obsahujícího setřízené body spočítáme sloupec s pořadím	 | ||||||
|  | 	aktualni_poradi = 1 | ||||||
|  | 	sloupec_s_poradim = [] | ||||||
|  | 
 | ||||||
|  | 	# seskupíme seznam všech bodů podle hodnot | ||||||
|  | 	for index in range(0, len(setrizene_body)): | ||||||
|  | 		# pokud je pořadí větší než číslo řádku, tak jsme vypsali větší rozsah a chceme | ||||||
|  | 		# vypsat už jen prázdné místo, než dojdeme na správný řádek | ||||||
|  | 		if (index + 1) < aktualni_poradi: | ||||||
|  | 			sloupec_s_poradim.append("") | ||||||
|  | 			continue | ||||||
|  | 		velikost_skupiny = 0 | ||||||
|  | 		# zjistíme počet po sobě jdoucích stejných hodnot | ||||||
|  | 		while setrizene_body[index] == setrizene_body[index + velikost_skupiny]: | ||||||
|  | 			velikost_skupiny = velikost_skupiny + 1 | ||||||
|  | 			# na konci musíme ošetřit přetečení seznamu | ||||||
|  | 			if (index + velikost_skupiny) > len(setrizene_body) - 1: | ||||||
|  | 				break | ||||||
|  | 		# pokud je velikost skupiny 1, vypíšu pořadí | ||||||
|  | 		if velikost_skupiny == 1: | ||||||
|  | 			sloupec_s_poradim.append("{}.".format(aktualni_poradi)) | ||||||
|  | 		# pokud je skupina větší, vypíšu rozsah | ||||||
|  | 		else: | ||||||
|  | 			sloupec_s_poradim.append("{}.–{}.".format(aktualni_poradi,  | ||||||
|  | 						aktualni_poradi+velikost_skupiny-1)) | ||||||
|  | 		# zvětšíme aktuální pořadí o tolik, kolik pozic bylo přeskočeno | ||||||
|  | 		aktualni_poradi = aktualni_poradi + velikost_skupiny | ||||||
|  | 	return sloupec_s_poradim | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def body_resitelu(resitele, za, odjakziva=True): | ||||||
|  | 	""" Funkce počítající počty bodů pro zadané řešitele,  | ||||||
|  | 	buď odjakživa do daného ročníku/čísla anebo za daný ročník/číslo. | ||||||
|  | 	Parametry: | ||||||
|  | 		resitele (seznam obsahující položky typu Resitel): aktivní řešitelé | ||||||
|  | 		za (Rocnik/Cislo): za co se mají počítat body  | ||||||
|  | 			(generování starších výsledkovek) | ||||||
|  | 		odjakziva (bool): zda se mají počítat body odjakživa, nebo jen za číslo/ročník | ||||||
|  | 			zadané v "za" | ||||||
|  | 	Výstup: | ||||||
|  | 		slovník (Resitel.id):body | ||||||
|  | 	"""		 | ||||||
|  | 	resitele_id = [r.id for r in resitele] | ||||||
|  | 	# Zjistíme, typ objektu v parametru "za" | ||||||
|  | 	if isinstance(za, m.Rocnik): | ||||||
|  | 		cislo = None | ||||||
|  | 		rocnik = za | ||||||
|  | 		rok = rocnik.prvni_rok | ||||||
|  | 	elif isinstance(za, m.Cislo): | ||||||
|  | 		cislo = za | ||||||
|  | 		rocnik = None | ||||||
|  | 		rok = cislo.rocnik.prvni_rok | ||||||
|  | 	else: | ||||||
|  | 		assert True, "body_resitelu: za není ani číslo ani ročník."	 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 	# Kvůli rychlosti používáme sčítáme body už v databázi, viz  | ||||||
|  | 	# https://docs.djangoproject.com/en/3.0/topics/db/aggregation/,  | ||||||
|  | 	# sekce Filtering on annotations (protože potřebujeme filtrovat výsledky | ||||||
|  | 	# jen do nějakého data, abychom uměli správně nagenerovat výsledkovky i  | ||||||
|  | 	# za historická čísla. | ||||||
|  | 
 | ||||||
|  | 	# Níže se vytváří dotaz na součet bodů za správně vyfiltrovaná hodnocení,  | ||||||
|  | 	# který se použije ve výsledném dotazu. | ||||||
|  | 	if cislo and odjakziva: # Body se sčítají odjakživa do zadaného čísla. | ||||||
|  | 		# Vyfiltrujeme všechna hodnocení, která jsou buď ze starších ročníků,  | ||||||
|  | 		# anebo ze stejného ročníku, jak je zadané číslo, tam ale sčítáme jen | ||||||
|  | 		# pro čísla s pořadím nejvýše stejným, jako má zadané číslo. | ||||||
|  | 		body_k_zapocteni = Sum('reseni__hodnoceni__body',  | ||||||
|  | 			filter=( Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok__lt=rok) | | ||||||
|  | 				 Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok=rok, | ||||||
|  | 				   reseni__hodnoceni__cislo_body__poradi__lte=cislo.poradi) )) | ||||||
|  | 	elif cislo and not odjakziva: # Body se sčítají za dané číslo. | ||||||
|  | 		body_k_zapocteni = Sum('reseni__hodnoceni__body', | ||||||
|  | 			filter=( Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok=rok, | ||||||
|  | 					reseni__hodnoceni__cislo_body__poradi__lte=cislo.poradi) )) | ||||||
|  | 	elif rocnik and odjakziva: # Spočítáme body za starší ročníky až do zadaného včetně.	 | ||||||
|  | 		body_k_zapocteni = Sum('reseni__hodnoceni__body',  | ||||||
|  | 			filter= Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok__lte=rok)) | ||||||
|  | 	elif rocnik and not odjakziva: # Spočítáme body za daný ročník. | ||||||
|  | 		body_k_zapocteni = Sum('reseni__hodnoceni__body', | ||||||
|  | 			filter= Q(reseni__hodnoceni__cislo_body__rocnik=rocnik)) | ||||||
|  | 	else:  | ||||||
|  | 		assert True, "body_resitelu: Neplatná kombinace za a odjakživa." | ||||||
|  | 
 | ||||||
|  | 	# Následující řádek přidá ke každému řešiteli údaj ".body" se součtem jejich bodů | ||||||
|  | 	resitele_s_body = m.Resitel.objects.filter(id__in=resitele_id).annotate( | ||||||
|  | 		body=body_k_zapocteni) | ||||||
|  | 	# Teď jen z QuerySetu řešitelů anotovaných body vygenerujeme slovník  | ||||||
|  | 	# indexovaný řešitelským id obsahující body.  | ||||||
|  | 	# Pokud jsou body None, nahradíme za 0. | ||||||
|  | 	slovnik = {int(res.id) : (res.body if res.body else 0) for res in resitele_s_body} | ||||||
|  | 	return slovnik | ||||||
|  | 
 | ||||||
|  | class RadekVysledkovkyRocniku(object): | ||||||
|  | 	""" Obsahuje věci, které se hodí vědět při konstruování výsledkovky. | ||||||
|  | 	Umožňuje snazší práci v templatu (lepší, než seznam).""" | ||||||
|  | 
 | ||||||
|  | 	def __init__(self, poradi, resitel, body_cisla_sezn, body_rocnik, body_odjakziva, rok): | ||||||
|  | 		self.poradi = poradi | ||||||
|  | 		self.resitel = resitel | ||||||
|  | 		self.rocnik_resitele = resitel.rocnik(rok) | ||||||
|  | 		self.body_rocnik = body_rocnik | ||||||
|  | 		self.body_celkem_odjakziva = body_odjakziva | ||||||
|  | 		self.body_cisla_sezn = body_cisla_sezn | ||||||
|  | 		self.titul = resitel.get_titul(body_odjakziva) | ||||||
|  | 
 | ||||||
|  | def setrid_resitele_a_body(slov_resitel_body): | ||||||
|  | 	setrizeni_resitele_id = [dvojice[0] for dvojice in slov_resitel_body] | ||||||
|  | 	setrizene_body = [dvojice[1] for dvojice in slov_resitel_body] | ||||||
|  | 	return setrizeni_resitele_id,  setrizene_body | ||||||
|  | 
 | ||||||
|  | def vysledkovka_rocniku(rocnik, jen_verejne=True): | ||||||
|  | 	""" Přebírá ročník (např. context["rocnik"]) a vrací výsledkovou listinu ve | ||||||
|  | 	formě vhodné pro šablonu "seminar/vysledkovka_rocniku.html" | ||||||
|  | 	""" | ||||||
|  | 	 | ||||||
|  | 	start = time.time() | ||||||
|  | 
 | ||||||
|  | 	## TODO možná chytřeji vybírat aktivní řešitele | ||||||
|  | 	# aktivní řešitelé - chceme letos něco poslal, TODO později vyfiltrujeme ty, kdo mají  | ||||||
|  | 	# u alespoň jedné hodnoty něco jiného než NULL | ||||||
|  | 	aktivni_resitele = list(resi_v_rocniku(rocnik)) | ||||||
|  | 	cisla = cisla_rocniku(rocnik, jen_verejne) | ||||||
|  | 	body_cisla_slov = {} | ||||||
|  | 	for cislo in cisla: | ||||||
|  | 		# získáme body za číslo | ||||||
|  | 		_, cislobody = secti_body_za_cislo(cislo, aktivni_resitele) | ||||||
|  | 		body_cisla_slov[cislo.id] = cislobody | ||||||
|  | 
 | ||||||
|  | 	# získáme body za ročník, seznam obsahuje dvojice (řešitel_id, body) setřízené sestupně | ||||||
|  | 	resitel_rocnikbody_sezn = secti_body_za_rocnik(rocnik, aktivni_resitele) | ||||||
|  | 
 | ||||||
|  | 	# setřídíme řešitele podle počtu bodů a získáme seznam s body od nejvyšších po nenižší | ||||||
|  | 	setrizeni_resitele_id, setrizene_body = setrid_resitele_a_body(resitel_rocnikbody_sezn) | ||||||
|  | 	poradi = sloupec_s_poradim(setrizene_body) | ||||||
|  | 
 | ||||||
|  | 	# získáme body odjakživa | ||||||
|  | 	resitel_odjakzivabody_slov = body_resitelu(aktivni_resitele, rocnik) | ||||||
|  | 
 | ||||||
|  | 	# vytvoříme jednotlivé sloupce výsledkovky | ||||||
|  | 	radky_vysledkovky = [] | ||||||
|  | 	i = 0 | ||||||
|  | 	setrizeni_resitele_dict = {} # Tento slovnik se vyrab | ||||||
|  | 	for r in m.Resitel.objects.filter(id__in=setrizeni_resitele_id).select_related('osoba'): | ||||||
|  | 		setrizeni_resitele_dict[r.id] = r | ||||||
|  | 	 | ||||||
|  | 	for ar_id in setrizeni_resitele_id: | ||||||
|  | 		# seznam počtu bodů daného řešitele pro jednotlivá čísla | ||||||
|  | 		body_cisla_sezn = [] | ||||||
|  | 		for cislo in cisla: | ||||||
|  | 			body_cisla_sezn.append(body_cisla_slov[cislo.id][ar_id]) | ||||||
|  | 
 | ||||||
|  | 		# vytáhneme informace pro daného řešitele | ||||||
|  | 		radek = RadekVysledkovkyRocniku( | ||||||
|  | 			poradi[i], # pořadí | ||||||
|  | 			setrizeni_resitele_dict[ar_id], # řešitel (z id) | ||||||
|  | 			body_cisla_sezn, # seznam bodů za čísla | ||||||
|  | 			setrizene_body[i], # body za ročník (spočítané výše s pořadím) | ||||||
|  | 			resitel_odjakzivabody_slov[ar_id], # body odjakživa | ||||||
|  | 			rocnik) # ročník semináře pro získání ročníku řešitele | ||||||
|  | 		radky_vysledkovky.append(radek) | ||||||
|  | 		i += 1 | ||||||
|  | 
 | ||||||
|  | 	end = time.time() | ||||||
|  | 	print("Vysledkovka rocniku",end-start) | ||||||
|  | 
 | ||||||
|  | 	return radky_vysledkovky | ||||||
|  | 
 | ||||||
|  | class RadekVysledkovkyCisla(object): | ||||||
|  | 	"""Obsahuje věci, které se hodí vědět při konstruování výsledkovky. | ||||||
|  | 	Umožňuje snazší práci v templatu (lepší, než seznam).""" | ||||||
|  | 
 | ||||||
|  | 	def __init__(self, poradi, resitel, body_problemy_sezn, | ||||||
|  | 				body_cislo, body_rocnik, body_odjakziva, rok, body_podproblemy, body_podproblemy_iter): | ||||||
|  | 		self.resitel = resitel | ||||||
|  | 		self.rocnik_resitele = resitel.rocnik(rok) | ||||||
|  | 		self.body_cislo = body_cislo | ||||||
|  | 		self.body_rocnik = body_rocnik | ||||||
|  | 		self.body_celkem_odjakziva = body_odjakziva | ||||||
|  | 		self.poradi = poradi | ||||||
|  | 		self.body_problemy_sezn = body_problemy_sezn | ||||||
|  | 		self.titul = resitel.get_titul(body_odjakziva) | ||||||
|  | 		self.body_podproblemy = body_podproblemy | ||||||
|  | 		self.body_podproblemy_iter = body_podproblemy_iter  # TODELETE | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def pricti_body(slovnik, resitel, body): | ||||||
|  | 	""" Přiřazuje danému řešiteli body do slovníku. """ | ||||||
|  | 	# testujeme na None (""), pokud je to první řešení  | ||||||
|  | 	# daného řešitele, předěláme na 0 | ||||||
|  | 	# (v dalším kroku přičteme reálný počet bodů), | ||||||
|  | 	# rozlišujeme tím mezi 0 a neodevzdaným řešením | ||||||
|  | 	if slovnik[resitel.id] == "": | ||||||
|  | 		slovnik[resitel.id] = 0 | ||||||
|  | 	 | ||||||
|  | 	slovnik[resitel.id] += body | ||||||
|  | 
 | ||||||
|  | def secti_body_za_rocnik(za, aktivni_resitele): | ||||||
|  | 	""" Spočítá body za ročník (celý nebo do daného čísla),  | ||||||
|  | 		setřídí je sestupně a vrátí jako seznam. | ||||||
|  | 	Parametry: | ||||||
|  | 		za (typu Rocnik nebo Cislo)	spočítá za ročník, nebo za ročník až do  | ||||||
|  | 						daného čísla | ||||||
|  | 	""" | ||||||
|  | 	# spočítáme všem řešitelům jejich body za ročník (False => ne odjakživa) | ||||||
|  | 	resitel_rocnikbody_slov = body_resitelu(aktivni_resitele, za, False) | ||||||
|  | 	# zeptáme se na dvojice (řešitel, body) za ročník a setřídíme sestupně | ||||||
|  | 	resitel_rocnikbody_sezn = sorted(resitel_rocnikbody_slov.items(), | ||||||
|  | 					key = lambda x: x[1], reverse = True) | ||||||
|  | 	return resitel_rocnikbody_sezn | ||||||
|  | 
 | ||||||
|  | def secti_body_za_cislo(cislo, aktivni_resitele, hlavni_problemy=None): | ||||||
|  | 	""" Spočítá u řešitelů body za číslo a za jednotlivé hlavní problémy (témata).""" | ||||||
|  | 	# TODO setřídit hlavní problémy čísla podle id, ať jsou ve stejném pořadí pokaždé | ||||||
|  | 	# pro každý hlavní problém zavedeme slovník s body za daný hlavní problém  | ||||||
|  | 	# pro jednotlivé řešitele (slovník slovníků hlavních problémů) | ||||||
|  | 
 | ||||||
|  | 	print("Scitam cislo",cislo) | ||||||
|  | 
 | ||||||
|  | 	if hlavni_problemy is None: | ||||||
|  | 		hlavni_problemy = hlavni_problemy_f(problemy_cisla(cislo)) | ||||||
|  | 
 | ||||||
|  | 	def ne_clanek_ne_konfera(problem): | ||||||
|  | 		inst = problem.get_real_instance() | ||||||
|  | 		return not(isinstance(inst, m.Clanek) or isinstance(inst, m.Konfera)) | ||||||
|  | 
 | ||||||
|  | 	temata_a_spol = list(filter(ne_clanek_ne_konfera, hlavni_problemy)) | ||||||
|  | 
 | ||||||
|  | 	hlavni_problemy_slovnik = {} | ||||||
|  | 	for hp in temata_a_spol: | ||||||
|  | 		hlavni_problemy_slovnik[hp.id] = {} | ||||||
|  | 
 | ||||||
|  | 	hlavni_problemy_slovnik[-1] = {} | ||||||
|  | 
 | ||||||
|  | 	# zakládání prázdných záznamů pro řešitele | ||||||
|  | 	cislobody = {} | ||||||
|  | 	for ar in aktivni_resitele: | ||||||
|  | 		# řešitele převedeme na řetězec pomocí unikátního id | ||||||
|  | 		cislobody[ar.id] = "" | ||||||
|  | 		for hp in temata_a_spol: | ||||||
|  | 			slovnik = hlavni_problemy_slovnik[hp.id] | ||||||
|  | 			slovnik[ar.id] = "" | ||||||
|  | 
 | ||||||
|  | 		hlavni_problemy_slovnik[-1][ar.id] = "" | ||||||
|  | 	 | ||||||
|  | 	# vezmeme všechna řešení s body do daného čísla | ||||||
|  | 	reseni_do_cisla = m.Reseni.objects.prefetch_related('problem', 'resitele',  | ||||||
|  | 				'hodnoceni_set').filter(hodnoceni__cislo_body=cislo) | ||||||
|  | 	 | ||||||
|  | 	start = time.time()	 | ||||||
|  | 	# projdeme všechna řešení do čísla a přičteme body každému řešiteli do celkových | ||||||
|  | 	# bodů i do bodů za problém | ||||||
|  | 	for reseni in reseni_do_cisla: | ||||||
|  | 		 | ||||||
|  | 		# řešení může řešit více problémů | ||||||
|  | 		for prob in list(reseni.problem.all()): | ||||||
|  | 			nadproblem = hlavni_problem(prob) | ||||||
|  | 			if ne_clanek_ne_konfera(nadproblem): | ||||||
|  | 				nadproblem_slovnik = hlavni_problemy_slovnik[nadproblem.id] | ||||||
|  | 			else: | ||||||
|  | 				nadproblem_slovnik = hlavni_problemy_slovnik[-1] | ||||||
|  | 			 | ||||||
|  | 			# a mít více hodnocení | ||||||
|  | 			for hodn in list(reseni.hodnoceni_set.all()): | ||||||
|  | 				body = hodn.body	 | ||||||
|  | 			 | ||||||
|  | 				# a mít více řešitelů | ||||||
|  | 				for resitel in list(reseni.resitele.all()): | ||||||
|  | 					if resitel not in aktivni_resitele: | ||||||
|  | 						print("Skipping {}".format(resitel.id)) | ||||||
|  | 						continue | ||||||
|  | 					pricti_body(cislobody, resitel, body) | ||||||
|  | 					pricti_body(nadproblem_slovnik, resitel, body) | ||||||
|  | 	end = time.time() | ||||||
|  | 	print("for cykly:", end-start) | ||||||
|  | 	return hlavni_problemy_slovnik, cislobody | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def secti_body_za_cislo_podle_temat(cislo, aktivni_resitele, podproblemy=None, temata=None): | ||||||
|  | 	""" Spočítá u řešitelů body za číslo za úlohy v jednotlivých hlavních problémech (témata).""" | ||||||
|  | 	if temata is None: | ||||||
|  | 		temata = hlavni_problemy_f(problemy_cisla(cislo)) | ||||||
|  | 
 | ||||||
|  | 	if podproblemy is None: | ||||||
|  | 		podproblemy_v_cislu(cislo, hlavni_problemy=temata) | ||||||
|  | 
 | ||||||
|  | 	body_slovnik = {} | ||||||
|  | 	for tema in temata: | ||||||
|  | 		body_slovnik[tema.id] = {} | ||||||
|  | 		for problem in podproblemy[tema.id]: | ||||||
|  | 			body_slovnik[tema.id][problem.id] = {} | ||||||
|  | 	body_slovnik[-1] = {} | ||||||
|  | 	for problem in podproblemy[-1]: | ||||||
|  | 		body_slovnik[-1][problem.id] = {} | ||||||
|  | 
 | ||||||
|  | 	# zakládání prázdných záznamů pro řešitele | ||||||
|  | 	for ar in aktivni_resitele: | ||||||
|  | 		for tema in temata: | ||||||
|  | 			for problem in podproblemy[tema.id]: | ||||||
|  | 				body_slovnik[tema.id][problem.id][ar.id] = "" | ||||||
|  | 
 | ||||||
|  | 		for problem in podproblemy[-1]: | ||||||
|  | 			body_slovnik[-1][problem.id][ar.id] = "" | ||||||
|  | 
 | ||||||
|  | 	temata = set(t.id for t in temata) | ||||||
|  | 
 | ||||||
|  | 	# vezmeme všechna řešení s body do daného čísla | ||||||
|  | 	reseni_do_cisla = m.Reseni.objects.prefetch_related('problem', 'resitele', | ||||||
|  | 		'hodnoceni_set').filter(hodnoceni__cislo_body=cislo) | ||||||
|  | 
 | ||||||
|  | 	# projdeme všechna řešení do čísla a přičteme body každému řešiteli do celkových | ||||||
|  | 	# bodů i do bodů za problém | ||||||
|  | 	for reseni in reseni_do_cisla: | ||||||
|  | 
 | ||||||
|  | 		# řešení může řešit více problémů | ||||||
|  | 		for prob in reseni.problem.all(): | ||||||
|  | 			nadproblem = hlavni_problem(prob) | ||||||
|  | 			if nadproblem.id in temata: | ||||||
|  | 				nadproblem_slovnik = body_slovnik[nadproblem.id] | ||||||
|  | 			else: | ||||||
|  | 				nadproblem_slovnik = body_slovnik[-1] | ||||||
|  | 
 | ||||||
|  | 			problem_slovnik = nadproblem_slovnik[prob.id] | ||||||
|  | 
 | ||||||
|  | 			# a mít více hodnocení | ||||||
|  | 			for hodn in reseni.hodnoceni_set.all(): | ||||||
|  | 				body = hodn.body | ||||||
|  | 
 | ||||||
|  | 				# a mít více řešitelů | ||||||
|  | 				for resitel in reseni.resitele.all(): | ||||||
|  | 					if resitel not in aktivni_resitele: | ||||||
|  | 						print("Skipping {}".format(resitel.id)) | ||||||
|  | 						continue | ||||||
|  | 					pricti_body(problem_slovnik, resitel, body) | ||||||
|  | 	return body_slovnik | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # TODELETE | ||||||
|  | class FixedIterator: | ||||||
|  | 	def next(self): | ||||||
|  | 		return self.niter.__next__() | ||||||
|  | 
 | ||||||
|  | 	def __init__(self, niter): | ||||||
|  | 		self.niter = niter | ||||||
|  | # TODELETE | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def vysledkovka_cisla(cislo, context=None): | ||||||
|  | 	if context is None: | ||||||
|  | 		context = {} | ||||||
|  | 	problemy = problemy_cisla(cislo) | ||||||
|  | 	hlavni_problemy = hlavni_problemy_f(problemy) | ||||||
|  | 	## TODO možná chytřeji vybírat aktivní řešitele | ||||||
|  | 	# aktivní řešitelé - chceme letos něco poslal, TODO později vyfiltrujeme ty, kdo mají  | ||||||
|  | 	# u alespoň jedné hodnoty něco jiného než NULL | ||||||
|  | 	aktivni_resitele = list(aktivniResitele(cislo)) | ||||||
|  | 
 | ||||||
|  | 	# získáme body za číslo | ||||||
|  | 	hlavni_problemy_slovnik, cislobody = secti_body_za_cislo(cislo, aktivni_resitele, hlavni_problemy) | ||||||
|  | 
 | ||||||
|  | 	# získáme body za ročník, seznam obsahuje dvojice (řešitel_id, body) setřízené sestupně | ||||||
|  | 	resitel_rocnikbody_sezn = secti_body_za_rocnik(cislo, aktivni_resitele) | ||||||
|  | 
 | ||||||
|  | 	# získáme body odjakživa | ||||||
|  | 	resitel_odjakzivabody_slov = body_resitelu(aktivni_resitele, cislo) | ||||||
|  | 
 | ||||||
|  | 	# řešitelé setřídění podle bodů za číslo sestupně | ||||||
|  | 	setrizeni_resitele_id = [dvojice[0] for dvojice in resitel_rocnikbody_sezn] | ||||||
|  | 			 | ||||||
|  | 	# spočítáme pořadí řešitelů | ||||||
|  | 	setrizeni_resitele_body = [dvojice[1] for dvojice in resitel_rocnikbody_sezn] | ||||||
|  | 	poradi = sloupec_s_poradim(setrizeni_resitele_body) | ||||||
|  | 
 | ||||||
|  | 	# vytvoříme jednotlivé sloupce výsledkovky | ||||||
|  | 	radky_vysledkovky = [] | ||||||
|  | 	i = 0 | ||||||
|  | 
 | ||||||
|  | 	def ne_clanek_ne_konfera(problem): | ||||||
|  | 		 | ||||||
|  | 		return not(isinstance(problem.get_real_instance(), m.Clanek) or isinstance(problem.get_real_instance(), m.Konfera)) | ||||||
|  | 
 | ||||||
|  | 	temata_a_spol = list(filter(ne_clanek_ne_konfera, hlavni_problemy)) | ||||||
|  | 
 | ||||||
|  | 	# získáme body u jednotlivých témat | ||||||
|  | 	podproblemy = podproblemy_v_cislu(cislo, problemy, temata_a_spol) | ||||||
|  | 	problemy_slovnik = secti_body_za_cislo_podle_temat(cislo, aktivni_resitele, podproblemy, temata_a_spol) | ||||||
|  | 
 | ||||||
|  | 	# def not_empty(value): | ||||||
|  | 	# 	return value != '' | ||||||
|  | 	# | ||||||
|  | 	# je_nejake_ostatni = any(filter(not_empty, hlavni_problemy_slovnik[-1].values())) > 0 | ||||||
|  | 
 | ||||||
|  | 	je_nejake_ostatni = len(hlavni_problemy) - len(temata_a_spol) > 0 | ||||||
|  | 
 | ||||||
|  | 	setrizeni_resitele_slovnik = {} | ||||||
|  | 	setrizeni_resitele = m.Resitel.objects.filter(id__in=setrizeni_resitele_id).select_related('osoba') | ||||||
|  | 	for r in setrizeni_resitele: | ||||||
|  | 		setrizeni_resitele_slovnik[r.id] = r | ||||||
|  | 
 | ||||||
|  | 	for ar_id in setrizeni_resitele_id: | ||||||
|  | 		# získáme seznam bodů za problémy pro daného řešitele | ||||||
|  | 		body_problemy = [] | ||||||
|  | 		body_podproblemy = [] | ||||||
|  | 		for hp in temata_a_spol: | ||||||
|  | 			body_problemy.append(hlavni_problemy_slovnik[hp.id][ar_id]) | ||||||
|  | 			body_podproblemy.append([problemy_slovnik[hp.id][it.id][ar_id] for it in podproblemy[hp.id]]) | ||||||
|  | 		if je_nejake_ostatni: | ||||||
|  | 			body_problemy.append(hlavni_problemy_slovnik[-1][ar_id]) | ||||||
|  | 			body_podproblemy.append([problemy_slovnik[-1][it.id][ar_id] for it in podproblemy[-1]]) | ||||||
|  | 		# vytáhneme informace pro daného řešitele | ||||||
|  | 		radek = RadekVysledkovkyCisla( | ||||||
|  | 			poradi[i], # pořadí | ||||||
|  | 			setrizeni_resitele_slovnik[ar_id], # řešitel (z id) | ||||||
|  | 			body_problemy, # seznam bodů za hlavní problémy čísla | ||||||
|  | 			cislobody[ar_id], # body za číslo | ||||||
|  | 			setrizeni_resitele_body[i], # body za ročník (spočítané výše s pořadím) | ||||||
|  | 			resitel_odjakzivabody_slov[ar_id], # body odjakživa | ||||||
|  | 			cislo.rocnik, | ||||||
|  | 			body_podproblemy,  # body všech podproblémů | ||||||
|  | 			FixedIterator(body_podproblemy.__iter__())  # TODELETE | ||||||
|  | 		)  # ročník semináře pro zjištění ročníku řešitele | ||||||
|  | 		radky_vysledkovky.append(radek) | ||||||
|  | 		i += 1 | ||||||
|  | 
 | ||||||
|  | 	# vytahané informace předáváme do kontextu | ||||||
|  | 	context['cislo'] = cislo | ||||||
|  | 	context['radky_vysledkovky'] = radky_vysledkovky | ||||||
|  | 	context['problemy'] = temata_a_spol | ||||||
|  | 	context['ostatni'] = je_nejake_ostatni | ||||||
|  | 	pt = [podproblemy[it.id] for it in temata_a_spol]+[podproblemy[-1]] | ||||||
|  | 	context['podproblemy'] = pt | ||||||
|  | 	context['podproblemy_iter'] = FixedIterator(pt.__iter__())  # TODELETE | ||||||
|  | 	#context['v_cisle_zadane'] = TODO | ||||||
|  | 	#context['resene_problemy'] = resene_problemy | ||||||
|  | 	return context | ||||||
		Loading…
	
		Reference in a new issue
	
	 Pavel "LEdoian" Turinsky
						Pavel "LEdoian" Turinsky