Merge branch 'data_migrations' into odevzdavatko
This commit is contained in:
		
						commit
						b48d400543
					
				
					 54 changed files with 10996 additions and 182 deletions
				
			
		
							
								
								
									
										8
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								Makefile
									
									
									
									
									
								
							|  | @ -34,6 +34,8 @@ install_web: venv_check | |||
| 	pip install --upgrade setuptools | ||||
| 	# Instalace závislostí webu | ||||
| 	pip install -r requirements.txt --upgrade | ||||
| 	# Po vygenerování testdat spusť  ./manage.py loaddata sitetree_new.json, ať máš menu | ||||
| 	# Pro synchronizaci flatpages spusť make sync_prod_flatpages | ||||
| 
 | ||||
| install_venv: | ||||
| 	${VENV} ${VENV_PATH} | ||||
|  | @ -139,3 +141,9 @@ sync_local_db: | |||
| 
 | ||||
| # Sync database and media. See above lines
 | ||||
| sync_local: sync_local_media sync_local_db | ||||
| 
 | ||||
| # Push local compiled Vue to gimli test site
 | ||||
| push_compiled_vue_to_test: | ||||
| 	scp vue_frontend/webpack-stats.json mam-web@gimli:/akce/mam/www/mamweb-test/vue_frontend/ | ||||
| 	rsync -ave ssh seminar/static/seminar/vue mam-web@gimli:/akce/mam/www/mamweb-test/seminar/static/seminar/ | ||||
| 	ssh mam-web@gimli.ms.mff.cuni.cz 'cd /akce/mam/www/mamweb-test/ && . env/bin/activate && ./manage.py collectstatic --noinput' | ||||
|  |  | |||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										10
									
								
								mamweb/routers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								mamweb/routers.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| from rest_framework import routers | ||||
| from seminar import viewsets as vs | ||||
| 
 | ||||
| router = routers.DefaultRouter() | ||||
| 
 | ||||
| router.register(r'ulohavzoraknode', vs.UlohaVzorakNodeViewSet,basename='ulohavzoraknode') | ||||
| router.register(r'text', vs.TextViewSet) | ||||
| router.register(r'textnode', vs.TextNodeViewSet) | ||||
| router.register(r'castnode', vs.CastNodeViewSet) | ||||
| 
 | ||||
|  | @ -120,6 +120,10 @@ INSTALLED_APPS = ( | |||
|     'imagekit', | ||||
| 
 | ||||
|     'polymorphic', | ||||
|      | ||||
|     'webpack_loader', | ||||
|     'rest_framework', | ||||
|     'rest_framework.authtoken', | ||||
| 
 | ||||
|     # MaMweb | ||||
|     'mamweb', | ||||
|  | @ -184,6 +188,27 @@ CKEDITOR_CONFIGS = { | |||
|     }, | ||||
| } | ||||
| 
 | ||||
| # Webpack loader | ||||
| VUE_FRONTEND_DIR = os.path.join(BASE_DIR, 'vue_frontend') | ||||
| 
 | ||||
| WEBPACK_LOADER = { | ||||
|     'DEFAULT': { | ||||
|         'CACHE': False, | ||||
|         'BUNDLE_DIR_NAME': 'vue/',  # must end with slash | ||||
|         'STATS_FILE': os.path.join(VUE_FRONTEND_DIR, 'webpack-stats.json'), | ||||
|         'POLL_INTERVAL': 0.1, | ||||
|         'TIMEOUT': None, | ||||
|         'IGNORE': [r'.+\.hot-update.js', r'.+\.map'] | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| # Dajngo REST Framework | ||||
| 
 | ||||
| REST_FRAMEWORK = { | ||||
|     'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', | ||||
|     'PAGE_SIZE': 100 | ||||
| } | ||||
| 
 | ||||
| # Comments | ||||
| 
 | ||||
|  | @ -270,6 +295,9 @@ LOGGING = { | |||
|             }, | ||||
|         } | ||||
| 
 | ||||
| # Permissions for uploads | ||||
| FILE_UPLOAD_PERMISSIONS = 0o0644 | ||||
| 
 | ||||
| # MaM specific | ||||
| 
 | ||||
| SEMINAR_RESENI_DIR = os.path.join('reseni') | ||||
|  |  | |||
							
								
								
									
										38
									
								
								mamweb/static/css/mamweb-dev.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								mamweb/static/css/mamweb-dev.css
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,38 @@ | |||
| /* | ||||
| .pink { | ||||
| 	background-color: #ffc0cb; | ||||
| } | ||||
| 
 | ||||
| div.borderized { | ||||
| 	border-style: solid; | ||||
| 	border-radius: 5px; | ||||
| 	padding: 5px; | ||||
| 	padding-right: 20px; | ||||
| } | ||||
| 
 | ||||
| div.tnmenu { | ||||
| 	float: right; | ||||
| 	margin-right: 0px; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| div.parent { | ||||
| 	border-width: 2px; | ||||
| } | ||||
| 
 | ||||
| div.children { | ||||
| 	border-width: 1px; | ||||
| } | ||||
| 
 | ||||
| div.node_type { | ||||
| 	background-color: #d4d4d4; | ||||
| } | ||||
| 
 | ||||
| .hidden-tn { | ||||
| 	display: none; | ||||
| } | ||||
| 
 | ||||
| /*test*/ | ||||
| h1 { | ||||
| 	color: chartreuse; | ||||
| } | ||||
|  | @ -759,17 +759,26 @@ div.odpocet { | |||
| 
 | ||||
| /*stránka organizátorů*/ | ||||
| 
 | ||||
| div.seznam_orgu { | ||||
| div.seznam_orgu, div.rozcestnik_temat { | ||||
| 	text-align: center; | ||||
| 	padding-bottom: 10px; | ||||
| } | ||||
| 
 | ||||
| div.org_pole, div.rocnik_pole { | ||||
| div.org_pole, div.rocnik_pole, div.tema_pole { | ||||
| 	display: inline-block; | ||||
| 	width: 30%; | ||||
| 	min-width: 300px; | ||||
| 	text-align: center; | ||||
| } | ||||
| 
 | ||||
| div.tema_pole { | ||||
| 	display: inline-block; | ||||
| 	width: 40%; | ||||
| 	min-width: 350px; | ||||
| 	padding-bottom: 20px; | ||||
| 	text-align: center; | ||||
| } | ||||
| 
 | ||||
| div.cislo_pole { | ||||
| 	display: inline-block; | ||||
| 	width: 15%; | ||||
|  | @ -812,6 +821,11 @@ div.org_email { | |||
| 	height: 205px; | ||||
| } | ||||
| 
 | ||||
| #tema-rozcestnik.flip-card { | ||||
| 	width: 300px; | ||||
| 	height: 300px; | ||||
| } | ||||
| 
 | ||||
| /* This container is needed to position the front and back side */ | ||||
| .flip-card-inner { | ||||
|  position: relative; | ||||
|  | @ -835,7 +849,8 @@ div.org_email { | |||
|  backface-visibility: hidden; | ||||
| } | ||||
| 
 | ||||
| div.flip-card-foto img { | ||||
| 
 | ||||
| div.flip-card-foto, div.flip-card-foto img { | ||||
| 	width: 100%; | ||||
| 	height: 100%; | ||||
| 
 | ||||
|  |  | |||
|  | @ -6,7 +6,8 @@ | |||
|     <title>{% block title %}{% block nadpis1a %}{% endblock %} – Korespondenční seminář M&M{% endblock title %}</title> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||
|     <link rel="shortcut icon" href="{% static 'images/MATFYZ_MM_barevne.svg' %}" type="image/x-icon"> | ||||
|     {% render_block "css" %} | ||||
|     {% render_block css %} | ||||
|     {% block custom_css %}{% endblock %} | ||||
|     <link href="{% static 'css/bootstrap-theme.css' %}" rel="stylesheet"> | ||||
|     <link href="{% static 'css/bootstrap.css' %}" rel="stylesheet"> | ||||
|     <link href="{% static 'css/mamweb.css' %}" rel="stylesheet"> | ||||
|  |  | |||
|  | @ -6,6 +6,8 @@ from django.views.generic.base import TemplateView | |||
| from django import views | ||||
| from django.urls import path # As per docs. | ||||
| 
 | ||||
| from .routers import router | ||||
| 
 | ||||
| urlpatterns = [ | ||||
| 
 | ||||
| 	# Admin a nastroje | ||||
|  | @ -25,6 +27,9 @@ urlpatterns = [ | |||
| 	path('comments_dj/', include('django_comments.urls')), | ||||
| 	path('comments_fl/', include('fluent_comments.urls')), | ||||
| 
 | ||||
| 	# REST API | ||||
| 	path('api/', include(router.urls)), | ||||
| 
 | ||||
| ] | ||||
| 
 | ||||
| # This is only needed when using runserver. | ||||
|  |  | |||
|  | @ -28,6 +28,9 @@ django-imagekit | |||
| django-polymorphic | ||||
| django-sitetree | ||||
| django_reverse_admin | ||||
| django-rest-framework | ||||
| django-webpack-loader | ||||
| django-rest-polymorphic | ||||
| 
 | ||||
| # Comments | ||||
| akismet==1.0.1 | ||||
|  |  | |||
							
								
								
									
										1
									
								
								seminar/.~lock.profile_vysledkovka.txt#
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								seminar/.~lock.profile_vysledkovka.txt#
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| ,anet,erebus,25.03.2020 22:21,file:///home/anet/.config/libreoffice/4; | ||||
|  | @ -107,6 +107,8 @@ class TreeNodeAdmin(PolymorphicParentModelAdmin): | |||
| 		m.PohadkaNode, | ||||
| 		m.UlohaVzorakNode, | ||||
| 		m.TextNode, | ||||
| 		m.CastNode, | ||||
| 		m.OrgTextNode, | ||||
| 		] | ||||
| 
 | ||||
| 	actions = ['aktualizuj_nazvy'] | ||||
|  | @ -160,6 +162,17 @@ class TextNodeAdmin(PolymorphicChildModelAdmin): | |||
| 	base_model = m.TextNode | ||||
| 	show_in_index = True | ||||
| 
 | ||||
| @admin.register(m.CastNode) | ||||
| class TextNodeAdmin(PolymorphicChildModelAdmin): | ||||
| 	base_model = m.CastNode | ||||
| 	show_in_index = True | ||||
| 	fields = ('nadpis',) | ||||
| 
 | ||||
| @admin.register(m.OrgTextNode) | ||||
| class TextNodeAdmin(PolymorphicChildModelAdmin): | ||||
| 	base_model = m.OrgTextNode | ||||
| 	show_in_index = True | ||||
| 
 | ||||
| 
 | ||||
| admin.site.register(m.Nastaveni, SingletonModelAdmin) | ||||
| admin.site.register(m.Novinky) | ||||
|  |  | |||
|  | @ -277,3 +277,8 @@ ReseniSPrilohamiFormSet = inlineformset_factory(m.Reseni,m.PrilohaReseni, | |||
| 
 | ||||
| 		) | ||||
| 
 | ||||
| class NahrajObrazekKTreeNoduForm(forms.ModelForm): | ||||
| 	class Meta: | ||||
| 		model = m.Obrazek | ||||
| 		fields = ('na_web',) | ||||
| 
 | ||||
|  |  | |||
|  | @ -28,7 +28,6 @@ from reversion import revisions as reversion | |||
| from seminar.utils import roman, FirstTagParser # Pro získání úryvku z TextNode | ||||
| 
 | ||||
| from unidecode import unidecode # Používám pro získání ID odkazu (ještě je to někde po někom zakomentované) | ||||
| from seminar.treelib import safe_pred | ||||
| 
 | ||||
| from polymorphic.models import PolymorphicModel | ||||
| 
 | ||||
|  | @ -744,11 +743,20 @@ class Problem(SeminarModelBase,PolymorphicModel): | |||
| 		return '<Není zadaný>' | ||||
| 
 | ||||
| 	def verejne(self): | ||||
| 		# FIXME: Tohle se liší podle typu problému, má se udělat polymorfně. | ||||
| 		# Zatím je tu jen dummy fail-safe default: nic není veřejné. | ||||
| 		return False | ||||
| 		# FIXME: Tohle je blbost | ||||
| 		return (self.cislo_zadani and self.cislo_zadani.verejne()) | ||||
| 		# aktuálně podle stavu problému | ||||
| 		# 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.  | ||||
| 		# Je to tak správně? | ||||
| 		stav_verejny = False | ||||
| 		if self.stav == 'zadany' or self.stav == 'vyreseny': | ||||
| 			stav_verejny = True | ||||
| 		return stav_verejny | ||||
| 		 | ||||
| 		#cislo_verejne = False | ||||
| 		#if (self.cislo_zadani and self.cislo_zadani.verejne()): | ||||
| 		#	cislo_verejne = True | ||||
| 		 | ||||
| 		#return (stav_verejny and cislo_verejne) | ||||
| 	verejne.boolean = True | ||||
| 
 | ||||
| 	def verejne_url(self): | ||||
|  | @ -844,9 +852,7 @@ class Text(SeminarModelBase): | |||
| 			tn.save() | ||||
| 
 | ||||
| 	def __str__(self): | ||||
| 		parser = FirstTagParser()		 | ||||
| 		parser.feed(str(self.na_web)) | ||||
| 		return parser.firstTag | ||||
| 		return str(self.na_web)[:20] | ||||
| 		 | ||||
| class Uloha(Problem): | ||||
| 	class Meta: | ||||
|  | @ -1367,6 +1373,7 @@ class MezicisloNode(TreeNode): | |||
| 
 | ||||
| 	# TODO: Využít TreeLib | ||||
| 	def aktualizuj_nazev(self): | ||||
| 		from seminar.treelib import safe_pred | ||||
| 		if safe_pred(self) is not None: | ||||
| 			if (self.prev.get_real_instance_class() != CisloNode and | ||||
| 				self.prev.get_real_instance_class() != MezicisloNode): | ||||
|  |  | |||
							
								
								
									
										7
									
								
								seminar/permissions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								seminar/permissions.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | |||
| from rest_framework.permissions import BasePermission | ||||
| 
 | ||||
| class AllowWrite(BasePermission): | ||||
| 
 | ||||
|          def has_permission(self, request, view): | ||||
|                   return request.user.has_perm('auth.org') | ||||
| 
 | ||||
							
								
								
									
										
											BIN
										
									
								
								seminar/static/images/tema-bez-obrazku.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								seminar/static/images/tema-bez-obrazku.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 300 KiB | 
							
								
								
									
										18
									
								
								seminar/static/seminar/treenode_editor.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								seminar/static/seminar/treenode_editor.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | |||
| function showSelectedItemForm(sel,id){ | ||||
| 	var option; | ||||
| 	var name; | ||||
| 	var div; | ||||
| 	Array.from(sel.options).forEach(function(option){ | ||||
| 		console.log(option); | ||||
| 		name = 'pridat-'+option.value+'-'+id; | ||||
| 		div = document.getElementById(name); | ||||
| 		console.log(div); | ||||
| 		div.style.display = 'none'; | ||||
| 	}); | ||||
| 	name = sel.options[sel.selectedIndex].value; | ||||
| 	name = 'pridat-'+name+'-'+id; | ||||
| 	div = document.getElementById(name); | ||||
| 	console.log(div); | ||||
| 	div.style.display = 'block'; | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										97
									
								
								seminar/templates/seminar/archiv/cislo-normal.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								seminar/templates/seminar/archiv/cislo-normal.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,97 @@ | |||
| {% extends "seminar/archiv/base_cisla.html" %} | ||||
| 
 | ||||
| {# {% block content %} | ||||
|  <div> | ||||
| 
 | ||||
|   <h1> | ||||
|     {% block nadpis1a %}{% block nadpis1b %} | ||||
|       Číslo {{ cislo }} | ||||
|     {% endblock %}{% endblock %} | ||||
|   </h1> | ||||
| 
 | ||||
|   {% 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 %} | ||||
|     <h2>Zadané problémy</h2> | ||||
|     <ul> | ||||
|     {% for p in v_cisle_zadane %} | ||||
|       <li{% if user.is_staff and not cislo.verejne %} class='mam-org-only'{% endif %}> | ||||
|         {% if user.is_staff or cislo.verejne %} | ||||
|           <a href='{{ p.verejne_url }}'>{% endif %}{{ p.kod_v_rocniku }} {{ p.nazev }} {{ p.body_v_zavorce }}{% if user.is_staff or cislo.verejne %}</a>{% endif %} | ||||
|     {% endfor %} | ||||
|     </ul> | ||||
|   {% endif %} | ||||
| 
 | ||||
|   {% if resene_problemy %} | ||||
|     <h2>Řešené problémy</h2> | ||||
|     <ul> | ||||
|     {% for p in resene_problemy %} | ||||
|       <li{% if user.is_staff and not cislo.verejne %} class='mam-org-only'{% endif %}> | ||||
|         {% if user.is_staff or cislo.verejne %} | ||||
|           <a href='{{ p.verejne_url }}'>{% endif %}{{ p.kod_v_rocniku }} {{ p.nazev }} {{ p.body_v_zavorce }}{% if user.is_staff or cislo.verejne %}</a>{% endif %} | ||||
|     {% endfor %} | ||||
|     </ul> | ||||
|   {% endif %} | ||||
| 
 | ||||
|   {% if user.is_staff %} | ||||
|       <div class="mam-org-only"> | ||||
|         <h2> Orgovské odkazy </h2> | ||||
|         <ul> | ||||
|           <li><a href="obalky.pdf">Obálky (PDF)</a></li> | ||||
|           <li><a href="tituly.tex">Tituly (TeX)</a></li> | ||||
|           <li><a href="vysledkovka.tex">Výsledkovka (TeX)</a></li> | ||||
|           <li><a href="obalkovani">Obálkování</a></li> | ||||
|         </ul> | ||||
|       </div> | ||||
|   {% endif %}  | ||||
| 
 | ||||
|   {% if cislo.verejna_vysledkovka %} | ||||
|   <h2>Výsledkovka</h2> | ||||
|   {% else %} | ||||
|     {% if user.is_staff %} | ||||
|       <div class='mam-org-only'> | ||||
|       <h2>Výsledkovka (neveřejná)</h2> | ||||
|     {% endif %} | ||||
|   {% endif %} | ||||
| 
 | ||||
|   {% if cislo.verejna_vysledkovka or user.is_staff %} | ||||
|     <table class='vysledkovka'> | ||||
|       <tr class='border-b'> | ||||
|         <th class='border-r'># | ||||
|         <th class='border-r'>Jméno #} | ||||
|         {# problémy by měly být veřejné, když je veřejná výsledkovka #} | ||||
| {#        {% for p in problemy %} | ||||
|         <th class='border-r'><a href="{{ p.verejne_url }}">{{ p.kod_v_rocniku }}</a> | ||||
|         {% endfor %} | ||||
|         <th class='border-r'>Za číslo</sup> | ||||
|         <th class='border-r'>Za ročník | ||||
|         <th class='border-r'>Odjakživa | ||||
|     {% for rv in radky_vysledkovky %} | ||||
|       <tr> | ||||
|         <td class='border-r'>{% autoescape off %}{{ rv.poradi }}{% endautoescape %} | ||||
|             <th class='border-r'> | ||||
|             {% if rv.resitel.titul != "" %} | ||||
|               {{ rv.resitel.titul }}<sup>MM</sup> | ||||
|             {% endif %} | ||||
|             {{ rv.resitel.osoba.plne_jmeno }} | ||||
|         {% for b in rv.hlavni_problemy_body %} | ||||
|         <td class='border-r'>{{ b }} | ||||
|         {% endfor %} | ||||
|         <td class='border-r'>{{ rv.body_cislo }} | ||||
|         <td class='border-r'><b>{{ rv.body_rocnik }}</b> | ||||
|         <td class='border-r'>{{ rv.body_celkem_odjakziva }} | ||||
|       </tr> | ||||
|     {% endfor %} | ||||
|     </table> | ||||
|   {% endif %} | ||||
| 
 | ||||
|   {% if not cislo.verejna_vysledkovka and user.is_staff %} | ||||
|       </div> | ||||
|   {% endif %} | ||||
| 
 | ||||
| </div>  | ||||
| {% endblock content %} #} | ||||
| 
 | ||||
|  | @ -1,4 +1,5 @@ | |||
| {% extends "base.html" %} | ||||
| {% load render_bundle from webpack_loader %} | ||||
| 
 | ||||
|  {% block content %} | ||||
|  <div> | ||||
|  | @ -48,6 +49,14 @@ | |||
|       </div> | ||||
|   {% endif %} | ||||
| 
 | ||||
|   <script id="vuedata" type="application/json">{"treenode":{{cislo.cislonode.id}}}</script> | ||||
|   <div id="app"> | ||||
| 	  <app></app> | ||||
|   </div> | ||||
|   {% render_bundle 'chunk-vendors' %} | ||||
|   {% render_bundle 'vue_app_01' %} | ||||
| 
 | ||||
| 
 | ||||
|   {% if cislo.verejna_vysledkovka %} | ||||
|   <h2>Výsledkovka</h2> | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										19
									
								
								seminar/templates/seminar/archiv/problem_tema.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								seminar/templates/seminar/archiv/problem_tema.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| {% extends "seminar/archiv/problem.html" %} | ||||
| 
 | ||||
| {% block problem %} | ||||
|     <h1> | ||||
|       {% block nadpis1a %}{% block nadpis1b %} | ||||
|         {{ problem.nazev_typu }} {{ problem.kod_v_rocniku }}: {{ problem.nazev }} | ||||
|       {% endblock %}{% endblock %} | ||||
|     </h1> | ||||
| 
 | ||||
|   <h2>Zadání</h2> | ||||
|   {{ problem.text_zadani |safe }} | ||||
|   {% if problem.text_reseni %} | ||||
|     <h2>Řešení</h2> | ||||
|     {{ problem.text_reseni |safe }} | ||||
|   {% endif %} | ||||
| 
 | ||||
|   {# TODO vysledkovka tematu #} | ||||
| 
 | ||||
| {% endblock %} | ||||
							
								
								
									
										23
									
								
								seminar/templates/seminar/archiv/problem_uloha.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								seminar/templates/seminar/archiv/problem_uloha.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | |||
| {% extends "seminar/archiv/problem.html" %} | ||||
| 
 | ||||
| {% block problem %} | ||||
|     <h1> | ||||
|       {% block nadpis1a %}{% block nadpis1b %} | ||||
|         {{ problem.nazev_typu }} {{ problem.kod_v_rocniku }}: {{ problem.nazev }} {{ problem.body_v_zavorce }} | ||||
|       {% endblock %}{% endblock %} | ||||
|     </h1> | ||||
|   {% if problem.cislo_zadani %} | ||||
|     <p>Zadáno v čísle <a href='{{ problem.cislo_zadani.verejne_url }}'>{{ problem.cislo_zadani.kod }}</a>. | ||||
|   {% endif %} | ||||
|   {% if problem.cislo_reseni %} | ||||
|     <p>Řešeno v čísle <a href='{{ problem.cislo_reseni.verejne_url }}'>{{ problem.cislo_reseni.kod }}</a>. | ||||
|   {% endif %} | ||||
| 
 | ||||
|   <h2>Zadání</h2> | ||||
|   {{ problem.text_zadani |safe }} | ||||
|   {% if problem.text_reseni %} | ||||
|     <h2>Řešení</h2> | ||||
|     {{ problem.text_reseni |safe }} | ||||
|   {% endif %} | ||||
| 
 | ||||
| {% endblock %} | ||||
							
								
								
									
										23
									
								
								seminar/templates/seminar/orphanage.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								seminar/templates/seminar/orphanage.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | |||
| {% extends "seminar/archiv/base.html" %} | ||||
| {% load staticfiles %} | ||||
| {% load sekizai_tags %} | ||||
| 
 | ||||
| {# toto z nejakeho duvodu nefunguje #} | ||||
| {% addtoblock css %} | ||||
| dfsdfs  | ||||
| <link rel="stylesheet" type="text/css" href="{% static 'css/mamweb-dev.css' %}" /> | ||||
| {% endaddtoblock "css" %} | ||||
| 
 | ||||
| {% block custom_css %} | ||||
| <link rel="stylesheet" type="text/css" href="{% static 'css/mamweb-dev.css' %}" /> | ||||
| {% endblock custom_css %} | ||||
| 
 | ||||
| {% load comments %} | ||||
| 
 | ||||
| {% block content %} | ||||
| <ul> | ||||
| {% for obj in object_list %} | ||||
| <li>{{obj}} (id {{obj.id}})</li> | ||||
| {% endfor %} | ||||
| </ul> | ||||
| {% endblock content %} | ||||
|  | @ -1,14 +1,54 @@ | |||
| {% extends "base.html" %} | ||||
| 
 | ||||
| 
 | ||||
| {% block content %} | ||||
| 
 | ||||
| <h2> | ||||
|   {% block nadpis1a %}{% block nadpis1b %} | ||||
|   Aktuální témata | ||||
|   {% endblock %}{% endblock %} | ||||
| </h2> | ||||
| 
 | ||||
| <p>Témata jsou texty nejen z oblasti matematiky, fyziky a informatiky, které popisují nějaký | ||||
|   problém a jsou doprovázeny návodnými úlohami. Vaším úkolem je zamyslet se nad daným | ||||
|   problémem a sepsat vaše úvahy ve formě krátkého textu.</p> | ||||
| 
 | ||||
| <p><a href="/co-je-MaM/jak-resit/">Jak řešit téma?</a></p> | ||||
| 
 | ||||
| 
 | ||||
| <div class="rozcestnik_temat"> | ||||
| 
 | ||||
| {% for tematko in tematka %} | ||||
| <h1>{{tematko.nazev}}</h1> | ||||
| <p>{{tematko.abstrakt}}</p> | ||||
| 	<ul> | ||||
| 	{% for cislo in tematko.cisla %} | ||||
| 		<li><a href="/{{rocnik}}/t{{tematko.kod}}/#{{cislo.0.1}}">{{cislo.0.0}}</a></li> | ||||
| 		<ul> | ||||
| 		{% for odkaz in cislo.1 %} | ||||
| 			<li><a href="/{{rocnik}}/t{{tematko.kod}}/#{{odkaz.1}}">{{odkaz.0}}</a></li>	 | ||||
| 		{% endfor %} | ||||
| 		</ul> | ||||
| 	{% endfor %} | ||||
| 	</ul> | ||||
| 
 | ||||
| {# karta témátka - zepředu ilustrační, zezadu abstrakt #} | ||||
| <div class="tema_pole"> | ||||
| 
 | ||||
|   <h3> | ||||
|     <a href='{{ rocnik.verejne_url }}'>Téma {{ tematko.nazev }}</a> | ||||
|   </h3> | ||||
| 
 | ||||
|   <div class="flip-card" id="tema-rozcestnik"> | ||||
| 
 | ||||
|     <div class="flip-card-inner"> | ||||
|       <div class="flip-card-front"> | ||||
|         <div class="flip-card-foto"> | ||||
|           {% if tematko.obrazek %} | ||||
|             <img src="{{ tematko.obrazek.url }}" alt="{{ tematko.nazev }}"> | ||||
|           {% else %} {# pokud témátko nemá fotku, zobrazuje se defaultní obrázek #} | ||||
|             {% load static %} <img src="{% static 'images/tema-bez-obrazku.png' %}" alt="{{ tematko.nazev }}"> | ||||
|           {% endif %} | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="flip-card-back"> | ||||
|           <p>{{ tematko.abstrakt }}</p> | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
|   </div> | ||||
| </div> | ||||
| {# konec karty témátka #} | ||||
| {% endfor %} | ||||
| 
 | ||||
| </div> | ||||
| 
 | ||||
| {% endblock %} | ||||
|  |  | |||
|  | @ -1,10 +1,12 @@ | |||
| {% extends "base.html" %} | ||||
| {% load render_bundle from webpack_loader %} | ||||
| 
 | ||||
| {% load comments %} | ||||
| 
 | ||||
| {% block content %} | ||||
| <div id="app"> | ||||
|   <app></app> | ||||
| </div> | ||||
| {% render_bundle 'chunk-vendors' %} | ||||
| {% render_bundle 'vue_app_01' %} | ||||
| 
 | ||||
| {%with obj=tnldata depth=1 template_name="seminar/treenode_recursive.html" %} | ||||
|    {%include template_name%} | ||||
| {%endwith%} | ||||
| {% endblock content %} | ||||
|  |  | |||
							
								
								
									
										27
									
								
								seminar/templates/seminar/treenode_add_stub.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								seminar/templates/seminar/treenode_add_stub.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | |||
| {% load treenodes %} | ||||
| {% if kam_slug == "syn" %} | ||||
| 	{% appendableChildren obj as dostupne_typy %} | ||||
| {% else %} | ||||
| 	{% appendableChildren obj.parent as dostupne_typy %} | ||||
| {% endif %} | ||||
| 
 | ||||
| {# ulohaZadani ulohaVzorak Reseni Cast Text #} | ||||
| {% if dostupne_typy %} | ||||
| 	<div class="pink">Přidat {{kam}} | ||||
| 		<select name="pridat-typ-{{obj.node.id}}-{{kam_slug}}" onchange="showSelectedItemForm(this,'{{obj.node.id}}-{{kam_slug}}')"> | ||||
| 			{% for typ in dostupne_typy %} | ||||
| 			<option value="{{typ.0}}">{{typ.1}}</option> | ||||
| 			{% endfor %} | ||||
| 		</select> | ||||
| 		<div class="hidden-tn" id="pridat-castNode-{{obj.node.id}}-{{kam_slug}}"> | ||||
| 			Nadpis: <input name="pridat-cast-{{obj.node.id}}-{{kam_slug}}" type="text">  | ||||
| 			<button action="submit" formaction="{%url 'treenode_pridat' obj.node.id kam_slug%}">Přidat</button> | ||||
| 		</div> | ||||
| 		<div class="hidden-tn" id="pridat-textNode-{{obj.node.id}}-{{kam_slug}}"> Vytvořit</div> | ||||
| 		<div class="hidden-tn" id="pridat-reseniNode-{{obj.node.id}}-{{kam_slug}}">Vytvořit, Tady bude autocomplete na reseniNode</div> | ||||
| 		<div class="hidden-tn" id="pridat-ulohaZadaniNode-{{obj.node.id}}-{{kam_slug}}">Vytvořit zadání</div> | ||||
| 		<div class="hidden-tn" id="pridat-ulohaVzorakNode-{{obj.node.id}}-{{kam_slug}}">Vytvořit vzorák k: Tady bude autocomplete na problémy k aktuálnímu kontextu</div> | ||||
| 
 | ||||
| 
 | ||||
| 	</div> | ||||
| {% endif %}{# appendablebleChildren #} | ||||
							
								
								
									
										18
									
								
								seminar/templates/seminar/treenode_name.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								seminar/templates/seminar/treenode_name.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | |||
| {% load treenodes %} | ||||
| {% if obj.node|isRocnik %} | ||||
| <h{{depth}}> Ročník {{obj.node.rocnik}} </h{{depth}}> | ||||
| {% elif obj.node|isCislo %} | ||||
| <h{{depth}}> Číslo {{obj.node.cislo}} </h{{depth}}> | ||||
| {% elif obj.node|isTemaVCisle %} | ||||
| <h{{depth}}> Téma {{obj.node.tema.nazev}} </h{{depth}}> | ||||
| {% elif obj.node|isUlohaZadani %} | ||||
| <h{{depth}}>Úloha {{obj.node.uloha.kod_v_rocniku}} ({{obj.node.uloha.max_body}} b)</h{{depth}}> | ||||
| {% elif obj.node|isUlohaVzorak %} | ||||
| <h{{depth}}>Řešení: {{obj.node.uloha.kod_v_rocniku}}</h{{depth}}> | ||||
| {% elif obj.node|isCast %} | ||||
| <h{{depth}}> {{obj.node.nadpis}} </h{{depth}}> | ||||
| {% elif obj.node|isText %} | ||||
| {{obj.node.text.na_web}} | ||||
| {% else %} | ||||
| Objekt jiného typu {{obj.node}} | ||||
| {% endif %} | ||||
|  | @ -1,28 +1,55 @@ | |||
| {% load treenodes %} | ||||
| {# <b>{{depth}}</b> #} | ||||
| <div> | ||||
| {%   if obj.node|isRocnik %} | ||||
| <h{{depth}}> Ročník {{obj.node.rocnik}} </h{{depth}}> | ||||
| {% elif obj.node|isCislo %} | ||||
| <h{{depth}}> Číslo {{obj.node.cislo}} </h{{depth}}> | ||||
| {% elif obj.node|isTemaVCisle %} | ||||
| <h{{depth}}> Téma {{obj.node.tema.nazev}} </h{{depth}}> | ||||
| {% elif obj.node|isUlohaZadani %} | ||||
| <h{{depth}}>Úloha {{obj.node.uloha.kod_v_rocniku}} ({{obj.node.uloha.max_body}} b)</h{{depth}}> | ||||
| {% elif obj.node|isUlohaVzorak %} | ||||
| <h{{depth}}>Řešení: {{obj.node.uloha.kod_v_rocniku}}</h{{depth}}> | ||||
| {% elif obj.node|isText %} | ||||
| {{obj.node.text.na_web}} | ||||
| {% else %} | ||||
| Objekt jiného typu {{obj.node}} | ||||
| {% endif %} | ||||
|     {%if obj.children %} | ||||
| 	<div> | ||||
|          {%for ch in obj.children %} | ||||
|               {%with obj=ch depth=depth|add:"1" template_name="seminar/treenode_recursive.html" %} | ||||
|                    {%include template_name%} | ||||
|               {%endwith%} | ||||
|          {%endfor%} | ||||
| 	</div> | ||||
|     {%endif%} | ||||
| <div class="borderized parent"> | ||||
| <div class="node_type"> | ||||
| 	{{obj.node}} | ||||
| 	{{obj.node.id}} | ||||
| 	{% if obj.node|deletable %} | ||||
| 		<button type="submit" formaction="{%url 'treenode_smazat' obj.node.id%}">Smazat</button> | ||||
| 	{% endif %} | ||||
| 	{% if obj.parent and  obj.parent|editableSiblings %} | ||||
| 		<button type="submit" formaction="{%url 'treenode_odvesitpryc' obj.node.id%}">Odvěsit pryč ze stromu {{obj.parent.node}}</button> | ||||
| 	{% endif %} | ||||
| 	{% if obj|canPodvesitPred %} | ||||
| 		<button type="submit" formaction="{%url 'treenode_podvesit' obj.node.id 'pred'%}">Podvěsit pod předchozí</button> - nejsou testovací data | ||||
| 	{% endif %} | ||||
| 	{% if obj|canPodvesitZa %} | ||||
| 		<button type="submit" formaction="{%url 'treenode_podvesit' obj.node.id 'za'%}">Podvěsit pod následující</button> - nejsou testovací data | ||||
| 	{% endif %} | ||||
| 
 | ||||
| 
 | ||||
| </div> | ||||
| {% if False %} | ||||
| 	<div class="node_move"> | ||||
| 		FIXME: není zatím implementováno | ||||
| 		<button>Zvyš úroveň nadpisu</button> - nejsou testovací data | ||||
| 	</div> | ||||
| {% endif %} | ||||
| {% include "seminar/treenode_name.html" %} | ||||
| {%if obj.children %} | ||||
| 	<div class="borderized children"> | ||||
| 
 | ||||
| 		{% with kam="před" kam_slug="syn" %} {% include "seminar/treenode_add_stub.html" %} {% endwith %} | ||||
| 		{%for ch in obj.children %} | ||||
| 		 | ||||
| 			{# ----------- Vypisujeme podstrom ----------#} | ||||
| 			{%with obj=ch depth=depth|add:"1" %} {%include "seminar/treenode_recursive.html" %} {%endwith%} | ||||
| 			{# ----------- Přidáváme mezi syny / za posledního -------- #} | ||||
| 			{% if forloop.last %} | ||||
| 				{% with kam="za" kam_slug="za" obj=ch %} {% include "seminar/treenode_add_stub.html" %} {% endwith %} | ||||
| 			{% else %} | ||||
| 				{% with kam="mezi" obj=ch kam_slug="za" %} {% include "seminar/treenode_add_stub.html" %} {% endwith %} | ||||
| 			{% endif %} | ||||
| 			{# ----------- Prohazujeme sousedy ----------#} | ||||
| 			<div class="pink"> | ||||
| 			{% if not forloop.last and ch|editableSiblings %} | ||||
| 				<button type="submit" formaction="{%url 'treenode_prohodit' ch.node.id%}">Prohodit ^ a v</button> | ||||
| 			{% endif %} | ||||
| 			</div> | ||||
| 		{% endfor %} | ||||
| 	</div> | ||||
| {% else %} | ||||
| 	{# ----------- Přidáváme prvního syna ----------#} | ||||
| 	{% with kam="jako syna" kam_slug="syn" %} {% include "seminar/treenode_add_stub.html" %} {% endwith %} | ||||
| {%endif%} | ||||
| </div> | ||||
|  |  | |||
							
								
								
									
										7
									
								
								seminar/templates/seminar/vuetest.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								seminar/templates/seminar/vuetest.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | |||
| {% load render_bundle from webpack_loader %} | ||||
| 
 | ||||
| <div id="app"> | ||||
|   <app></app> | ||||
| </div> | ||||
| {% render_bundle 'chunk-vendors' %} | ||||
| {% render_bundle 'vue_app_01' %} | ||||
|  | @ -1,49 +1,243 @@ | |||
| from django import template | ||||
| from enum import Enum | ||||
| import seminar.models as m | ||||
| 
 | ||||
| register = template.Library() | ||||
| 
 | ||||
| @register.filter | ||||
| def nodeType(value): | ||||
| 	if isinstance(value,RocnikNode): return "Ročník" | ||||
| 	if isinstance(value,CisloNode): return "Číslo" | ||||
| 	if isinstance(value,CastNode): return "Část" | ||||
| 	if isinstance(value,TextNode): return "Text" | ||||
| 	if isinstance(value,TemaVCisleNode): return "Téma v čísle" | ||||
| 	if isinstance(value,KonferaNode): return "Konfera" | ||||
| 	if isinstance(value,ClanekNode): return "Článek" | ||||
| 	if isinstance(value,UlohaVzorakNode): return "Vzorák" | ||||
| 	if isinstance(value,UlohaZadaniNode): return "Zadání úlohy" | ||||
| 	if isinstance(value,PohadkaNode): return "Pohádka" | ||||
| 
 | ||||
| ### NASLEDUJICI FUNKCE SE POUZIVAJI VE views_all.py V SEKCI PRIPRAVJICI TNLData | ||||
| ### NEMAZAT, PRESUNOUT S TNLDaty NEKAM BOKEM | ||||
| 
 | ||||
| @register.filter | ||||
| def isRocnik(value): | ||||
|     return isinstance(value, m.RocnikNode) | ||||
| 	return isinstance(value, m.RocnikNode) | ||||
| 
 | ||||
| @register.filter | ||||
| def isCislo(value): | ||||
|     return isinstance(value, m.CisloNode) | ||||
| 	return isinstance(value, m.CisloNode) | ||||
| 
 | ||||
| @register.filter | ||||
| def isCast(value): | ||||
|     return isinstance(value, m.CastNode) | ||||
| 	return isinstance(value, m.CastNode) | ||||
| 
 | ||||
| @register.filter | ||||
| def isText(value): | ||||
|     return isinstance(value, m.TextNode) | ||||
| 	return isinstance(value, m.TextNode) | ||||
| 
 | ||||
| @register.filter | ||||
| def isTemaVCisle(value): | ||||
|     return isinstance(value, m.TemaVCisleNode) | ||||
| 	return isinstance(value, m.TemaVCisleNode) | ||||
| 
 | ||||
| @register.filter | ||||
| def isKonfera(value): | ||||
|     return isinstance(value, m.KonferaNode) | ||||
| 	return isinstance(value, m.KonferaNode) | ||||
| 
 | ||||
| @register.filter | ||||
| def isClanek(value): | ||||
|     return isinstance(value, m.ClanekNode) | ||||
| 	return isinstance(value, m.ClanekNode) | ||||
| 
 | ||||
| @register.filter | ||||
| def isUlohaVzorak(value): | ||||
|     return isinstance(value, m.UlohaVzorakNode) | ||||
| 	return isinstance(value, m.UlohaVzorakNode) | ||||
| 
 | ||||
| @register.filter | ||||
| def isUlohaZadani(value): | ||||
|     return isinstance(value, m.UlohaZadaniNode) | ||||
| 	return isinstance(value, m.UlohaZadaniNode) | ||||
| 
 | ||||
| @register.filter | ||||
| def isPohadka(value): | ||||
|     return isinstance(value, m.PohadkaNode) | ||||
| 	return isinstance(value, m.PohadkaNode) | ||||
| 
 | ||||
| @register.filter | ||||
| def isReseni(value): | ||||
| 	return False | ||||
| #	return isinstance(value, m.OtisteneReseniNode) | ||||
| 
 | ||||
| @register.filter | ||||
| def isOrgText(value): | ||||
| 	return False | ||||
| #	return isinstance(value, m.OrgTextNode) | ||||
| 
 | ||||
| 
 | ||||
| ###  | ||||
| 
 | ||||
| #@register.filter | ||||
| #def isOtisteneReseniNode(value): | ||||
| #    return isinstance(value, m.OtisteneReseniNode) | ||||
| #def podvesitelneNody(value): | ||||
| #	if isText() | ||||
| 
 | ||||
| @register.filter | ||||
| def deletable(value): | ||||
| 	if isTemaVCisle(value): | ||||
| 		return True | ||||
| 	if isOrgText(value): | ||||
| 		return True | ||||
| 	if isReseni(value): | ||||
| 		return True | ||||
| 	if isUlohaZadani(value): | ||||
| 		return True | ||||
| 	if isUlohaVzorak(value): | ||||
| 		return True | ||||
| 	if isCast(value): | ||||
| 		return True | ||||
| 	if isText(value): | ||||
| 		return True | ||||
| 	return False | ||||
| 
 | ||||
| @register.filter | ||||
| def editableSiblings(value): | ||||
| 	if isCast(value.node): | ||||
| 		return True | ||||
| 	if isText(value.node): | ||||
| 		return True | ||||
| 	if isReseni(value.node) and value.tema_in_path: | ||||
| 		return True | ||||
| 	if isUlohaZadani(value.node) and value.tema_in_path: | ||||
| 		return True | ||||
| 	if isUlohaVzorak(value.node) and value.tema_in_path: | ||||
| 		return True | ||||
| 	return False | ||||
| 
 | ||||
| @register.filter | ||||
| def editableChildren(value): | ||||
| 	if isRocnik(value.node): | ||||
| 		return False | ||||
| 	if isCislo(value.node): | ||||
| 		return False | ||||
| 	if isText(value.node): | ||||
| 		return False | ||||
| 	return True | ||||
| 
 | ||||
| @register.filter | ||||
| def textOnlySubtree(value): | ||||
| 	text_only = True | ||||
| 	if isText(value.node): | ||||
| 		return True | ||||
| 	if not isCast(value.node): | ||||
| 		return False | ||||
| 	for ch in value.children: | ||||
| 		if not textOnlySubtree(ch): | ||||
| 			return False | ||||
| 	return True | ||||
| 
 | ||||
| def canPodvesit(obj,new_parent): | ||||
| 	if isCast(new_parent.node): | ||||
| #		print("Lze",obj,new_parent) | ||||
| 		return True | ||||
| 	if textOnlySubtree(obj): | ||||
| #		print("Lze",obj,new_parent) | ||||
| 		return True | ||||
| 	return False | ||||
| 
 | ||||
| @register.filter | ||||
| def canPodvesitZa(value): | ||||
| 	if not value.index or value.index+1 >= len(value.parent.children): | ||||
| 		return False | ||||
| 	new_parent = value.parent.children[value.index+1] | ||||
| 	return canPodvesit(value,new_parent) | ||||
| 
 | ||||
| @register.filter | ||||
| def canPodvesitPred(value): | ||||
| 	if not value.index or value.index <= 0: | ||||
| 		return False | ||||
| 	new_parent = value.parent.children[value.index-1] | ||||
| 	return canPodvesit(value,new_parent) | ||||
| 
 | ||||
| 
 | ||||
| class NodeTypes(Enum): | ||||
| 	ROCNIK = ('rocnikNode','Ročník') | ||||
| 	CISLO = ('cisloNode', 'Číslo') | ||||
| 	MEZICISLO = ('mezicisloNode', 'Mezičíslo') | ||||
| 	CAST = ('castNode', 'Část') | ||||
| 	TEXT = ('textNode', 'Text') | ||||
| 	TEMAVCISLE = ('temaVCisleNode', 'Téma v čísle') | ||||
| 	RESENI = ('reseniNode','Řešení') | ||||
| 	ULOHAZADANI = ('ulohaZadaniNode','Zadání') | ||||
| 	ULOHAVZORAK = ('ulohaVzorakNode','Vzorák') | ||||
| 	POHADKA = ('pohadkaNode','Pohádka') | ||||
| 	ORGTEXT = ('orgText','Orgtext') | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| @register.simple_tag | ||||
| def appendableChildren(value): | ||||
| 	print(value) | ||||
| 	print(value.node) | ||||
| 	print(isUlohaZadani(value.node)) | ||||
| 	if isTemaVCisle(value.node): | ||||
| 		return (NodeTypes.RESENI.value[0], | ||||
| 			NodeTypes.ULOHAZADANI.value[0], | ||||
| 			NodeTypes.ULOHAVZORAK.value[0], | ||||
| 			NodeTypes.CAST.value[0], | ||||
| 			NodeTypes.TEXT.value[0], | ||||
| 			) | ||||
| 	if isOrgText(value.node) or isReseni(value.node) or isUlohaZadani(value.node) or isUlohaVzorak(value.node): | ||||
| 		print("Text/Cast") | ||||
| 		return (NodeTypes.CAST.value[0], | ||||
| 			NodeTypes.TEXT.value[0], | ||||
| 			) | ||||
| 	if isCast(value.node): | ||||
| 		return appendableChildren(value.parent) | ||||
| 	return [] | ||||
| 
 | ||||
| @register.simple_tag | ||||
| def canAppendReseni(value): | ||||
| 	if isTemaVCisle(value.node): | ||||
| 		return True | ||||
| 	if isCast(value.node): | ||||
| 		return canAppendReseni(value.parent) | ||||
| 	return False | ||||
| 
 | ||||
| @register.simple_tag | ||||
| def canAppendUlohaZadani(value): | ||||
| 	if isTemaVCisle(value.node): | ||||
| 		return True | ||||
| 	if isCast(value.node): | ||||
| 		return canAppendUlohaZadani(value.parent) | ||||
| 	return False | ||||
| 
 | ||||
| @register.simple_tag | ||||
| def canAppendUlohaVzorak(value): | ||||
| 	if isTemaVCisle(value.node): | ||||
| 		return True | ||||
| 	if isCast(value.node): | ||||
| 		return canAppendUlohaVzorak(value.parent) | ||||
| 	return False | ||||
| 
 | ||||
| @register.simple_tag | ||||
| def canAppendCast(value): | ||||
| 	if isTemaVCisle(value.node) or isOrgText(value.node) or isReseni(value.node) or isUlohaZadani(value.node) or isUlohaVzorak(value.node): | ||||
| 		return True | ||||
| 	if isCast(value.node): | ||||
| 		return canAppendCast(value.parent) | ||||
| 	return False | ||||
| 
 | ||||
| @register.simple_tag | ||||
| def canAppendText(value): | ||||
| 	if isTemaVCisle(value.node) or isOrgText(value.node) or isReseni(value.node) or isUlohaZadani(value.node) or isUlohaVzorak(value.node): | ||||
| 		return True | ||||
| 	if isCast(value.node): | ||||
| 		return canAppendText(value.parent) | ||||
| 	return False | ||||
| 
 | ||||
| #@register.filter | ||||
| #def is(value): | ||||
| #	return | ||||
| # | ||||
| #			NodeTypes..value, | ||||
| #@register.filter | ||||
| #def is(value): | ||||
| #	return | ||||
| 
 | ||||
|  |  | |||
|  | @ -140,7 +140,7 @@ def gen_resitele(rnd, osoby, skoly): | |||
| 				x += 1 | ||||
| 				os.user = user | ||||
| 				os.save() | ||||
| 			os.user.user_permissions.add(resitel_perm) | ||||
| 				os.user.user_permissions.add(resitel_perm) | ||||
| 			resitele.append(Resitel.objects.create(osoba=os, skola=rnd.choice(skoly), | ||||
| 				rok_maturity=rnd.randint(2019, 2029), | ||||
| 				zasilat=rnd.choice(Resitel.ZASILAT_CHOICES)[0])) | ||||
|  | @ -199,7 +199,7 @@ def gen_organizatori(rnd, osoby, last_rocnik): | |||
| 				x += 1 | ||||
| 				os.user = user | ||||
| 				os.save() | ||||
| 			os.user.user_permissions.add(org_perm) | ||||
| 				os.user.user_permissions.add(org_perm) | ||||
| 			organizatori.append(Organizator.objects.create(osoba=os, | ||||
| 				organizuje_od=od, organizuje_do=do, strucny_popis_organizatora = popis_orga)) | ||||
| 	return organizatori | ||||
|  | @ -244,8 +244,8 @@ def gen_zadani_ulohy(rnd, cisla, organizatori, pocet_oboru, poradi_cisla, poradi | |||
| 		na_web = text, | ||||
| 		do_cisla = text, | ||||
| 	) | ||||
| 	zad = TextNode.objects.create(text = text_zadani) | ||||
| 	uloha_zadani = UlohaZadaniNode.objects.create(uloha = p, first_child = zad) | ||||
| 	zad = TextNode.objects.create(text = text_zadani, root = p.cislo_zadani.rocnik.rocniknode) | ||||
| 	uloha_zadani = UlohaZadaniNode.objects.create(uloha = p, first_child = zad, root = p.cislo_zadani.rocnik.rocniknode) | ||||
| 	p.ulohazadaninode = uloha_zadani | ||||
| 	otec_syn(cisla[poradi_cisla-2-1].cislonode, uloha_zadani) | ||||
| 
 | ||||
|  | @ -266,8 +266,8 @@ def gen_vzoroveho_reseni_ulohy(rnd, organizatori, uloha, pocet_opravovatelu): | |||
| 		na_web = obsah, | ||||
| 		do_cisla = obsah | ||||
| 	) | ||||
| 	vzorak = TextNode.objects.create(text = text_vzoraku) | ||||
| 	uloha_vzorak = UlohaVzorakNode.objects.create(uloha = uloha, first_child = vzorak) | ||||
| 	vzorak = TextNode.objects.create(text = text_vzoraku, root = uloha.cislo_zadani.rocnik.rocniknode) | ||||
| 	uloha_vzorak = UlohaVzorakNode.objects.create(uloha = uloha, first_child = vzorak, root = uloha.cislo_zadani.rocnik.rocniknode) | ||||
| 	uloha.ulohavzoraknode = uloha_vzorak | ||||
| 
 | ||||
| 	uloha.opravovatele.set(rnd.sample(organizatori, pocet_opravovatelu)) | ||||
|  | @ -434,7 +434,7 @@ def gen_cisla(rnd, rocniky): | |||
| 				datum_deadline=deadline, | ||||
| 				verejne_db=True | ||||
| 			) | ||||
| 			node2 = CisloNode.objects.create(cislo = cislo, succ = node) | ||||
| 			node2 = CisloNode.objects.create(cislo = cislo, succ = node, root=rocnik.rocniknode) | ||||
| 			cislo.save() | ||||
| 			node = node2 | ||||
| 			if otec: | ||||
|  | @ -473,54 +473,54 @@ def gen_dlouhe_tema(rnd, organizatori, rocnik, nazev, obor, kod): | |||
| 	for cislo in cisla: | ||||
| 		# Přidáme TemaVCisleNode do daného čísla | ||||
| 		cislo_node = cislo.cislonode	 | ||||
| 		tema_cislo_node = TemaVCisleNode.objects.create(tema = tema) | ||||
| 		tema_cislo_node = TemaVCisleNode.objects.create(tema = tema, root = cislo_node.root) | ||||
| 		insert_last_child(cislo_node, tema_cislo_node) | ||||
| 		 | ||||
| 		# Přidávání obsahu do čísla | ||||
| 		cast_node = m.CastNode.objects.create(nadpis = "Příspěvek k číslu {}".format(cislo.kod)) | ||||
| 		cast_node = m.CastNode.objects.create(nadpis = "Příspěvek k číslu {}".format(cislo.kod), root=cislo_node.root) | ||||
| 		add_first_child(tema_cislo_node, cast_node) | ||||
| 	 | ||||
| 		text_node = TextNode.objects.create(text = get_text()) | ||||
| 		text_node = TextNode.objects.create(text = get_text(), root=cislo_node.root) | ||||
| 		add_first_child(cast_node, text_node) | ||||
| 
 | ||||
| 		cast_node2 = m.CastNode.objects.create(nadpis = "První podproblém") | ||||
| 		cast_node2 = m.CastNode.objects.create(nadpis = "První podproblém", root=cislo_node.root) | ||||
| 		add_first_child(text_node, cast_node2) | ||||
| 		 | ||||
| 		text_node2 = TextNode.objects.create(text = get_text()) | ||||
| 		text_node2 = TextNode.objects.create(text = get_text(), root=cislo_node.root) | ||||
| 		add_first_child(cast_node2, text_node2) | ||||
| 		 | ||||
| 		cast_node3 = m.CastNode.objects.create(nadpis = "Druhý podproblém") | ||||
| 		cast_node3 = m.CastNode.objects.create(nadpis = "Druhý podproblém", root=cislo_node.root) | ||||
| 		add_first_child(text_node2, cast_node3) | ||||
| 
 | ||||
| 		text_node3 = TextNode.objects.create(text = get_text()) | ||||
| 		text_node3 = TextNode.objects.create(text = get_text(), root=cislo_node.root) | ||||
| 		add_first_child(cast_node3, text_node3) | ||||
| 
 | ||||
| 		cast_node4 = m.CastNode.objects.create(nadpis = "Třetí podproblém") | ||||
| 		cast_node4 = m.CastNode.objects.create(nadpis = "Třetí podproblém", root=cislo_node.root) | ||||
| 		add_first_child(text_node3, cast_node4)	 | ||||
| 
 | ||||
| 		text_node4 = TextNode.objects.create(text = get_text()) | ||||
| 		text_node4 = TextNode.objects.create(text = get_text(), root=cislo_node.root) | ||||
| 		add_first_child(cast_node3, text_node4) | ||||
| 		 | ||||
| 		cast_node3a = m.CastNode.objects.create(nadpis = "Podproblém paralelní s " | ||||
| 					"druhým podproblémem") | ||||
| 					"druhým podproblémem", root=cislo_node.root) | ||||
| 		cast_node3.succ = cast_node3a | ||||
| 		cast_node3.save() | ||||
| 
 | ||||
| 		text_node3a = TextNode.objects.create(text = get_text()) | ||||
| 		text_node3a = TextNode.objects.create(text = get_text(), root=cislo_node.root) | ||||
| 		add_first_child(cast_node3a, text_node3a) | ||||
| 
 | ||||
| 		# Občas přidáme mezičíslo | ||||
| 		if rnd.randint(1, 3) == 1: | ||||
| 			create_node_after(cislo_node, m.MezicisloNode)		 | ||||
| 			create_node_after(cislo_node, m.MezicisloNode, root=cislo_node.root) | ||||
| 			mezicislo_node = cislo_node.succ | ||||
| 	 | ||||
| 			cast_node_mezicislo = m.CastNode.objects.create( | ||||
| 					nadpis = "Příspěvek k mezičíslu".format(cislo.kod)) | ||||
| 					nadpis = "Příspěvek k mezičíslu".format(cislo.kod), root=cislo_node.root) | ||||
| 			add_first_child(mezicislo_node, cast_node_mezicislo) | ||||
| 
 | ||||
| 			odstavec = lorem.paragraph() | ||||
| 			text_mezicislo = Text.objects.create(na_web = odstavec, do_cisla = odstavec)	 | ||||
| 			text_node_mezicislo = TextNode.objects.create(text = text_mezicislo) | ||||
| 			text_mezicislo = Text.objects.create(na_web = odstavec, do_cisla = odstavec) | ||||
| 			text_node_mezicislo = TextNode.objects.create(text = text_mezicislo, root=cislo_node.root) | ||||
| 			add_first_child(cast_node_mezicislo, text_node_mezicislo) | ||||
| 
 | ||||
| 	return tema | ||||
|  | @ -564,7 +564,7 @@ def gen_temata(rnd, rocniky, rocnik_cisla, organizatori): | |||
| 
 | ||||
| 			# Vyrobíme TemaVCisleNody pro obsah | ||||
| 			for i in range(zacatek_tematu, konec_tematu+1): | ||||
| 				node = TemaVCisleNode.objects.create(tema = t) | ||||
| 				node = TemaVCisleNode.objects.create(tema = t,root=rocnik.rocniknode) | ||||
| 				# FIXME: Není to off-by-one? | ||||
| 				otec = cisla[i-1].cislonode | ||||
| 				otec_syn(otec, node) | ||||
|  | @ -621,8 +621,8 @@ def gen_ulohy_tematu(rnd, organizatori, tema, kod, cislo, cislo_se_vzorakem): | |||
| 		na_web = obsah, | ||||
| 		do_cisla = obsah, | ||||
| 		) | ||||
| 	zad = TextNode.objects.create(text = text_zadani) | ||||
| 	uloha_zadani = UlohaZadaniNode.objects.create(uloha=uloha, first_child = zad) | ||||
| 	zad = TextNode.objects.create(text = text_zadani, 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 | ||||
| 
 | ||||
| 	return uloha, uloha_zadani | ||||
|  |  | |||
|  | @ -1,7 +1,9 @@ | |||
| from django.core.exceptions import ObjectDoesNotExist | ||||
| from django.db import transaction | ||||
| # NOTE: node.prev a node.succ jsou implementovány přímo v models.TreeNode | ||||
| # TODO: Všechny tyto funkce se naivně spoléhají na to, že jako parametr dostanou nějaký TreeNode (některé možná zvládnou i None) | ||||
| # TODO: Chceme, aby všechno nějak zvládlo None jako parametr. | ||||
| # TODO: Do nějakých consistency-checků přidat hledání polo-sirotků (kteří nesplňují invarianty, třeba nejsou dosažitelní a mají root, vyrábějí DAG, ...) | ||||
| 
 | ||||
| # Slouží k debugování pro rychlé získání představy o podobě podstromu pod tímto TreeNode. | ||||
| def print_tree(node,indent=0): | ||||
|  | @ -12,20 +14,31 @@ def print_tree(node,indent=0): | |||
| 	if node.succ: | ||||
| 		print_tree(node.succ, indent=indent) | ||||
| 
 | ||||
| def is_orphan(node): | ||||
| 	""" Zjišťuje, jestli už je daný Node někde pověšený či nikoli. """ | ||||
| 	if safe_father_of_first(node) is None and safe_pred(node) is None: | ||||
| 		return True | ||||
| 	else: | ||||
| 		return False | ||||
| 
 | ||||
| # Django je trošku hloupé, takže node.prev nevrací None, ale hází django.core.exceptions.ObjectDoesNotExist | ||||
| def safe_pred(node): | ||||
| 	if node is None: | ||||
| 		return None | ||||
| 	try: | ||||
| 		return node.prev | ||||
| 	except ObjectDoesNotExist: | ||||
| 		return None | ||||
| 
 | ||||
| # FIXME: Proč????? | ||||
| def safe_succ(node): | ||||
| 	try: | ||||
| 		return node.succ | ||||
| 	except ObjectDoesNotExist: | ||||
| 		return None | ||||
| 
 | ||||
| def safe_father_of_first(node): | ||||
| 	if node is None: | ||||
| 		return None | ||||
| 	first = first_brother(node) | ||||
| 	try: | ||||
| 		return first.father_of_first | ||||
| 	except ObjectDoesNotExist: | ||||
| 		return None | ||||
| 
 | ||||
| def first_brother(node): | ||||
| 	if node is None: | ||||
| 		return None | ||||
|  | @ -34,15 +47,8 @@ def first_brother(node): | |||
| 		brother = safe_pred(brother) | ||||
| 	return brother | ||||
| 
 | ||||
| # A to samé pro .father_of_first | ||||
| def safe_father_of_first(node): | ||||
| 	first = first_brother(node) | ||||
| 	try: | ||||
| 		return first.father_of_first | ||||
| 	except ObjectDoesNotExist: | ||||
| 		return None | ||||
| 
 | ||||
| ## Rodinné vztahy | ||||
| # Tohle se teď zrovna k None chová správně, ale je potřeba na to dávat pozor | ||||
| def get_parent(node): | ||||
| 	# Nejdřív získáme prvního potomka... | ||||
| 	while safe_pred(node) is not None: | ||||
|  | @ -51,14 +57,33 @@ def get_parent(node): | |||
| 	return safe_father_of_first(node) | ||||
| 
 | ||||
| def get_last_child(node): | ||||
| 	if node is None: | ||||
| 		return None | ||||
| 	first = node.first_child | ||||
| 	if first is None: | ||||
| 		return None | ||||
| 	else: | ||||
| 		return last_brother(first) | ||||
| 
 | ||||
| def is_orphan(node): | ||||
| 	""" Zjišťuje, jestli už je daný Node někde pověšený či nikoli. """ | ||||
| 	# None jsem se rozhodl, že sirotek není | ||||
| 	if node is None: | ||||
| 		return False | ||||
| 	if get_parent(node) is None: | ||||
| 		if node.succ is not None or safe_pred(node) is not None or safe_father_of_first(node) is not None or node.root is not None: | ||||
| 			import logging | ||||
| 			logger = logging.getLogger(__name__) | ||||
| 			# Error = pošle mail :-) | ||||
| 			logger.error(f"Node-sirotek s id {node.id} má rodinné vztahy (Node: {node})") | ||||
| 		return True | ||||
| 	else: | ||||
| 		return False | ||||
| 
 | ||||
| # Obecný next: další Node v "the-right-order" pořadí (já, pak potomci, pak sousedé) | ||||
| def general_next(node): | ||||
| 	if node is None: | ||||
| 		return None | ||||
| 	# Máme potomka? | ||||
| 	if node.first_child is not None: | ||||
| 		return node.first_child | ||||
|  | @ -71,6 +96,8 @@ def general_next(node): | |||
| 	return node.succ | ||||
| 
 | ||||
| def last_brother(node): | ||||
| 	if node is None: | ||||
| 		return None | ||||
| 	while node.succ is not None: | ||||
| 		node = node.succ | ||||
| 	return node | ||||
|  | @ -78,6 +105,7 @@ def last_brother(node): | |||
| def general_prev(node): | ||||
| 	# Předchůdce je buď rekurzivně poslední potomek předchůdce, nebo náš otec. | ||||
| 	# Otce vyřešíme nejdřív: | ||||
| 	# Tady se ošetří node=None samo | ||||
| 	if safe_pred(node) is None: | ||||
| 		return safe_father_of_first(node) | ||||
| 	pred = safe_pred(node) | ||||
|  | @ -95,12 +123,16 @@ def me_and_right_brothers(node): | |||
| 		current = current.succ | ||||
| 
 | ||||
| def right_brothers(node): | ||||
| 	if node is None: | ||||
| 		return | ||||
| 	generator = me_and_right_brothers(node.succ) | ||||
| 	for item in generator: | ||||
| 		yield item | ||||
| 
 | ||||
| # Generátor všech sourozenců (vč. sám sebe) | ||||
| def all_brothers(node): | ||||
| 	if node is None: | ||||
| 		return | ||||
| 	# Najdeme prvního bratra | ||||
| 	fb = first_brother(node) | ||||
| 	marb = me_and_right_brothers(fb) | ||||
|  | @ -108,6 +140,8 @@ def all_brothers(node): | |||
| 		yield cur | ||||
| 	 | ||||
| def all_proper_brothers(node): | ||||
| 	if node is None: | ||||
| 		return | ||||
| 	all = all_brothers(node) | ||||
| 	for br in all: | ||||
| 		if br is node: | ||||
|  | @ -116,12 +150,16 @@ def all_proper_brothers(node): | |||
| 
 | ||||
| def all_children(node): | ||||
| 	""" Generátor všech potomků zadaného Node. """ | ||||
| 	if node is None: | ||||
| 		return | ||||
| 	brothers = all_brothers(node.first_child) | ||||
| 	for br in brothers: | ||||
| 		yield br | ||||
| 
 | ||||
| def all_children_of_type(node, type): | ||||
| 	""" Generuje všechny potomky daného Node a daného typu. """ | ||||
| 	if node is None: | ||||
| 		return | ||||
| 	brothers = all_brothers(node.first_child) | ||||
| 	for br in brothers: | ||||
| 		if isinstance(br, type): | ||||
|  | @ -130,6 +168,8 @@ def all_children_of_type(node, type): | |||
| # Generátor následníků v "the-right-order" | ||||
| # Bez tohoto vrcholu | ||||
| def all_following(node): | ||||
| 	if node is None: | ||||
| 		return | ||||
| 	current = general_next(node) | ||||
| 	while current is not None: | ||||
| 		yield current | ||||
|  | @ -139,12 +179,16 @@ def all_following(node): | |||
| # Najdi dalšího bratra nějakého typu, nebo None. | ||||
| # hledá i podtřídy, i.e. get_next_brother_of_type(neco, TreeNode) je prostě succ. | ||||
| def get_next_brother_of_type(node, type): | ||||
| 	if node is None: | ||||
| 		return | ||||
| 	for current in right_brothers(node): | ||||
| 		if isinstance(current, type): | ||||
| 			return current | ||||
| 	return None | ||||
| 	 | ||||
| def get_prev_brother_of_type(node, type): | ||||
| 	if node is None: | ||||
| 		return | ||||
| 	# Na tohle není rozumný generátor, ani ho asi nechceme, prostě to implementujeme cyklem. | ||||
| 	current = node | ||||
| 	while safe_pred(current) is not None: | ||||
|  | @ -155,6 +199,8 @@ def get_prev_brother_of_type(node, type): | |||
| 
 | ||||
| # Totéž pro "the-right-order" pořadí | ||||
| def get_next_node_of_type(node, type): | ||||
| 	if node is None: | ||||
| 		return | ||||
| 	for cur in all_folowing(node): | ||||
| 		if isinstance(cur, type): | ||||
| 			return cur | ||||
|  | @ -162,6 +208,8 @@ def get_next_node_of_type(node, type): | |||
| 
 | ||||
| def get_prev_node_of_type(node, type): | ||||
| 	# Na tohle není rozumný generátor, ani ho asi nechceme, prostě to implementujeme cyklem. | ||||
| 	if node is None: | ||||
| 		return | ||||
| 	current = node | ||||
| 	while general_prev(current) is not None: | ||||
| 		current = general_prev(current) | ||||
|  | @ -171,20 +219,38 @@ def get_prev_node_of_type(node, type): | |||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| # Exception, kterou některé metody při špatném použití mohou házet | ||||
| # Hlavní důvod je možnost informovat o selhání, aby se příslušný problém dal zobrazit na frontendu, | ||||
| class TreeLibError(RuntimeError): | ||||
| 	pass | ||||
| 
 | ||||
| 
 | ||||
| # Editace stromu: | ||||
| def create_node_after(predecessor, type, **kwargs): | ||||
| 	from seminar.models import TreeNode | ||||
| 	if predecessor is None: | ||||
| 		raise TreeLibError("Nelze vyrábět sirotky! (predecessor=None)") | ||||
| 	if not issubclass(type, TreeNode): | ||||
| 		raise TreeLibError("Nový node není node!") | ||||
| 	new_node = type.objects.create(**kwargs) | ||||
| 	new_node.root = predecessor.root | ||||
| 	new_node.save() | ||||
| 	succ = predecessor.succ | ||||
| 	predecessor.succ = new_node | ||||
| 	predecessor.save() | ||||
| 	new_node.succ = succ | ||||
| 	new_node.save() | ||||
| 	return new_node | ||||
| 
 | ||||
| # Vyrábí prvního syna, ostatní nalepí za (existují-li) | ||||
| def create_child(parent, type, **kwargs): | ||||
| 	from seminar.models import TreeNode | ||||
| 	if parent is None: | ||||
| 		raise TreeLibError("Nelze vyrábět sirotky! (parent=None)") | ||||
| 	if not issubclass(type, TreeNode): | ||||
| 		raise TreeLibError("Nový node není node!") | ||||
| 	new_node = type.objects.create(**kwargs) | ||||
| 	new_node.root = parent.root | ||||
| 	new_node.save() | ||||
| 	orig_child = parent.first_child | ||||
| 	parent.first_child = new_node | ||||
|  | @ -193,8 +259,11 @@ def create_child(parent, type, **kwargs): | |||
| 		# Přidáme původního prvního syna jako potomka nového vrcholu | ||||
| 		new_node.succ = orig_child | ||||
| 		new_node.save() | ||||
| 	return new_node | ||||
| 
 | ||||
| def insert_last_child(parent, node): | ||||
| 	if parent is None: | ||||
| 		raise TreeLibError("Nelze vyrábět sirotky! (parent=None)") | ||||
| 	""" Zadaný Node přidá jako posledního potomka otce. """ | ||||
| 	last = get_last_child(parent) | ||||
| 	if not is_orphan(node): | ||||
|  | @ -213,6 +282,11 @@ def insert_last_child(parent, node): | |||
| 		last.save() | ||||
| 
 | ||||
| def create_node_before(successor, type, **kwargs): | ||||
| 	from seminar.models import TreeNode | ||||
| 	if successor is None: | ||||
| 		raise TreeLibError("Nelze vyrábět sirotky! (successor=None)") | ||||
| 	if not issubclass(type, TreeNode): | ||||
| 		raise TreeLibError("Nový node není node!") | ||||
| 	if safe_pred(successor) is not None: | ||||
| 		# Easy: přidáme za předchůdce | ||||
| 		create_node_after(successor.prev, type, **kwargs) | ||||
|  | @ -223,36 +297,18 @@ def create_node_before(successor, type, **kwargs): | |||
| 		create_child(successor.father_of_first, type, **kwargs) | ||||
| 	# Teď už easy: Jsme sirotci, takže se vyrobíme a našeho následníka si přidáme jako succ | ||||
| 	new = type.objects.create(**kwargs) | ||||
| 	new.root = successor.root | ||||
| 	new.succ = successor | ||||
| 	new.save() | ||||
| 	return new | ||||
| 
 | ||||
| 
 | ||||
| # ValueError, pokud je (aspoň) jeden parametr None | ||||
| def swap(node, other): | ||||
| 	raise NotImplementedError("YAGNI (You aren't gonna need it).") | ||||
| 
 | ||||
| # Exception, kterou některé metody při špatném použití mohou házet | ||||
| # Hlavní důvod je možnost informovat o selhání, aby se příslušný problém dal zobrazit na frontendu, | ||||
| class TreeLibError(RuntimeError): | ||||
| 	pass | ||||
| 
 | ||||
| def swap_pred(node): | ||||
| 	if node is None: | ||||
| 		raise TreeLibError("Nelze přesunout None. Tohle by se nemělo stát.") | ||||
| 	pred = safe_pred(node) | ||||
| 	if pred is None: | ||||
| 		raise TreeLibError("Nelze posunout vlevo, není tam žádný další uzel.") | ||||
| 	pre_pred = safe_pred(pred) | ||||
| 	succ = node.succ | ||||
| 
 | ||||
| 	if pre_pred is not None: | ||||
| 		pre_pred.succ = node | ||||
| 		pre_pred.save() | ||||
| 	node.succ = pred | ||||
| 	node.save() | ||||
| 	pred.succ = succ | ||||
| 	pred.save() | ||||
| 
 | ||||
| @transaction.atomic | ||||
| def swap_succ(node): | ||||
| 	if node is None: | ||||
| 		raise TreeLibError("Nelze přesunout None. Tohle by se nemělo stát.") | ||||
|  | @ -263,63 +319,147 @@ def swap_succ(node): | |||
| 	post_succ = succ.succ | ||||
| 
 | ||||
| 	if pred is not None: | ||||
| 		pred.succ = succ | ||||
| 		pred.succ = None | ||||
| 		pred.save() | ||||
| 
 | ||||
| 	# Nemame predchudce -> je potreba upravit otce | ||||
| 	father = safe_father_of_first(node) | ||||
| 	if pred is None and father is not None: # Mame otce | ||||
| 		father.first_child = succ | ||||
| 		father.save() | ||||
| 
 | ||||
| 
 | ||||
| 	succ.succ = node | ||||
| 	succ.save() | ||||
| 	node.succ = post_succ | ||||
| 	node.save() | ||||
| 	if pred is not None: | ||||
| 		pred.succ = succ | ||||
| 		pred.save() | ||||
| 
 | ||||
| @transaction.atomic | ||||
| def swap_pred(node): | ||||
| 	if node is None: | ||||
| 		raise TreeLibError("Nelze přesunout None. Tohle by se nemělo stát.") | ||||
| 	pred = safe_pred(node) | ||||
| 	if pred is None: | ||||
| 		raise TreeLibError("Nelze posunout vlevo, není tam žádný další uzel.") | ||||
| 	return swap_succ(pred) | ||||
| 
 | ||||
| # Rotace stromu | ||||
| # Dokumentace viz wiki: | ||||
| # (lower bude jednoduchá rotace, ne mega, existence jednoduché rotace mi došla až po nakreslení obrázku) | ||||
| def raise_node(node): | ||||
| 	if node is None: | ||||
| 		raise TreeLibError("Nelze přesunout None. Tohle by se nemělo stát.") | ||||
| 	# Pojmenování viz WIKI (as of 2020-03-19 01:33:44 GMT+1) | ||||
| 	# FIXME: Velmi naivní, chybí error checky | ||||
| 	# FIXME: Trochu méně naivní, nevěřím tomu, prosím otestovat | ||||
| 	D = node | ||||
| 	C = get_parent(D) | ||||
| 	E = C.succ | ||||
| 	subtree4_head = D.first_child | ||||
| 	subtree4_tail = last_brother(subtree4_head) | ||||
| 	subtree3P_head = D.succ | ||||
| 	subtree3L_head = C.first_child | ||||
| 	subtree3L_tail = safe_pred(D) | ||||
| 	if C is None: | ||||
| 		raise TreeLibError("Nelze povýšit vrchol, jenž nemá otce.") | ||||
| 	E = C.succ	# Může být None a ničemu to nevadí | ||||
| 	subtree4_head = D.first_child	# Může být None, ale pak se musí z 3P udělat přímo potomek D | ||||
| 	subtree4_tail = last_brother(subtree4_head)	# Měl by být None právě když je sub4_head=None | ||||
| 	subtree3P_head = D.succ	# Může být None a ničemu to nevadí | ||||
| 	subtree3L_tail = safe_pred(D)	# Pokud je None, D je první syn C a C má tedy skončit bezdětný | ||||
| 	 | ||||
| 	# Prostor pro motlitbu... | ||||
| 	pass | ||||
| 	 | ||||
| 	# Amen. | ||||
| 	C.succ = D | ||||
| 	# Teď už nesmíme spadnout, protože jinak skončíme se stromem v nekonzistentním stavu | ||||
| 	C.succ = D	# Nespadne | ||||
| 	C.save() | ||||
| 	D.succ = E | ||||
| 	D.succ = E	# Nespadne | ||||
| 	D.save() | ||||
| 	subtree3L_tail.succ = None | ||||
| 	subtree3L_tail.save() | ||||
| 	subtree4_tail.succ = subtree3P.head | ||||
| 	subtree4_tail.save() | ||||
| 
 | ||||
| 	if subtree3L_tail is not None: | ||||
| 		subtree3L_tail.succ = None | ||||
| 		subtree3L_tail.save() | ||||
| 	else: | ||||
| 		assert C.first_child is D | ||||
| 		C.first_child = None | ||||
| 		C.save() | ||||
| 
 | ||||
| 	if subtree4_tail is not None: | ||||
| 		subtree4_tail.succ = subtree3P_head | ||||
| 		subtree4_tail.save() | ||||
| 	else: | ||||
| 		D.first_child = subtree3P_head | ||||
| 		D.save() | ||||
| 
 | ||||
| 	# To by mělo být všechno... | ||||
| 
 | ||||
| # (lower bude jednoduchá rotace, ne mega, existence jednoduché rotace mi došla až po nakreslení obrázku) | ||||
| def lower_node(node): | ||||
| 	if node is None: | ||||
| 		raise TreeLibError("Nelze přesunout None. Tohle by se nemělo stát.") | ||||
| 	# Pojmenování viz WIKI (as of 2020-03-19 01:33:44 GMT+1) | ||||
| 	# FIXME: Velmi naivní, chybí error checky | ||||
| 	# FIXME: Trochu naivní, prosím otestovat | ||||
| 	C = node | ||||
| 	D = C.succ | ||||
| 	D = C.succ	# Může být None a ničemu to nevadí | ||||
| 	B = safe_pred(C) | ||||
| 	subtree2_head = B.first_child | ||||
| 	subtree2_tail = last_brother(subtree2_head) | ||||
| 	if B is None: | ||||
| 		raise TreeLibError("Nelze ponížit prvního syna (není pod co)") | ||||
| 	subtree2_head = B.first_child	# Je-li None, pak se z C má stát první syn | ||||
| 	subtree2_tail = last_brother(subtree2_head)	# None iff head=None, doufám | ||||
| 	 | ||||
| 	# Prostor pro motlitbu... | ||||
| 	pass | ||||
| 	 | ||||
| 	# Amen. | ||||
| 	B.succ = D | ||||
| 	# Teď už nesmíme spadnout, protože jinak skončíme se stromem v nekonzistentním stavu | ||||
| 	B.succ = D	# Nespadne | ||||
| 	B.save() | ||||
| 	subtree2_tail.succ = C | ||||
| 	subtree2_tail.save() | ||||
| 	if subtree2_tail is not None: | ||||
| 		subtree2_tail.succ = C | ||||
| 		subtree2_tail.save() | ||||
| 	else: | ||||
| 		assert subtree2_head is None | ||||
| 		B.first_child = C | ||||
| 		B.save() | ||||
| 
 | ||||
| 	# To by mělo být všechno... | ||||
| 
 | ||||
| def disconnect_node(node): | ||||
| 	#FIXME: dodělat odstranění roota všem potomkům | ||||
| 	if node is None: | ||||
| 		raise TreeLibError("Nelze odpojit None. Tohle by se nemělo stát.") | ||||
| 
 | ||||
| 	print(f'My:{node}, predchudce:{safe_pred(node)}, naslednik:{safe_succ(node)}, otec:{safe_father_of_first(node)}') | ||||
| 	 | ||||
| 	# Jsme prvnim synem | ||||
| 	if safe_pred(node) is None: | ||||
| 		if safe_succ(node) is None: # Jsme jedinym synem - upravime otce (pokud mame) a odpojime se | ||||
| 			father = safe_father_of_first(node) | ||||
| 			if father is not None: | ||||
| 				father.first_child = None | ||||
| 				father.save() | ||||
| 			return  | ||||
| 
 | ||||
| 		else: # mame bratra | ||||
| 			swap_succ(node) # Staneme se neprvním synem, pokracujeme mimo if | ||||
| 
 | ||||
| 	# Jsme neprvním synem | ||||
| 	prev = node.prev | ||||
| 	prev.succ = node.succ | ||||
| 	node.succ = None | ||||
| 	node.save() | ||||
| 	clear_root(node) | ||||
| 	prev.save() | ||||
| 
 | ||||
| def clear_root(node): | ||||
| 	node.root = None | ||||
| 	node.save() | ||||
| 	if node.first_child: | ||||
| 		clear_root(node.first_child) | ||||
| 	if node.succ: | ||||
| 		clear_root(node.succ) | ||||
| 
 | ||||
| def set_root(node,root): | ||||
| 	node.root = root | ||||
| 	node.save() | ||||
| 	if node.first_child: | ||||
| 		clear_root(node.first_child) | ||||
| 	if node.succ: | ||||
| 		clear_root(node.succ) | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| from django.urls import path, include | ||||
| from django.urls import path, include, re_path | ||||
| from django.contrib.auth.decorators import login_required | ||||
| from . import views, export | ||||
| from .utils import org_required, resitel_required | ||||
|  | @ -20,6 +20,14 @@ urlpatterns = [ | |||
| 	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('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/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/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/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/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'), | ||||
| 
 | ||||
| 	# Soustredeni | ||||
|  | @ -50,7 +58,7 @@ urlpatterns = [ | |||
| 
 | ||||
| 	# Zadani | ||||
| 	path('zadani/aktualni/', views.AktualniZadaniView.as_view(), name='seminar_aktualni_zadani'), | ||||
| #	path('zadani/temata/', views.ZadaniTemataView, name='seminar_temata'), | ||||
| 	path('zadani/temata/', views.ZadaniTemataView, name='seminar_temata'), | ||||
| 	#path('zadani/vysledkova-listina/', views.ZadaniAktualniVysledkovkaView, name='seminar_vysledky'), | ||||
| 	path('stare-novinky/', views.StareNovinkyView.as_view(), name='stare_novinky'), | ||||
| 
 | ||||
|  | @ -149,6 +157,9 @@ urlpatterns = [ | |||
| 	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'), | ||||
| 
 | ||||
| 	re_path(r'^temp/vue/.*$',views.VueTestView.as_view(),name='vue_test_view'), | ||||
| 	path('temp/image_upload/', views.NahrajObrazekKTreeNoduView.as_view()), | ||||
| 
 | ||||
| 	path('', views.TitulniStranaView.as_view(), name='titulni_strana'), | ||||
| 	path('jak-resit/', views.JakResitView.as_view(), name='jak-resit'), | ||||
| 
 | ||||
|  |  | |||
|  | @ -148,16 +148,12 @@ def resi_v_rocniku(rocnik, cislo=None): | |||
| 
 | ||||
| 	if cislo is None: | ||||
| 		# filtrujeme pouze podle ročníku | ||||
| 		letosni_reseni = m.Reseni.objects.filter(hodnoceni__cislo_body__rocnik=rocnik) | ||||
| 		return m.Resitel.objects.filter(rok_maturity__gte=rocnik.druhy_rok(), | ||||
| 										reseni__hodnoceni__cislo_body__rocnik=rocnik).distinct() | ||||
| 	else:  # filtrujeme podle ročníku i čísla | ||||
| 		letosni_reseni = m.Reseni.objects.filter(hodnoceni__cislo_body__rocnik=rocnik, | ||||
| 												 hodnoceni__cislo_body__poradi__lte=cislo.poradi) | ||||
| 
 | ||||
| 	# vygenerujeme queryset řešitelů, co letos něco poslali | ||||
| 	letosni_resitele = m.Resitel.objects.none() | ||||
| 	for reseni in letosni_reseni: | ||||
| 		letosni_resitele = letosni_resitele | reseni.resitele.filter(rok_maturity__gte=rocnik.druhy_rok()) | ||||
| 	return letosni_resitele.distinct() | ||||
| 		return m.Resitel.objects.filter(rok_maturity__gte=rocnik.druhy_rok(), | ||||
| 										reseni__hodnoceni__cislo_body__rocnik=rocnik, | ||||
| 										reseni__hodnoceni__cislo_body__poradi__lte=cislo.poradi).distinct() | ||||
| 
 | ||||
| 
 | ||||
| def aktivniResitele(cislo, pouze_letosni=False): | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| from .views_all import * | ||||
| from .autocomplete import * | ||||
| from .views_rest import * | ||||
| from .odevzdavatko import * | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| # coding:utf-8 | ||||
| 
 | ||||
| 
 | ||||
| from django.shortcuts import get_object_or_404, render, redirect | ||||
| from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, JsonResponse | ||||
|  | @ -16,13 +16,19 @@ from django.contrib.auth import views as auth_views | |||
| from django.contrib.auth.models import User, Permission | ||||
| from django.contrib.auth.mixins import LoginRequiredMixin | ||||
| from django.db import transaction | ||||
| from django.core import serializers | ||||
| from django.core.exceptions import PermissionDenied | ||||
| from django.forms.models import model_to_dict | ||||
| 
 | ||||
| import seminar.models as s | ||||
| import seminar.models as m | ||||
| from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni, Organizator, Resitel, Novinky, Soustredeni_Ucastnici, Pohadka, Tema, Clanek, Osoba, Skola # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci | ||||
| #from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva | ||||
| from seminar import utils, treelib | ||||
| from seminar.forms import PrihlaskaForm, LoginForm, ProfileEditForm | ||||
| import seminar.forms as f | ||||
| import seminar.templatetags.treenodes as tnltt | ||||
| import seminar.views.views_rest as vr | ||||
| 
 | ||||
| from datetime import timedelta, date, datetime, MAXYEAR | ||||
| from django.utils import timezone | ||||
|  | @ -89,17 +95,142 @@ class ObalkovaniView(generic.ListView): | |||
| 		return context | ||||
| 
 | ||||
| class TNLData(object): | ||||
| 	def __init__(self,anode): | ||||
| 	def __init__(self,anode,parent=None, index=None): | ||||
| 		self.node = anode | ||||
| 		self.sernode = vr.TreeNodeSerializer(anode) | ||||
| 		self.children = [] | ||||
| 		self.parent = parent | ||||
| 		self.tema_in_path = False | ||||
| 		self.index = index | ||||
| 
 | ||||
| 		if parent: | ||||
| 			self.tema_in_path = parent.tema_in_path | ||||
| 		if isinstance(anode, m.TemaVCisleNode): | ||||
| 			self.tema_in_path = True | ||||
| 
 | ||||
| 	def add_edit_options(self): | ||||
| 		self.deletable = tnltt.deletable(self)  | ||||
| 		self.editable_siblings = tnltt.editableSiblings(self) | ||||
| 		self.editable_children = tnltt.editableChildren(self) | ||||
| 		self.text_only_subtree = tnltt.textOnlySubtree(self) | ||||
| 		self.can_podvesit_za = tnltt.canPodvesitZa(self) | ||||
| 		self.can_podvesit_pred = tnltt.canPodvesitPred(self) | ||||
| 		self.appendable_children = tnltt.appendableChildren(self) | ||||
| 		print("appChld",self.appendable_children) | ||||
| 		if self.parent: | ||||
| 			self.appendable_siblings = tnltt.appendableChildren(self.parent) | ||||
| 		else: | ||||
| 			self.appendable_siblings = [] | ||||
| 	@classmethod | ||||
| 	def public_above(cls, anode): | ||||
| 		""" Returns output of verejne for closest Rocnik, Cislo or Problem above. | ||||
| 		(All of them have method verejne.)""" | ||||
| 		parent = anode # chceme začít už od konkrétního node včetně | ||||
| 		while True: | ||||
| 			rocnik = isinstance(parent, s.RocnikNode) | ||||
| 			cislo = isinstance(parent, s.CisloNode) | ||||
| 			uloha = (isinstance(parent, s.UlohaVzorakNode) or  | ||||
| 				isinstance(parent, s.UlohaZadaniNode)) | ||||
| 			tema = isinstance(parent, s.TemaVCisleNode) | ||||
| 
 | ||||
| 			if (rocnik or cislo or uloha or tema) or parent==None: | ||||
| 				break | ||||
| 			else: | ||||
| 				parent = treelib.get_parent(parent) | ||||
| 		if rocnik: | ||||
| 			return parent.rocnik.verejne() | ||||
| 		elif cislo: | ||||
| 			return parent.cislo.verejne() | ||||
| 		elif uloha: | ||||
| 			return parent.uloha.verejne() | ||||
| 		elif tema: | ||||
| 			return parent.tema.verejne() | ||||
| 		elif None: | ||||
| 			print("Existuje TreeNode, který není pod číslem, ročníkem, úlohou" | ||||
| 			"ani tématem. {}".format(anode)) | ||||
| 			return False | ||||
| 	 | ||||
| 	@classmethod | ||||
| 	def all_public_children(cls, anode): | ||||
| 		for ch in treelib.all_children(anode): | ||||
| 			if TNLData.public_above(ch): | ||||
| 				yield ch | ||||
| 			else: | ||||
| 				continue | ||||
| 
 | ||||
| 	@classmethod | ||||
| 	def from_treenode(cls, anode, user, parent=None, index=None): | ||||
| 		if TNLData.public_above(anode) or user.has_perm('auth.org'): | ||||
| 			out = cls(anode,parent,index) | ||||
| 		else: | ||||
| 			raise PermissionDenied() | ||||
| 
 | ||||
| 		if user.has_perm('auth.org'): | ||||
| 			enum_children = enumerate(treelib.all_children(anode)) | ||||
| 		else: | ||||
| 			enum_children = enumerate(TNLData.all_public_children(anode))	 | ||||
| 	 | ||||
| 		for (idx,ch) in enum_children: | ||||
| 			outitem = cls.from_treenode(ch, user, out,  idx) | ||||
| 			out.children.append(outitem) | ||||
| 		out.add_edit_options() | ||||
| 		return out | ||||
| 	 | ||||
| 	@classmethod | ||||
| 	def from_tnldata_list(cls, tnllist): | ||||
| 		"""Vyrobíme virtuální TNL, který nemá obsah, ale má za potomky všechna zadaná TNLData""" | ||||
| 		result = cls(None) | ||||
| 		for idx, tnl in enumerate(tnllist): | ||||
| 			result.children.append(tnl) | ||||
| 			tnl.parent = result | ||||
| 			tnl.index = idx | ||||
| 		result.add_edit_options() | ||||
| 		return result | ||||
| 
 | ||||
| 	@classmethod | ||||
| 	def filter_treenode(cls, treenode, predicate): | ||||
| 		tnll = cls._filter_treenode_recursive(treenode, predicate) # TreeNodeList List :-) | ||||
| 		return TNLData.from_tnldata_list(tnll) | ||||
| 
 | ||||
| 	@classmethod | ||||
| 	def _filter_treenode_recursive(cls, treenode, predicate): | ||||
| 		if predicate(treenode): | ||||
| 			return [cls.from_treenode(treenode)] | ||||
| 		else: | ||||
| 			found = [] | ||||
| 			for tn in all_children(treenode): | ||||
| 				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. | ||||
| 				for tnl in result: | ||||
| 					found.append(tnl) | ||||
| 			return found | ||||
| 	 | ||||
| 	def to_json(self): | ||||
| 		#self.node = anode | ||||
| 		#self.children = [] | ||||
| 		#self.parent = parent | ||||
| 		#self.tema_in_path = False | ||||
| 		#self.index = index | ||||
| 		out = {} | ||||
| 		out['node'] = self.sernode.data | ||||
| 		out['children'] = [n.to_json() for n in self.children] | ||||
| 		out['tema_in_path'] = self.tema_in_path | ||||
| 		out['index'] = self.index | ||||
| 		out['deletable'] = self.deletable | ||||
| 		out['editable_siblings'] = self.editable_siblings | ||||
| 		out['editable_children'] = self.editable_children | ||||
| 		out['text_only_subtree'] = self.text_only_subtree | ||||
| 		out['can_podvesit_za'] = self.can_podvesit_za | ||||
| 		out['can_podvesit_pod'] = self.can_podvesit_pred | ||||
| 		out['appendable_children'] = self.appendable_children | ||||
| 		out['appendable_siblings'] = self.appendable_siblings | ||||
| 
 | ||||
| 		return out | ||||
| 
 | ||||
| 
 | ||||
| def treenode_strom_na_seznamy(node): | ||||
| 	out = TNLData(node) | ||||
| 	for ch in treelib.all_children(node): | ||||
| 		outitem = treenode_strom_na_seznamy(ch) | ||||
| 		out.children.append(outitem) | ||||
| 	return out | ||||
| 
 | ||||
| 	def __repr__(self): | ||||
| 		return("TNL({})".format(self.node)) | ||||
| 
 | ||||
| class TreeNodeView(generic.DetailView): | ||||
| 	model = s.TreeNode | ||||
|  | @ -107,22 +238,181 @@ class TreeNodeView(generic.DetailView): | |||
| 
 | ||||
| 	def get_context_data(self,**kwargs): | ||||
| 		context = super().get_context_data(**kwargs) | ||||
| 		context['tnldata'] = treenode_strom_na_seznamy(self.object) | ||||
| 		context['tnldata'] = TNLData.from_treenode(self.object,self.request.user) | ||||
| 		return context | ||||
| 	 | ||||
| # 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): | ||||
| 	def get_object(self): | ||||
| 		nastaveni = get_object_or_404(Nastaveni) | ||||
| 		return nastaveni.aktualni_cislo.cislonode | ||||
| 
 | ||||
| 	def get_context_data(self,**kwargs): | ||||
| 		nastaveni = get_object_or_404(Nastaveni) | ||||
| class TreeNodeJSONView(generic.DetailView): | ||||
| 	model = s.TreeNode | ||||
| 
 | ||||
| 	def get(self,request,*args, **kwargs): | ||||
| 		self.object = self.get_object() | ||||
| 		data = TNLData.from_treenode(self.object,self.request.user).to_json() | ||||
| 		return JsonResponse(data) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class TreeNodePridatView(generic.View): | ||||
| 	type_from_str = { | ||||
| 		'rocnikNode': m.RocnikNode, | ||||
| 		'cisloNode': m.CisloNode, | ||||
| 		'castNode': m.CastNode, | ||||
| 		'textNode': m.TextNode, | ||||
| 		'temaVCisleNode': m.TemaVCisleNode, | ||||
| 		'reseniNode': m.ReseniNode, | ||||
| 		'ulohaZadaniNode': m.UlohaZadaniNode, | ||||
| 		'ulohaVzorakNode': m.UlohaVzorakNode, | ||||
| 		'pohadkaNode': m.PohadkaNode, | ||||
| 		'orgText': m.OrgTextNode, | ||||
| 		} | ||||
| 
 | ||||
| 	def post(self, request, *args, **kwargs): | ||||
| 		######## FIXME: ROZEPSANE, NEFUNGUJE, DOPSAT !!!!!! ########### | ||||
| 		node = s.TreeNode.objects.get(pk=self.kwargs['pk']) | ||||
| 		kam = self.kwargs['kam'] | ||||
| 		co = self.kwargs['co'] | ||||
| 		typ = self.type_from_str[co] | ||||
| 
 | ||||
| 		raise NotImplementedError('Neni to dopsane, dopis to!') | ||||
| 
 | ||||
| 		if kam not in ('pred','syn','za'): | ||||
| 			raise ValidationError('Přidat lze pouze před nebo za node nebo jako syna') | ||||
| 		 | ||||
| 		if co == m.TextNode: | ||||
| 			new_obj = m.Text() | ||||
| 			new_obj.save() | ||||
| 		elif co == m.CastNode: | ||||
| 			new_obj = m.CastNode() | ||||
| 			new_obj.nadpis = request.POST.get('pridat-castNode-{}-{}'.format(node.id,kam)) | ||||
| 			new_obj.save() | ||||
| 		elif co == m.ReseniNode: | ||||
| 			new_obj = m | ||||
| 			pass | ||||
| 		elif co == m.UlohaZadaniNode: | ||||
| 			pass | ||||
| 		elif co == m.UlohaReseniNode: | ||||
| 			pass | ||||
| 		else: | ||||
| 			new_obj = None | ||||
| 
 | ||||
| 
 | ||||
| 		if kam == 'pred': | ||||
| 			pass | ||||
| 
 | ||||
| 
 | ||||
| 		if kam == 'syn': | ||||
| 			if typ == m.TextNode: | ||||
| 				text_obj = m.Text() | ||||
| 				text_obj.save() | ||||
| 				node = treelib.create_child(node,typ,text=text_obj) | ||||
| 			else: | ||||
| 				node = treelib.create_child(node,typ) | ||||
| 		if kam == 'za': | ||||
| 			if typ == m.TextNode: | ||||
| 				text_obj = m.Text() | ||||
| 				text_obj.save() | ||||
| 				node = treelib.create_node_after(node,typ,text=text_obj) | ||||
| 			else: | ||||
| 				node = treelib.create_node_after(node,typ) | ||||
| 
 | ||||
| 		return redirect(node.get_admin_url()) | ||||
| 		 | ||||
| 
 | ||||
| class TreeNodeSmazatView(generic.base.View): | ||||
| 	def post(self, request, *args, **kwargs): | ||||
| 		node = s.TreeNode.objects.get(pk=self.kwargs['pk']) | ||||
| 		if node.first_child: | ||||
| 			raise NotImplementedError('Mazání TreeNode se syny není zatím podporováno!') | ||||
| 		treelib.disconnect_node(node) | ||||
| 		node.delete() | ||||
| 		return redirect(request.headers.get('referer')) | ||||
| 
 | ||||
| class TreeNodeOdvesitPrycView(generic.base.View): | ||||
| 	def post(self, request, *args, **kwargs): | ||||
| 		node = s.TreeNode.objects.get(pk=self.kwargs['pk']) | ||||
| 		treelib.disconnect_node(node) | ||||
| 		node.root = None | ||||
| 		node.save() | ||||
| 		return redirect(request.headers.get('referer')) | ||||
| 
 | ||||
| 
 | ||||
| class TreeNodePodvesitView(generic.base.View): | ||||
| 	def post(self, request, *args, **kwargs): | ||||
| 		node = s.TreeNode.objects.get(pk=self.kwargs['pk']) | ||||
| 		kam = self.kwargs['kam'] | ||||
| 		if kam == 'pred': | ||||
| 			treelib.lower_node(node) | ||||
| 		elif kam == 'za': | ||||
| 			raise NotImplementedError('Podvěsit za není zatím podporováno') | ||||
| 		return redirect(request.headers.get('referer')) | ||||
| 
 | ||||
| class TreeNodeProhoditView(generic.base.View): | ||||
| 	def post(self, request, *args, **kwargs): | ||||
| 		node = s.TreeNode.objects.get(pk=self.kwargs['pk']) | ||||
| 		treelib.swap_succ(node) | ||||
| 		return redirect(request.headers.get('referer')) | ||||
| 		#FIXME ve formulari predat puvodni url a vratit redirect na ni | ||||
| 
 | ||||
| class SirotcinecView(generic.ListView): | ||||
| 	model = s.TreeNode | ||||
| 	template_name = 'seminar/orphanage.html' | ||||
| 	 | ||||
| 	def get_queryset(self): | ||||
| 		return s.TreeNode.objects.not_instance_of(s.RocnikNode).filter(root=None,prev=None,succ=None,father_of_first=None) | ||||
| 
 | ||||
| # FIXME pouzit Django REST Framework | ||||
| class TextWebView(generic.DetailView): | ||||
| 	model = s.Text | ||||
| 
 | ||||
| 	def get(self,request,*args, **kwargs): | ||||
| 		self.object = self.get_object() | ||||
| 		return JsonResponse(model_to_dict(self.object,exclude='do_cisla')) | ||||
| 
 | ||||
| 
 | ||||
| class ProblemView(generic.DetailView): | ||||
| 	model = s.Problem | ||||
| 	# Zkopírujeme template_name od TreeNodeView, protože jsme prakticky jen trošku upravený TreeNodeView | ||||
| 	template_name = TreeNodeView.template_name | ||||
| 
 | ||||
| 	def get_context_data(self, **kwargs): | ||||
| 		context = super().get_context_data(**kwargs) | ||||
| 		verejne = nastaveni.aktualni_cislo.verejne() | ||||
| 		context['verejne'] = verejne	 | ||||
| 		user = self.request.user | ||||
| 		# Teď potřebujeme doplnit tnldata do kontextu. | ||||
| 		# Ošklivý type switch, hezčí by bylo udělat to polymorfni. FIXME. | ||||
| 		if False: | ||||
| 			# Hezčí formátování zbytku :-P | ||||
| 			pass | ||||
| 		elif isinstance(self.object, s.Clanek) or  isinstance(self.object, s.Konfera): | ||||
| 			# Tyhle Problémy mají ŘešeníNode | ||||
| 			context['tnldata'] = TNLData.from_treenode(self.object.reseninode,user) | ||||
| 		elif isinstance(self.object, s.Uloha): | ||||
| 			# FIXME: Teď vždycky zobrazujeme i vzorák! Možná by bylo hezčí/lepší mít to stejně jako pro Téma: procházet jen dosažitelné z Ročníku / čísla / whatever | ||||
| 			tnl_zadani = TNLData.from_treenode(self.object.ulohazadaninode,user) | ||||
| 			tnl_vzorak = TNLData.from_treenode(self.object.ulohavzoraknode,user) | ||||
| 			context['tnldata'] = TNLData.from_tnldata_list([tnl_zadani, tnl_vzorak]) | ||||
| 		elif isinstance(self.object, s.Tema): | ||||
| 			rocniknode = self.object.rocnik.rocniknode | ||||
| 			context['tnldata'] = TNLData.filter_treenode(rocniknode, lambda x: isinstance(x, s.TemaVCisleNode)) | ||||
| 		else: | ||||
| 			raise ValueError("Obecný problém nejde zobrazit.") | ||||
| 		return context | ||||
| 
 | ||||
| 
 | ||||
| class AktualniZadaniView(generic.TemplateView): | ||||
| 	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... | ||||
| #class AktualniZadaniView(TreeNodeView): | ||||
| #	def get_object(self): | ||||
| #		nastaveni = get_object_or_404(Nastaveni) | ||||
| #		return nastaveni.aktualni_cislo.cislonode | ||||
| # | ||||
| #	def get_context_data(self,**kwargs): | ||||
| #		nastaveni = get_object_or_404(Nastaveni) | ||||
| #		context = super().get_context_data(**kwargs) | ||||
| #		verejne = nastaveni.aktualni_cislo.verejne() | ||||
| #		context['verejne'] = verejne	 | ||||
| #		return context | ||||
| 
 | ||||
| #def AktualniZadaniView(request): | ||||
| #	nastaveni = get_object_or_404(Nastaveni) | ||||
| #	verejne = nastaveni.aktualni_cislo.verejne() | ||||
|  | @ -138,7 +428,19 @@ class AktualniZadaniView(TreeNodeView): | |||
| #				}, | ||||
| #			) | ||||
| # | ||||
| #def ZadaniTemataView(request): | ||||
| def ZadaniTemataView(request): | ||||
| 	nastaveni = get_object_or_404(Nastaveni) | ||||
| 	verejne = nastaveni.aktualni_cislo.verejne() | ||||
| 	akt_rocnik = nastaveni.aktualni_cislo.rocnik | ||||
| 	temata = s.Tema.objects.filter(rocnik=akt_rocnik, stav='zadany') | ||||
| 	return render(request, 'seminar/tematka/rozcestnik.html', | ||||
| 			{ | ||||
| 			 'tematka': temata, | ||||
| 			 'verejne': verejne, | ||||
| 				}, | ||||
| 			) | ||||
| 
 | ||||
| 
 | ||||
| #	nastaveni = get_object_or_404(Nastaveni) | ||||
| #	temata = verejna_temata(nastaveni.aktualni_rocnik) | ||||
| #	for t in temata: | ||||
|  | @ -1318,6 +1620,32 @@ class PasswordChangeView(auth_views.PasswordChangeView): | |||
| 	#template_name = 'seminar/password_change.html' | ||||
| 	success_url = reverse_lazy('titulni_strana') | ||||
| 
 | ||||
| class VueTestView(generic.TemplateView): | ||||
| 	template_name = 'seminar/vuetest.html' | ||||
| 
 | ||||
| class NahrajObrazekKTreeNoduView(LoginRequiredMixin, CreateView): | ||||
| 	model = s.Obrazek | ||||
| 	form_class = f.NahrajObrazekKTreeNoduForm | ||||
| 
 | ||||
| 	def get_initial(self): | ||||
| 		initial = super().get_initial() | ||||
| 		initial['na_web'] = self.request.FILES['upload'] | ||||
| 		return initial | ||||
| 
 | ||||
| 
 | ||||
| 	def form_valid(self,form): | ||||
| 		print(self.request.headers) | ||||
| 		print(self.request.headers['Textid']) | ||||
| 		print(form.instance) | ||||
| 		print(form) | ||||
| 		self.object = form.save(commit=False) | ||||
| 		print(self.object.na_web) | ||||
| 		self.object.text = m.Text.objects.get(pk=int(self.request.headers['Textid'])) | ||||
| 		self.object.save() | ||||
| 
 | ||||
| 		return JsonResponse({"url":self.object.na_web.url}) | ||||
| 
 | ||||
| 	 | ||||
| 
 | ||||
| # Jen hloupé rozhazovátko | ||||
| def profilView(request): | ||||
|  |  | |||
							
								
								
									
										162
									
								
								seminar/views/views_rest.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								seminar/views/views_rest.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,162 @@ | |||
| from rest_framework import serializers | ||||
| from rest_polymorphic.serializers import PolymorphicSerializer | ||||
| 
 | ||||
| import seminar.models as m | ||||
| from seminar import treelib | ||||
| 
 | ||||
| DEFAULT_NODE_DEPTH = 2 | ||||
| 
 | ||||
| class TextSerializer(serializers.ModelSerializer): | ||||
| 	class Meta: | ||||
| 		model = m.Text | ||||
| 		fields = '__all__' | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class UlohaVzorakNodeSerializer(serializers.ModelSerializer): | ||||
| 	class Meta: | ||||
| 		model = m.UlohaVzorakNode | ||||
| 		fields = '__all__' | ||||
| 		depth = DEFAULT_NODE_DEPTH | ||||
| 	 | ||||
| class UlohaZadaniNodeSerializer(serializers.ModelSerializer): | ||||
| 	class Meta: | ||||
| 		model = m.UlohaZadaniNode | ||||
| 		fields = '__all__' | ||||
| 		depth = DEFAULT_NODE_DEPTH | ||||
| 
 | ||||
| class RocnikNodeSerializer(serializers.ModelSerializer): | ||||
| 	class Meta: | ||||
| 		model = m.RocnikNode | ||||
| 		fields = '__all__' | ||||
| 		depth = DEFAULT_NODE_DEPTH | ||||
| 
 | ||||
| class CisloNodeSerializer(serializers.ModelSerializer): | ||||
| 	class Meta: | ||||
| 		model = m.CisloNode | ||||
| 		fields = '__all__' | ||||
| 		depth = DEFAULT_NODE_DEPTH | ||||
| 
 | ||||
| class MezicisloNodeSerializer(serializers.ModelSerializer): | ||||
| 	class Meta: | ||||
| 		model = m.MezicisloNode | ||||
| 		fields = '__all__' | ||||
| 		depth = DEFAULT_NODE_DEPTH | ||||
| 
 | ||||
| class TemaVCisleNodeSerializer(serializers.ModelSerializer): | ||||
| 	class Meta: | ||||
| 		model = m.TemaVCisleNode | ||||
| 		fields = '__all__' | ||||
| 		depth = DEFAULT_NODE_DEPTH | ||||
| 
 | ||||
| class OrgTextNodeSerializer(serializers.ModelSerializer): | ||||
| 	class Meta: | ||||
| 		model = m.OrgTextNode | ||||
| 		fields = '__all__' | ||||
| 		depth = DEFAULT_NODE_DEPTH | ||||
| 
 | ||||
| class PohadkaNodeSerializer(serializers.ModelSerializer): | ||||
| 	class Meta: | ||||
| 		model = m.PohadkaNode | ||||
| 		fields = '__all__' | ||||
| 		depth = DEFAULT_NODE_DEPTH | ||||
| 
 | ||||
| class TextNodeSerializer(serializers.ModelSerializer): | ||||
| 	text = TextSerializer() | ||||
| 
 | ||||
| 	class Meta: | ||||
| 		model = m.TextNode | ||||
| 		fields = ('id','text','polymorphic_ctype') | ||||
| 		depth = DEFAULT_NODE_DEPTH | ||||
| 
 | ||||
| class TextNodeWriteSerializer(serializers.ModelSerializer): | ||||
| 	text = TextSerializer() | ||||
| 
 | ||||
| 	def update(self,node,validated_data): | ||||
| 		node.text.na_web = validated_data.get('text').get('na_web') | ||||
| 		node.text.save() | ||||
| 		return node | ||||
| 
 | ||||
| 	class Meta: | ||||
| 		model = m.TextNode | ||||
| 		fields = ('id','text') | ||||
| 		depth = DEFAULT_NODE_DEPTH | ||||
| 
 | ||||
| class TextNodeCreateSerializer(serializers.ModelSerializer): | ||||
| 	text = TextSerializer() | ||||
| 	refnode = serializers.CharField() | ||||
| 	where = serializers.CharField() | ||||
| 
 | ||||
| 	def create(self,validated_data): | ||||
| 		temp_text = validated_data.pop('text') | ||||
| 		where = validated_data.pop('where') | ||||
| 		refnode_id = validated_data.pop('refnode') | ||||
| 		refnode = m.TreeNode.objects.get(pk=refnode_id) | ||||
| 		text = m.Text.objects.create(**temp_text) | ||||
| 		if where == 'syn': | ||||
| 			node = treelib.create_child(refnode,m.TextNode,text=text) | ||||
| 		elif where == 'za': | ||||
| 			node = treelib.create_node_after(refnode,m.TextNode,text=text) | ||||
| 		elif where == 'pred': | ||||
| 			node = treelib.create_node_before(refnode,m.TextNode,text=text) | ||||
| 		node.where = None | ||||
| 		node.refnode = None | ||||
| 		return node | ||||
| 
 | ||||
| 	class Meta: | ||||
| 		model = m.TextNode | ||||
| 		fields = ('text','where','refnode') | ||||
| 		depth = DEFAULT_NODE_DEPTH | ||||
| 
 | ||||
| class CastNodeSerializer(serializers.ModelSerializer): | ||||
| 	class Meta: | ||||
| 		model = m.CastNode | ||||
| 		fields = '__all__' | ||||
| 		depth = DEFAULT_NODE_DEPTH | ||||
| 
 | ||||
| class CastNodeCreateSerializer(serializers.ModelSerializer): | ||||
| 	refnode = serializers.CharField() | ||||
| 	where = serializers.CharField() | ||||
| 
 | ||||
| 	def create(self,validated_data): | ||||
| 		temp_nadpis = validated_data.pop('nadpis') | ||||
| 		where = validated_data.pop('where') | ||||
| 		refnode_id = validated_data.pop('refnode') | ||||
| 		refnode = m.TreeNode.objects.get(pk=refnode_id) | ||||
| 		if where == 'syn': | ||||
| 			node = treelib.create_child(refnode,m.CastNode,nadpis=temp_nadpis) | ||||
| 		elif where == 'za': | ||||
| 			node = treelib.create_node_after(refnode,m.CastNode,nadpis=temp_nadpis) | ||||
| 		elif where == 'pred': | ||||
| 			node = treelib.create_node_before(refnode,m.CastNode,nadpis=temp_nadpis) | ||||
| 		node.where = None | ||||
| 		node.refnode = None | ||||
| 		return node | ||||
| 
 | ||||
| 	class Meta: | ||||
| 		model = m.CastNode | ||||
| 		fields = ('nadpis','where','refnode') | ||||
| 		depth = DEFAULT_NODE_DEPTH | ||||
| 
 | ||||
| class ReseniNodeSerializer(serializers.ModelSerializer): | ||||
| 	class Meta: | ||||
| 		model = m.ReseniNode | ||||
| 		fields = '__all__' | ||||
| 		depth = DEFAULT_NODE_DEPTH | ||||
| 
 | ||||
| 
 | ||||
| class TreeNodeSerializer(PolymorphicSerializer): | ||||
| 	model_serializer_mapping = { | ||||
| 		m.RocnikNode: RocnikNodeSerializer, | ||||
| 		m.CisloNode: CisloNodeSerializer, | ||||
| 		m.MezicisloNode: MezicisloNodeSerializer, | ||||
| 		m.TemaVCisleNode: TemaVCisleNodeSerializer, | ||||
| 		m.OrgTextNode: OrgTextNodeSerializer, | ||||
| 		m.UlohaZadaniNode: UlohaZadaniNodeSerializer, | ||||
| 		m.UlohaVzorakNode: UlohaVzorakNodeSerializer, | ||||
| 		m.PohadkaNode: PohadkaNodeSerializer, | ||||
| 		m.TextNode: TextNodeSerializer, | ||||
| 		m.CastNode: CastNodeSerializer, | ||||
| 		m.ReseniNode: ReseniNodeSerializer, | ||||
| 	} | ||||
| 
 | ||||
							
								
								
									
										96
									
								
								seminar/viewsets.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								seminar/viewsets.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,96 @@ | |||
| from rest_framework import viewsets,filters | ||||
| from rest_framework.permissions import BasePermission, AllowAny | ||||
| from . import models as m | ||||
| from . import views | ||||
| 
 | ||||
| from seminar.permissions import AllowWrite  | ||||
| 
 | ||||
| class PermissionMixin(object): | ||||
| 	""" Redefines get_permissions so that only organizers can make changes. """ | ||||
| 
 | ||||
| 	def get_permissions(self): | ||||
| 		permission_classes = [] | ||||
| 		print("get_permissions have been called.") | ||||
| 		if self.action in ["create", "update", "partial_update", "destroy"]: | ||||
| 			permission_classes = [AllowWrite] # speciální permission na zápis - orgové | ||||
| 		else: | ||||
| 			permission_classes = [AllowAny]  | ||||
| 		# návštěvník nemusí být zalogován, aby si prohlížel obsah | ||||
| 		return [permission() for permission in permission_classes] | ||||
| 
 | ||||
| class ReadWriteSerializerMixin(object): | ||||
| 	""" | ||||
| 	Overrides get_serializer_class to choose the read serializer | ||||
| 	for GET requests and the write serializer for POST requests. | ||||
| 
 | ||||
| 	Set read_serializer_class and write_serializer_class attributes on a | ||||
| 	viewset.  | ||||
| 	""" | ||||
| 
 | ||||
| 	read_serializer_class = None | ||||
| 	create_serializer_class = None | ||||
| 	write_serializer_class = None | ||||
| 
 | ||||
| 	def get_serializer_class(self):		 | ||||
| 		if self.action == "create": | ||||
| 			return self.get_create_serializer_class() | ||||
| 		if self.action in ["update", "partial_update", "destroy"]: | ||||
| 			return self.get_write_serializer_class() | ||||
| 		return self.get_read_serializer_class() | ||||
| 
 | ||||
| 	def get_read_serializer_class(self): | ||||
| 		assert self.read_serializer_class is not None, ( | ||||
| 			"'%s' should either include a `read_serializer_class` attribute," | ||||
| 			"or override the `get_read_serializer_class()` method." | ||||
| 			% self.__class__.__name__ | ||||
| 		) | ||||
| 		return self.read_serializer_class | ||||
| 
 | ||||
| 	def get_write_serializer_class(self): | ||||
| 		assert self.write_serializer_class is not None, ( | ||||
| 			"'%s' should either include a `write_serializer_class` attribute," | ||||
| 			"or override the `get_write_serializer_class()` method." | ||||
| 			% self.__class__.__name__ | ||||
| 		) | ||||
| 		return self.write_serializer_class | ||||
| 
 | ||||
| 	def get_create_serializer_class(self): | ||||
| 		assert self.create_serializer_class is not None, ( | ||||
| 			"'%s' should either include a `create_serializer_class` attribute," | ||||
| 			"or override the `get_create_serializer_class()` method." | ||||
| 			% self.__class__.__name__ | ||||
| 		) | ||||
| 		return self.create_serializer_class | ||||
| 
 | ||||
| class UlohaVzorakNodeViewSet(PermissionMixin, viewsets.ModelViewSet): | ||||
| 	queryset = m.UlohaVzorakNode.objects.all() | ||||
| 	serializer_class = views.UlohaVzorakNodeSerializer | ||||
| 
 | ||||
| class TextViewSet(PermissionMixin, viewsets.ModelViewSet): | ||||
| 	queryset = m.Text.objects.all() | ||||
| 	serializer_class = views.TextSerializer | ||||
| 
 | ||||
| class TextNodeViewSet(PermissionMixin, ReadWriteSerializerMixin,viewsets.ModelViewSet): | ||||
| 	queryset = m.TextNode.objects.all() | ||||
| 	read_serializer_class = views.TextNodeSerializer | ||||
| 	write_serializer_class = views.TextNodeWriteSerializer | ||||
| 	create_serializer_class = views.TextNodeCreateSerializer | ||||
| 
 | ||||
| class CastNodeViewSet(PermissionMixin, ReadWriteSerializerMixin,viewsets.ModelViewSet): | ||||
| 	queryset = m.CastNode.objects.all() | ||||
| 	read_serializer_class = views.CastNodeSerializer | ||||
| 	write_serializer_class = views.CastNodeSerializer | ||||
| 	create_serializer_class = views.CastNodeCreateSerializer | ||||
| 
 | ||||
| class UlohaVzorakNodeViewSet(PermissionMixin, viewsets.ModelViewSet): | ||||
| 	serializer_class = views.UlohaVzorakNodeSerializer | ||||
| 
 | ||||
| 	def get_queryset(self): | ||||
| 		queryset = m.UlohaVzorakNode.objects.all() | ||||
| 		nazev = self.request.query_params.get('nazev',None) | ||||
| 		if nazev is not None: | ||||
| 			queryset = queryset.filter(nazev__contains=nazev) | ||||
| 		if self.request.user.has_perm('auth.org'): | ||||
| 			return queryset | ||||
| 		else: # pro neorgy jen zveřejněné vzoráky | ||||
| 			return queryset.filter(uloha__cislo_reseni__verejne_db=True) | ||||
							
								
								
									
										22
									
								
								vue_frontend/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vue_frontend/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | |||
| .DS_Store | ||||
| node_modules | ||||
| /dist | ||||
| 
 | ||||
| # local env files | ||||
| .env.local | ||||
| .env.*.local | ||||
| 
 | ||||
| # Log files | ||||
| npm-debug.log* | ||||
| yarn-debug.log* | ||||
| yarn-error.log* | ||||
| pnpm-debug.log* | ||||
| 
 | ||||
| # Editor directories and files | ||||
| .idea | ||||
| .vscode | ||||
| *.suo | ||||
| *.ntvs* | ||||
| *.njsproj | ||||
| *.sln | ||||
| *.sw? | ||||
							
								
								
									
										5
									
								
								vue_frontend/babel.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								vue_frontend/babel.config.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | |||
| module.exports = { | ||||
|   presets: [ | ||||
|     '@vue/cli-plugin-babel/preset' | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										48
									
								
								vue_frontend/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								vue_frontend/package.json
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,48 @@ | |||
| { | ||||
|   "name": "vue_frontend", | ||||
|   "version": "0.1.0", | ||||
|   "private": true, | ||||
|   "scripts": { | ||||
|     "serve": "vue-cli-service serve", | ||||
|     "build": "vue-cli-service build", | ||||
|     "lint": "vue-cli-service lint" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@ckeditor/ckeditor5-upload": "^23.0.0", | ||||
|     "@ckeditor/ckeditor5-vue": "^1.0.1", | ||||
|     "axios": "^0.19.2", | ||||
|     "ckeditor5-build-classic-simple-upload-adapter-image-resize": "^1.0.4", | ||||
|     "core-js": "^3.6.5", | ||||
|     "vue": "^2.6.11", | ||||
|     "vue-router": "^3.4.3" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@vue/cli-plugin-babel": "~4.4.0", | ||||
|     "@vue/cli-plugin-eslint": "~4.4.0", | ||||
|     "@vue/cli-service": "^4.5.6", | ||||
|     "babel-eslint": "^10.1.0", | ||||
|     "eslint": "^6.7.2", | ||||
|     "eslint-plugin-vue": "^6.2.2", | ||||
|     "vue-template-compiler": "^2.6.11", | ||||
|     "webpack-bundle-tracker": "0.4.3" | ||||
|   }, | ||||
|   "eslintConfig": { | ||||
|     "root": true, | ||||
|     "env": { | ||||
|       "node": true | ||||
|     }, | ||||
|     "extends": [ | ||||
|       "plugin:vue/essential", | ||||
|       "eslint:recommended" | ||||
|     ], | ||||
|     "parserOptions": { | ||||
|       "parser": "babel-eslint" | ||||
|     }, | ||||
|     "rules": {} | ||||
|   }, | ||||
|   "browserslist": [ | ||||
|     "> 1%", | ||||
|     "last 2 versions", | ||||
|     "not dead" | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										7
									
								
								vue_frontend/src/App.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								vue_frontend/src/App.vue
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | |||
| <template> | ||||
|   <router-view id="app"/> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| export default { name: 'app' } | ||||
| </script> | ||||
							
								
								
									
										50
									
								
								vue_frontend/src/components/AddNewNode.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								vue_frontend/src/components/AddNewNode.vue
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,50 @@ | |||
| <template> | ||||
| 	<div class="addnewnode"> | ||||
| 		<button v-if="types.includes('castNode')" v-on:click="selected='castNode'" :disabled="selected && selected !== 'castNode'">Část</button> | ||||
| 		<button v-if="types.includes('textNode')" v-on:click="selected='textNode'" :disabled="selected && selected !== 'textNode'">Text</button> | ||||
| 		<button v-if="types.includes('reseniNode')" v-on:click="selected='reseniNode'" :disabled="selected && selected !== 'reseniNode'">Řešení</button> | ||||
| 		<button v-if="types.includes('ulohaZadaniNode')" v-on:click="selected='ulohaZadaniNode'" :disabled="selected && selected !== 'ulohaZadaniNode'">Zadání úlohy</button> | ||||
| 		<button v-if="types.includes('ulohaVzorakNode')" v-on:click="selected='ulohaVzorakNode'" :disabled="selected && selected !== 'ulohaVzorakNode'">Vzorák</button> | ||||
| 		<div v-if="selected"> | ||||
| 			<component :is='selected' :item='null' :where="where" :refnode="refnode" create></component> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| import castNode from './CastNode.vue' | ||||
| import textNode from './TextNode.vue' | ||||
| import ulohaZadaniNode from './UlohaZadaniNode.vue' | ||||
| import ulohaVzorakNode from './UlohaVzorakNode.vue' | ||||
| //import reseniNode from './UlohaVzorakNode.vue' | ||||
| 
 | ||||
| export default { | ||||
| 	name: 'AddNewNode', | ||||
| 	props: { | ||||
| 		types: Array, | ||||
| 		where: String, | ||||
| 		refnode: Object, | ||||
| 	}, | ||||
| 	data: () => ({ | ||||
| 		selected: null, | ||||
| 	}), | ||||
| 	components: { | ||||
| 		castNode, | ||||
| 		textNode, | ||||
| 		ulohaZadaniNode, | ||||
| 		ulohaVzorakNode, | ||||
| 	}, | ||||
| 	mounted: function() { | ||||
| 		this.$root.$on('dataUpdated',(arg) => { | ||||
| 			this.selected = null; | ||||
| 			console.log('dataUpdated'+arg); | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <style> | ||||
| .addnewnode { | ||||
| 	display: inline; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										86
									
								
								vue_frontend/src/components/CastNode.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								vue_frontend/src/components/CastNode.vue
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,86 @@ | |||
| <template> | ||||
| 	<div class="castnode"> | ||||
| 		<!--pre>CastNode {{item}} {{typeof(item)}}</pre--> | ||||
| 		<div v-if="editorShow"> | ||||
| 			<input type="text" v-model="currentText" /> | ||||
| 			<button v-on:click="saveText">Uložit</button> | ||||
| 			<button v-on:click="currentText = originalText;editorShow=!editorShow;">Zahodit úpravy</button> | ||||
| 		</div> | ||||
| 		<div v-else> | ||||
| 		<h4>{{ currentText }} </h4> <button v-if="editorMode" v-on:click="editorShow=!editorShow">Upravit</button> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| import axios from 'axios' | ||||
| 
 | ||||
| export default { | ||||
| 	name: 'CastNode', | ||||
| 	data: () => ({ | ||||
| 		editorShow: false, | ||||
| 		currentText: "", | ||||
| 		originalText: "", | ||||
| 	}), | ||||
| 	props: { | ||||
| 		item: Object, | ||||
| 		editorShow: Boolean, | ||||
| 		create: Boolean, | ||||
| 		where: String, | ||||
| 		refnode: Object | ||||
| 	}, | ||||
| 	mounted: function() { | ||||
| 		if (this.create){ | ||||
| 			this.currentText = ""; | ||||
| 			this.originalText = ""; | ||||
| 			this.editorShow = true; | ||||
| 		} | ||||
| 		else { | ||||
| 			this.currentText = this.item.node.nadpis; | ||||
| 			this.originalText = this.item.node.nadpis; | ||||
| 		} | ||||
| 
 | ||||
| 		//this.getText(); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		saveText: function() { | ||||
| 			console.log("Saving cast"); | ||||
| 			console.log(this.currentText); | ||||
| 			if (this.create){ | ||||
| 				console.log(this.refnode); | ||||
| 				console.log(this.where); | ||||
| 				axios.post('/api/castnode/',{ | ||||
| 					'nadpis': this.currentText, | ||||
| 					'refnode': this.refnode.id, | ||||
| 					'where': this.where | ||||
| 				}).then(response => { | ||||
| 					this.originalText = response.data.nadpis; | ||||
| 					this.$root.$emit('updateData',"castNode create update"); | ||||
| 				}) | ||||
| 				.catch(e => { | ||||
| 					console.log(e); | ||||
| 					this.errors.push(e); | ||||
| 				}); | ||||
| 			} else { | ||||
| 				axios.put('/api/castnode/'+this.item.node.id+'/',{ | ||||
| 					'nadpis': this.currentText, | ||||
| 					'id': this.item.node.id | ||||
| 				}).then(response => { | ||||
| 					this.originalText = response.data.nadpis; | ||||
| 				}) | ||||
| 				.catch(e => { | ||||
| 					console.log(e); | ||||
| 					this.errors.push(e) | ||||
| 				}); | ||||
| 				 | ||||
| 			} | ||||
| 
 | ||||
| 			this.editorShow = false; | ||||
| 		} | ||||
| 		} | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <!-- Add "scoped" attribute to limit CSS to this component only --> | ||||
| <style scoped> | ||||
| </style> | ||||
							
								
								
									
										19
									
								
								vue_frontend/src/components/CisloNode.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								vue_frontend/src/components/CisloNode.vue
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| <template> | ||||
| 	<div class="cislonode"> | ||||
| 		<!--pre>CisloNode {{item}} {{typeof(item)}}</pre--> | ||||
| 		<h1>{{ item.node.cislo.poradi }}. číslo</h1> | ||||
| 	</div> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| export default { | ||||
| 	name: 'CisloNode', | ||||
| 	props: { | ||||
| 		item: Object | ||||
| 	} | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <!-- Add "scoped" attribute to limit CSS to this component only --> | ||||
| <style scoped> | ||||
| </style> | ||||
							
								
								
									
										21
									
								
								vue_frontend/src/components/RocnikNode.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vue_frontend/src/components/RocnikNode.vue
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | |||
| <template> | ||||
| 
 | ||||
| 	<div class="rocniknode"> | ||||
| 		<!--pre>RocnikNode {{item}} {{typeof(item)}}</pre--> | ||||
| 		<h1>Ročník {{ item.node.rocnik.rocnik }} ({{ item.node.rocnik.prvni_rok }}/{{item.node.rocnik.prvni_rok+1 }})</h1> | ||||
| 	</div> | ||||
| 
 | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| export default { | ||||
| 	name: 'RocnikNode', | ||||
| 	props: { | ||||
| 		item: Object | ||||
| 	} | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <!-- Add "scoped" attribute to limit CSS to this component only --> | ||||
| <style scoped> | ||||
| </style> | ||||
							
								
								
									
										19
									
								
								vue_frontend/src/components/TemaVCisleNode.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								vue_frontend/src/components/TemaVCisleNode.vue
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| <template> | ||||
| 	<div class="temavcislenode"> | ||||
| 		<!--pre>TemaVCisleNode {{item}} {{typeof(item)}}</pre--> | ||||
| 		<h2>Téma {{ item.node.tema.kod }}: {{ item.node.tema.nazev }}</h2> | ||||
| 	</div> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| export default { | ||||
| 	name: 'TemaVCisleNode', | ||||
| 	props: { | ||||
| 		item: Object | ||||
| 	} | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <!-- Add "scoped" attribute to limit CSS to this component only --> | ||||
| <style scoped> | ||||
| </style> | ||||
							
								
								
									
										174
									
								
								vue_frontend/src/components/TextNode.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								vue_frontend/src/components/TextNode.vue
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,174 @@ | |||
| <template> | ||||
| 	<div v-if="loading" class="loading"> | ||||
| 	<p>Loading...</p> | ||||
| 	</div> | ||||
| 	<div v-else class="textnode"> | ||||
| 		<!--pre>TextNode {{item}} {{typeof(item)}}</pre--> | ||||
| 		<template v-if="editorShow"> | ||||
| 
 | ||||
| 			<div v-if="plainEditShow"> | ||||
| 				<textarea id="textarea" v-model="currentText" rows="8" cols="50"></textarea> | ||||
| 				<br> | ||||
| 				<button v-on:click="plainEditShow=!plainEditShow">Editovat v editoru</button> | ||||
| 			</div> | ||||
| 
 | ||||
| 			<div v-else> | ||||
| 				<component v-bind:is="editorComponent" :editor="editor" v-model="currentText" :config="editorConfig"></component> | ||||
| 				<button v-on:click="plainEditShow=!plainEditShow">Editovat HTML</button> | ||||
| 			</div> | ||||
| 			<button v-on:click="saveText">Uložit</button> | ||||
| 			<button v-on:click="currentText = originalText;editorShow=!editorShow;">Zahodit úpravy</button> | ||||
| 
 | ||||
| 		</template> | ||||
| 
 | ||||
| 		<template v-else v-bind:class="changedObject"> | ||||
| 			<p v-html="currentText"></p> | ||||
| 			<button v-if="editorMode" v-on:click="editorShow=!editorShow">Upravit</button> | ||||
| 		</template> | ||||
| 	</div> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| import axios from 'axios' | ||||
| 
 | ||||
| //import ClassicEditor from '@ckeditor/ckeditor5-build-classic' | ||||
| import ClassicEditor from 'ckeditor5-build-classic-simple-upload-adapter-image-resize'; | ||||
| import CKEditor from '@ckeditor/ckeditor5-vue'; | ||||
| 
 | ||||
| export default { | ||||
| 	name: 'TextNode', | ||||
| 	data: () => ({ | ||||
| 		loading: false, | ||||
| 		editor: ClassicEditor, | ||||
| 		editorData: '<p>Content of the editor.</p>', | ||||
| 		editorConfig: { | ||||
| 			extraPlugins: ['SimpleUploadAdapter'], | ||||
| 			simpleUpload: { | ||||
| 				uploadUrl: "/temp/image_upload/", | ||||
| 				headers: {}, | ||||
| 				withCredentials: true | ||||
| 			} | ||||
| 			// The configuration of the editor. | ||||
| 		}, | ||||
| 		editorShow: false, | ||||
| 		plainEditShow: false, | ||||
| 		editorComponent: CKEditor.component, | ||||
| 		currentText: "",// this.item.node.text.na_web, | ||||
| 		originalText: "",// this.item.node.text.na_web, | ||||
| 	}), | ||||
| 	computed: { | ||||
| 		changedObject: function () { | ||||
| 			//console.log(this.currentText); | ||||
| 			//console.log(this.originalText); | ||||
| 			return { | ||||
| 				changed: this.currentText !== this.originalText, | ||||
| 				} | ||||
| 			}, | ||||
| 		textId: function () { | ||||
| 			console.log(this.create); | ||||
| 			console.log(this.node.text.id); | ||||
| 			if (this.create){ | ||||
| 				return null; | ||||
| 			} | ||||
| 			return this.node.text.id; | ||||
| 
 | ||||
| 		} | ||||
| 	}, | ||||
| 	props: { | ||||
| 		item: Object, | ||||
| 		editorShow: Boolean, | ||||
| 		editorMode: Boolean, | ||||
| 		create: Boolean, | ||||
| 		where: String, | ||||
| 		refnode: Object | ||||
| 	}, | ||||
| 	mounted: function() { | ||||
| 		//console.log("mounted"); | ||||
| 		this.editorConfig.simpleUpload.headers['X-CSRFToken'] = this.getCookie('csrftoken'); | ||||
| 		axios.defaults.headers.common['X-CSRFToken'] = this.getCookie('csrftoken'); | ||||
| 		if (this.create){ | ||||
| 			this.currentText = ""; | ||||
| 			this.originalText = ""; | ||||
| 			this.editorShow = true; | ||||
| 		} else { | ||||
| 			this.currentText = this.item.node.text.na_web; | ||||
| 			this.originalText = this.item.node.text.na_web; | ||||
| 			this.editorConfig.simpleUpload.headers.textId = this.item.node.text.id; | ||||
| 
 | ||||
| 		} | ||||
| 		//this.getText(); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		getCookie: function (name){ | ||||
| 			var cookieValue = null; | ||||
| 			if (document.cookie && document.cookie != '') { | ||||
| 				var cookies = document.cookie.split(';'); | ||||
| 				for (var i = 0; i < cookies.length; i++) { | ||||
| 					var cookie = cookies[i].trim(); | ||||
| 					// Does this cookie string begin with the name we want? | ||||
| 
 | ||||
| 					if (cookie.substring(0, name.length + 1) == (name + '=')) { | ||||
| 						cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); | ||||
| 						break; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			return cookieValue; | ||||
| 		}, | ||||
| 		getText: function() { | ||||
| 			this.loading = true; | ||||
| 			console.log(this.item); | ||||
| 			console.log(this.item.node.text); | ||||
| 			axios.get('/treenode/text/'+this.item.node.text) | ||||
| 			.then((response) => { | ||||
| 				this.text = response.data.na_web; | ||||
| 				this.loading = false; | ||||
| 			}) | ||||
| 			.catch((err) => { | ||||
| 				this.loading = false; | ||||
| 				console.log(err); | ||||
| 				}) | ||||
| 		}, | ||||
| 		 | ||||
| 		saveText: function() { | ||||
| 			console.log("Saving text"); | ||||
| 			console.log(this.currentText); | ||||
| 			if (this.create){ | ||||
| 				console.log(this.refnode); | ||||
| 				console.log(this.where); | ||||
| 				axios.post('/api/textnode/',{ | ||||
| 					'text': { 'na_web': this.currentText}, | ||||
| 					'refnode': this.refnode.id, | ||||
| 					'where': this.where | ||||
| 				}).then(response => { | ||||
| 					this.originalText = response.data.text.na_web; | ||||
| 					this.loading = false; | ||||
| 					this.$root.$emit('updateData',"textNode create update"); | ||||
| 					}) | ||||
| 				.catch(e => { | ||||
| 					this.errors.push(e) | ||||
| 				}); | ||||
| 			} else { | ||||
| 				axios.put('/api/textnode/'+this.item.node.id+'/',{ | ||||
| 					'text': { 'na_web': this.currentText}, | ||||
| 					'id': this.item.node.id | ||||
| 				}).then(response => { | ||||
| 					this.originalText = response.data.text.na_web; | ||||
| 				}) | ||||
| 				.catch(e => { | ||||
| 					this.errors.push(e) | ||||
| 				}); | ||||
| 				 | ||||
| 			} | ||||
| 			this.editorShow = false; | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <!-- Add "scoped" attribute to limit CSS to this component only --> | ||||
| <style scoped> | ||||
| .changed { | ||||
| 	background-color: 'yellow'; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										161
									
								
								vue_frontend/src/components/TreeNode.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								vue_frontend/src/components/TreeNode.vue
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,161 @@ | |||
| <template> | ||||
| 
 | ||||
| 	<div :class="editorMode ? 'treenode-org' : 'treenode'"> | ||||
| 	<!--b v-if="v_tematu">v tematu</b> | ||||
| 	<b v-if="visible">visible</b> | ||||
| 	Force visible: {{String(force_visible)}}--> | ||||
| 	<component :is='item.node.polymorphic_ctype.model' :item='item' :key='item.node.id' | ||||
| 								:editorMode="editorMode" | ||||
| 								:debugMode="debugMode"></component> | ||||
| 
 | ||||
| 
 | ||||
| 	<button v-if="debugMode" v-on:click="debugShow = !debugShow" class="nodebug">Ladící data</button> <!-- bude tu nějaký if na class="nodebug", v debug módu bude tlačítko vidět, jinak ne --> | ||||
| 	<div v-if="debugShow"> | ||||
| 		<pre>{{ item.node.polymorphic_ctype.model }}</pre> | ||||
| 		<pre>{{ item }}</pre> | ||||
| 	</div> | ||||
| 
 | ||||
| 	<div v-if="item.children.length === 0"> | ||||
| 		<div v-if="item.appendable_children.length > 0 && editorMode"> | ||||
| 			<b>Vložit jako syna: </b> | ||||
| 			<addnewnode :types="item.appendable_children" :refnode="item.node" where="syn" /> | ||||
| 		</div> | ||||
| 	</div> | ||||
| 
 | ||||
| 	<div v-else :class="editorMode ? 'children-org' : 'children'"> <!-- bude tu nějaký if na class="children" --> | ||||
| 		<div v-if="item.children.length > 0 && item.children[0].appendable_siblings.length > 0 && editorMode"> | ||||
| 			<b>Vložit před: </b> | ||||
| 			<addnewnode :types="item.children[0].appendable_siblings" :refnode="item.children[0].node" where="pred" />  | ||||
| 		</div> | ||||
| 			<div v-if="item.node.polymorphic_ctype.model==='temavcislenode'"> | ||||
| 				<!--Children: {{String(showChildren)}}--> | ||||
| 				<div v-for="(chld, index) in item.children" v-bind:key="chld.nazev" > | ||||
| 					<!--Hide: {{hideNode(chld)}}, v tematu: {{v_tematu}}, force_visible: {{force_visible}}--> | ||||
| 					<div v-if="!hideNode(chld)"> | ||||
| 						<div v-if="chld.node.polymorphic_ctype.model==='ulohazadaninode'"> | ||||
| 							<button v-if="showChildren" v-on:click="showChildren=!showChildren"> Schovat </button> | ||||
| 							<button v-else v-on:click="showChildren=!showChildren"> Rozbalit </button> | ||||
| 							<TreeNode :item="chld" :v_tematu="true" | ||||
| 									:force_visible="showChildren" | ||||
| 									:editorMode="editorMode" | ||||
| 									:debugMode="debugMode"> | ||||
| 							</TreeNode> | ||||
| 						</div> | ||||
| 						<div v-else> | ||||
| 							<TreeNode :item="chld" :v_tematu="true" | ||||
| 									:force_visible="showChildren" | ||||
| 									:editorMode="editorMode" | ||||
| 									:debugMode="debugMode"> | ||||
| 							</TreeNode> | ||||
| 						</div> | ||||
| 						<div v-if="chld.appendable_siblings.length > 0 && editorMode" > | ||||
| 							<b v-if="index < (item.children.length - 1)">Vložit mezi: </b> | ||||
| 							<b v-else>Vložit za: </b> | ||||
| 							<addnewnode :types="chld.appendable_siblings" :refnode="chld.node" where="za" />  | ||||
| 						</div> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 				<button v-if="showChildren" v-on:click="showChildren=!showChildren"> Schovat </button> | ||||
| 				<button v-else v-on:click="showChildren=!showChildren"> Rozbalit </button> | ||||
| 			</div> | ||||
| 			<div v-else> | ||||
| 				<div v-for="(chld, index) in item.children" v-bind:key="chld.nazev" > | ||||
| 					<div v-if="v_tematu && chld.node.polymorphic_ctype.model==='ulohazadaninode'"> | ||||
| 						<div> Tady možná něco je </div> | ||||
| 						<TreeNode :item="chld" :v_tematu="v_tematu" | ||||
| 								:force_visible="force_visible" | ||||
| 								:editorMode="editorMode" | ||||
| 								:debugMode="debugMode"> | ||||
| 						</TreeNode> | ||||
| 					</div> | ||||
| 					<div v-else> | ||||
| 						<TreeNode :item="chld" :v_tematu="v_tematu" | ||||
| 								:force_visible="force_visible" | ||||
| 								:editorMode="editorMode" | ||||
| 								:debugMode="debugMode"> | ||||
| 						</TreeNode> | ||||
| 					</div> | ||||
| 					<div v-if="chld.appendable_siblings.length > 0 && editorMode" > | ||||
| 						<b v-if="index < (item.children.length - 1)">Vložit mezi: </b> | ||||
| 						<b v-else>Vložit za: </b> | ||||
| 						<addnewnode :types="chld.appendable_siblings" :refnode="chld.node" where="za" />  | ||||
| 					</div> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 
 | ||||
| 	</div> | ||||
| 
 | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| 
 | ||||
| import rocniknode from './RocnikNode.vue' | ||||
| import cislonode from './CisloNode.vue' | ||||
| import temavcislenode from './TemaVCisleNode.vue' | ||||
| import castnode from './CastNode.vue' | ||||
| import textnode from './TextNode.vue' | ||||
| import ulohazadaninode from './UlohaZadaniNode.vue' | ||||
| import ulohavzoraknode from './UlohaVzorakNode.vue' | ||||
| import addnewnode from './AddNewNode.vue' | ||||
| 
 | ||||
| export default { | ||||
| 	name: 'TreeNode', | ||||
| 	components: { | ||||
| 		rocniknode, | ||||
| 		cislonode, | ||||
| 		temavcislenode, | ||||
| 		castnode, | ||||
| 		textnode, | ||||
| 		ulohazadaninode, | ||||
| 		ulohavzoraknode, | ||||
| 		addnewnode | ||||
| 	}, | ||||
| 	data: () => ({ | ||||
| 		debugShow: false, | ||||
| 		showChildren: false | ||||
| 	}), | ||||
| 	computed: { | ||||
| 	}, | ||||
| 	props: { | ||||
| 		item: Object, | ||||
| 		force_visible: Boolean, | ||||
| 		v_tematu: Boolean, | ||||
| 		editorMode: Boolean, | ||||
| 		debugMode: Boolean, | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		hideNode: function(chld){ | ||||
| 			if (this.showChildren || this.force_visible){ | ||||
| 				return false; | ||||
| 			} | ||||
| 			if (chld.node.polymorphic_ctype.model === 'ulohazadaninode'){ | ||||
| 				return false; | ||||
| 			} | ||||
| 			return true; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <!-- Add "scoped" attribute to limit CSS to this component only --> | ||||
| <style scoped> | ||||
| 
 | ||||
| 
 | ||||
| .treenode-org { | ||||
|   padding: 5px; | ||||
|   margin: 5px; | ||||
|   border: #6a0043 2px solid; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| .children-org { | ||||
|   padding: 10px; | ||||
|   margin: 5px; | ||||
|   border: #6a0043 2px dashed; | ||||
| } | ||||
| 
 | ||||
| .nodebug { | ||||
| 	/* display: none; */ | ||||
| } | ||||
| 
 | ||||
| </style> | ||||
							
								
								
									
										75
									
								
								vue_frontend/src/components/TreeNodeRoot.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								vue_frontend/src/components/TreeNodeRoot.vue
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,75 @@ | |||
| <template> | ||||
| 	<div id="app"> | ||||
| 		<div id="loading" v-if="loading"> | ||||
| 		Loading... | ||||
| 		</div> | ||||
| 		<!--pre> | ||||
| 		{{item}} | ||||
| 		</pre--> | ||||
| 		<button v-show="editorMode" v-on:click="editorMode = false">Vypnout editační mód</button> | ||||
| 		<button v-show="!editorMode" v-on:click="editorMode = true">Zapnout editační mód</button> | ||||
| 		<button v-show="debugMode" v-on:click="debugMode = false">Vypnout ladicí mód</button> | ||||
| 		<button v-show="!debugMode" v-on:click="debugMode = true">Zapnout ladicí mód</button> | ||||
| 		<TreeNode :item="item" :editorMode="editorMode" :debugMode="debugMode"/> | ||||
| 	</div> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| import TreeNode from './TreeNode.vue' | ||||
| import axios from 'axios' | ||||
| export default { | ||||
| 	name: 'App', | ||||
| 	components: { | ||||
| 		TreeNode, | ||||
| 	}, | ||||
| 	data: () => ({ | ||||
| 		loading: true, | ||||
| 		item: null, | ||||
| 		tnid: 1, | ||||
| 		editorMode: false, | ||||
| 		debugMode: false, | ||||
| 	}), | ||||
| 	props:{ | ||||
| 		tnid: Number, | ||||
| 		tnsource: String, | ||||
| 		editorMode: Boolean, | ||||
| 		debugMode: Boolean, | ||||
| 	}, | ||||
| 	mounted: function() { | ||||
| 		if (this.tnsource && this.tnsource=='inline'){ | ||||
| 			let data = JSON.parse(document.getElementById('vuedata').textContent); | ||||
| 			this.tnid = data.treenode; | ||||
| 		} | ||||
| 		this.getArticles(); | ||||
| 		this.$root.$on('updateData',(arg) => { | ||||
| 			console.log(arg); | ||||
| 			this.getArticles(); | ||||
| 		}); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		getArticles: function() { | ||||
| 			this.loading = true; | ||||
| 			axios.get('/treenode/'+this.tnid+'/json/') | ||||
| 			.then((response) => { | ||||
| 				this.item = response.data; | ||||
| 				this.loading = false; | ||||
| 				console.log('Data updated'); | ||||
| 				this.$root.$emit('dataUpdated',"dataUpdated"); | ||||
| 			}) | ||||
| 			.catch((err) => { | ||||
| 				this.loading = false; | ||||
| 				console.log(err); | ||||
| 				}) | ||||
| 		} | ||||
| 	}, | ||||
| 	events: { | ||||
| 		updateData: function(){ | ||||
| 			this.getArticles() | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <style> | ||||
| @import '../../../mamweb/static/css/mamweb.css'; | ||||
| </style> | ||||
							
								
								
									
										75
									
								
								vue_frontend/src/components/UlohaVzorakNode.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								vue_frontend/src/components/UlohaVzorakNode.vue
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,75 @@ | |||
| <template> | ||||
| 	<div class="ulohavzoraknode"> | ||||
| 		<!--pre>UlohaVzorakNode {{item}} {{typeof(item)}}</pre--> | ||||
| 		<h5>Řešení {{item.node.uloha.cislo_zadani.poradi}}.{{ item.node.uloha.kod }}: {{ item.node.uloha.nazev }}</h5> | ||||
| 		<button v-if="editorMode" v-on:click="showSelect=!showSelect" class="upravit">Upravit</button> | ||||
| 		<div v-if="showSelect"> | ||||
| 			<form class="searchForm" v-on:submit.prevent="submitSearch"> | ||||
| 				<input type="text" v-model="searchQuery" placeholder="Napište název" @keyup="submitSearch"> | ||||
| 			</form> | ||||
| 			<div class="searchResult" v-show="isResult"> | ||||
| 				<ul> | ||||
| 					<li v-for="res in searchResults" :key="res.id" v-on:click="setSelected(res)">{{res.nazev}}</li> | ||||
| 				</ul> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| import axios from 'axios' | ||||
| 
 | ||||
| export default { | ||||
| 	name: 'UlohaVzorakNode', | ||||
| 	data: () => { | ||||
| 		return { | ||||
| 			isResult: false, | ||||
| 			searchQuery: '', | ||||
| 			searchResults: [], | ||||
| 			showSelect: false, | ||||
| 			selected: null, | ||||
| 			selected_id: null | ||||
| 		} | ||||
| 	}, | ||||
| 	props: { | ||||
| 		item: Object, | ||||
| 		create: Boolean, | ||||
| 		showSelect: Boolean, | ||||
| 		editorMode: Boolean, | ||||
| 	}, | ||||
| 	mounted: function(){ | ||||
| 		if (this.item.node.uloha === null){ | ||||
| 			console.log("Uloha je null!"); | ||||
| 			console.log(this.item); | ||||
| 		} | ||||
| 		if (this.create){ | ||||
| 			this.showSelect = true; | ||||
| 			console.log('Creating'); | ||||
| 		} | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		submitSearch: function(){ | ||||
| 			if (this.searchQuery.length < 3) { return;} | ||||
| 			var reqURL = "/api/ulohavzoraknode/?nazev="+this.searchQuery; | ||||
| 			axios.get(reqURL).then( (response) => { | ||||
| 				this.searchResults = response.data.results; | ||||
| 				this.isResult = true; | ||||
| 				console.log("Got:"); | ||||
| 				console.log(this.searchResults); | ||||
| 			}).catch( (err) => { /* fail response msg */ | ||||
| 				console.log(err); | ||||
| 			}); | ||||
| 		}, | ||||
| 		setSelected: function(res){ | ||||
| 			this.searchQuery = res.nazev | ||||
| 			this.selected_id = res.id | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <style scoped> | ||||
| .upravit { | ||||
| 	margin-top:-40px; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										23
									
								
								vue_frontend/src/components/UlohaZadaniNode.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								vue_frontend/src/components/UlohaZadaniNode.vue
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | |||
| <template> | ||||
| 	<div class="ulohazadaninode"> | ||||
| 		<!--pre>UlohaZadaniNode {{item.node.uloha}} {{typeof(item)}}</pre--> | ||||
| 		<h5>Zadání {{item.node.uloha.cislo_zadani.poradi}}.{{ item.node.uloha.kod }}: {{ item.node.uloha.nazev }}</h5> | ||||
| 	</div> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| export default { | ||||
| 	name: 'UlohaZadaniNode', | ||||
| 	props: { | ||||
| 		item: Object, | ||||
| 		created: Boolean | ||||
| 	, | ||||
| 	mounted: function(){ | ||||
| 		if (this.item.node.uloha === null){ | ||||
| 			console.log("Uloha je null!"); | ||||
| 			console.log(this.item); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										12
									
								
								vue_frontend/src/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								vue_frontend/src/main.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | |||
| import Vue from 'vue' | ||||
| import App from './App.vue' | ||||
| import router from './router' | ||||
| import CKEditor from '@ckeditor/ckeditor5-vue' | ||||
| 
 | ||||
| Vue.config.productionTip = false | ||||
| Vue.use(CKEditor); | ||||
| 
 | ||||
| new Vue({ | ||||
| 	router, | ||||
| 	render: h => h(App), | ||||
| }).$mount('#app') | ||||
							
								
								
									
										33
									
								
								vue_frontend/src/router/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								vue_frontend/src/router/index.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,33 @@ | |||
| import Vue from 'vue' | ||||
| import Router from 'vue-router' | ||||
| import TreeNodeRoot from '../components/TreeNodeRoot.vue' | ||||
| 
 | ||||
| Vue.use(Router) | ||||
| 
 | ||||
| export default new Router({ | ||||
| 	mode: 'history', | ||||
| 	linkActiveClass: 'active', | ||||
| 	routes: [{ | ||||
| 		path: '/temp/vue', | ||||
| 		name: 'treenodedefault', | ||||
| 		props: {'tnid': 23}, | ||||
| 		component: TreeNodeRoot | ||||
| 	}, { | ||||
| 		path: '/temp/vue/:tnid', | ||||
| 		name: 'treenode', | ||||
| 		props: true, | ||||
| 		component: TreeNodeRoot | ||||
| 	}, { | ||||
| 		path: '/zadani/aktualni', | ||||
| 		name: 'treenode_zadani', | ||||
| 		props: {'tnid': 23}, | ||||
| 		component: TreeNodeRoot | ||||
| 	}, { | ||||
| 		path: '/cislo/:cislo', | ||||
| 		name: 'treenode_cislo', | ||||
| 		props: {'tnsource':'inline'}, | ||||
| 		component: TreeNodeRoot | ||||
| 	} | ||||
| 	] | ||||
| }) | ||||
| 
 | ||||
							
								
								
									
										61
									
								
								vue_frontend/vue.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								vue_frontend/vue.config.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,61 @@ | |||
| const BundleTracker = require("webpack-bundle-tracker"); | ||||
| 
 | ||||
| const pages = { | ||||
|     'vue_app_01': { | ||||
|         entry: './src/main.js', | ||||
|         chunks: ['chunk-vendors'] | ||||
|     }, | ||||
| /*    'vue_app_02': { | ||||
|         entry: './src/newhampshir.js', | ||||
|         chunks: ['chunk-vendors'] | ||||
|     }, | ||||
| */ | ||||
| } | ||||
| 
 | ||||
| module.exports = { | ||||
|     pages: pages, | ||||
|     filenameHashing: false, | ||||
|     productionSourceMap: false, | ||||
|     publicPath: process.env.NODE_ENV === 'production' | ||||
|         ? '/static/seminar/vue/' | ||||
|         : 'http://localhost:8080/', | ||||
|     outputDir: '../seminar/static/seminar/vue/', | ||||
| 
 | ||||
|     chainWebpack: config => { | ||||
| 
 | ||||
|         config.optimization | ||||
|             .splitChunks({ | ||||
|                 cacheGroups: { | ||||
|                     vendor: { | ||||
|                         test: /[\\/]node_modules[\\/]/, | ||||
|                         name: "chunk-vendors", | ||||
|                         chunks: "all", | ||||
|                         priority: 1 | ||||
|                     }, | ||||
|                 }, | ||||
|             }); | ||||
| 
 | ||||
|         Object.keys(pages).forEach(page => { | ||||
|             config.plugins.delete(`html-${page}`); | ||||
|             config.plugins.delete(`preload-${page}`); | ||||
|             config.plugins.delete(`prefetch-${page}`); | ||||
|         }) | ||||
| 
 | ||||
|         config | ||||
|             .plugin('BundleTracker') | ||||
|             .use(BundleTracker, [{filename: '../vue_frontend/webpack-stats.json'}]); | ||||
| 
 | ||||
|         config.resolve.alias | ||||
|             .set('__STATIC__', 'static') | ||||
| 
 | ||||
|         config.devServer | ||||
|             .public('http://localhost:8080') | ||||
|             .host('localhost') | ||||
|             .port(8080) | ||||
|             .hotOnly(true) | ||||
|             .watchOptions({poll: 1000}) | ||||
|             .https(false) | ||||
|             .headers({"Access-Control-Allow-Origin": ["*"]}) | ||||
| 
 | ||||
|     } | ||||
| }; | ||||
							
								
								
									
										8544
									
								
								vue_frontend/yarn.lock
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8544
									
								
								vue_frontend/yarn.lock
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
		Loading…
	
		Reference in a new issue
	
	 Pavel "LEdoian" Turinsky
						Pavel "LEdoian" Turinsky