Merge remote-tracking branch 'Gimli/data_migrations' into test
This commit is contained in:
		
						commit
						3408183070
					
				
					 38 changed files with 1976 additions and 170 deletions
				
			
		
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							|  | @ -3,8 +3,8 @@ from seminar.utils import org_required | |||
| from . import views | ||||
| 
 | ||||
| urlpatterns = [ | ||||
| 	path('korektury/', org_required(views.KorekturyAktualniListView.as_view()), name='korektury-list'), | ||||
| 	path('korektury/zastarale/', org_required(views.KorekturyZastaraleListView.as_view()), name='korektury-list'), | ||||
| 	path('korektury/', org_required(views.KorekturyAktualniListView.as_view()), name='korektury_list'), | ||||
| 	path('korektury/zastarale/', org_required(views.KorekturyZastaraleListView.as_view()), name='korektury_stare_list'), | ||||
| 	path('korektury/<int:pdf>/', org_required(views.KorekturyView.as_view()), name='korektury'), | ||||
| 	path('korektury/help/', org_required(views.KorekturyHelpView.as_view()), name='korektury-help'), | ||||
| ] | ||||
|  |  | |||
|  | @ -4,7 +4,12 @@ from seminar import viewsets as vs | |||
| router = routers.DefaultRouter() | ||||
| 
 | ||||
| router.register(r'ulohavzoraknode', vs.UlohaVzorakNodeViewSet,basename='ulohavzoraknode') | ||||
| router.register(r'reseninode', vs.ReseniNodeViewSet,basename='reseninode') | ||||
| router.register(r'text', vs.TextViewSet) | ||||
| router.register(r'textnode', vs.TextNodeViewSet) | ||||
| router.register(r'castnode', vs.CastNodeViewSet) | ||||
| router.register(r'problem', vs.ProblemViewSet, basename='problem') | ||||
| router.register(r'uloha', vs.UlohaViewSet, basename='uloha') | ||||
| router.register(r'reseni', vs.ReseniViewSet, basename='reseni') | ||||
| router.register(r'ulohazadaninode', vs.UlohaZadaniNodeViewSet) | ||||
| 
 | ||||
|  |  | |||
|  | @ -295,6 +295,9 @@ LOGGING = { | |||
|             }, | ||||
|         } | ||||
| 
 | ||||
| # Permissions for uploads | ||||
| FILE_UPLOAD_PERMISSIONS = 0o0644 | ||||
| 
 | ||||
| # MaM specific | ||||
| 
 | ||||
| SEMINAR_RESENI_DIR = os.path.join('reseni') | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ INTERNAL_IPS = ['127.0.0.1'] | |||
| 
 | ||||
| TEMPLATES[0]['OPTIONS']['debug'] = True | ||||
| 
 | ||||
| ALLOWED_HOSTS = ['127.0.0.1'] | ||||
| ALLOWED_HOSTS = ['127.0.0.1', '192.168.43.34'] | ||||
| 
 | ||||
| # Database | ||||
| # https://docs.djangoproject.com/en/1.7/ref/settings/#databases | ||||
|  |  | |||
|  | @ -347,6 +347,10 @@ div.zadani_azad_termin { | |||
| 	bottom: 0px; | ||||
| } | ||||
| 
 | ||||
| #footer p.license a { | ||||
| 	color: #333; | ||||
| } | ||||
| 
 | ||||
| p.license-mobile { | ||||
| 	display: none; | ||||
| } | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| from django.contrib import admin | ||||
| from django.contrib.auth.models import Permission | ||||
| 
 | ||||
| from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter | ||||
| from reversion.admin import VersionAdmin | ||||
|  | @ -18,7 +19,7 @@ admin.site.register(m.Soustredeni) | |||
| 
 | ||||
| @admin.register(m.Osoba) | ||||
| class OsobaAdmin(admin.ModelAdmin): | ||||
| 	actions = ['synchronizuj_maily'] | ||||
| 	actions = ['synchronizuj_maily', 'udelej_orgem'] | ||||
| 
 | ||||
| 	def synchronizuj_maily(self, request, queryset): | ||||
| 		for o in queryset: | ||||
|  | @ -29,6 +30,20 @@ class OsobaAdmin(admin.ModelAdmin): | |||
| 		self.message_user(request, "E-maily synchronizovány.") | ||||
| 	synchronizuj_maily.short_description = "Synchronizuj vybraným osobám e-maily do uživatelů" | ||||
| 
 | ||||
| 	def udelej_orgem(self,request,queryset): | ||||
| 		org_perm = Permission.objects.filter(codename__exact='org').first() | ||||
| 		print(queryset) | ||||
| 		for o in queryset: | ||||
| 			user = o.user | ||||
| 			user.user_permissions.add(org_perm) | ||||
| 			user.is_staff = True | ||||
| 			user.save() | ||||
| 			org = m.Organizator.objects.create(osoba=o) | ||||
| 			org.save() | ||||
| 	udelej_orgem.short_description = "Udělej vybraných osob organizátory" | ||||
| 
 | ||||
| 			 | ||||
| 
 | ||||
| @admin.register(m.Problem) | ||||
| class ProblemAdmin(PolymorphicParentModelAdmin): | ||||
| 	base_model = m.Problem | ||||
|  |  | |||
|  | @ -10,6 +10,21 @@ import seminar.models as m | |||
| from datetime import date | ||||
| import logging | ||||
| 
 | ||||
| # pro přidání políčka do formuláře je potřeba | ||||
| # - mít v modelu tu položku, kterou chci upravovat | ||||
| # - přidat do views (prihlaskaView, resitelEditView) | ||||
| # - přidat do forms | ||||
| # - includovat do html | ||||
| 
 | ||||
| class DateInput(forms.DateInput): | ||||
|     # aby se datum dalo vybírat z kalendáře | ||||
|     input_type = 'date'  | ||||
|      | ||||
| class TelInput(forms.TextInput): | ||||
|      # tohle je možná k niřemu, ale alepsoň to mění input type a nic to nekazí | ||||
|     input_type = 'tel' | ||||
|     input_pattern="^[+]?[()/0-9. -]{9,}$" | ||||
| 
 | ||||
| class LoginForm(forms.Form): | ||||
| 	username = forms.CharField(label='Přihlašovací jméno',  | ||||
| 			max_length=256,  | ||||
|  | @ -42,8 +57,8 @@ class PrihlaskaForm(forms.Form): | |||
| 	pohlavi_muz = forms.ChoiceField(label='Pohlaví', | ||||
| 			choices = ((True,'muž'),(False,'žena')), required=True) | ||||
| 	email = forms.EmailField(label='E-mail',max_length=256, required=True) | ||||
| 	telefon = forms.CharField(label='Telefon',max_length=256, required=False) | ||||
| 	datum_narozeni = forms.DateField(label='Datum narození', required=False) | ||||
| 	telefon = forms.CharField(widget=TelInput(),label='Telefon',max_length=256, required=False) | ||||
| 	datum_narozeni = forms.DateField(widget=DateInput(),label='Datum narození', required=False) | ||||
| 	ulice = forms.CharField(label='Ulice', max_length=256, required=False) | ||||
| 	mesto = forms.CharField(label='Město', max_length=256, required=False) | ||||
| 	psc = forms.CharField(label='PSČ', max_length=32, required=False) | ||||
|  | @ -74,6 +89,8 @@ class PrihlaskaForm(forms.Form): | |||
| 		max_value=date.today().year+8, | ||||
| 		required=True) | ||||
| 	zasilat = forms.ChoiceField(label='Kam zasílat čísla a řešení',choices = Resitel.ZASILAT_CHOICES, required=True) | ||||
| 	zasilat_cislo_emailem = forms.BooleanField(label='Chci dostávat emailem upozornění na vydání nového čísla', required=True) | ||||
| 
 | ||||
| 	gdpr = forms.BooleanField(label='Souhlasím se zpracováním osobních údajů', required=True) | ||||
| 	spam = forms.BooleanField(label='Souhlasím se zasíláním materiálů od MFF UK', required=False) | ||||
| 	 | ||||
|  | @ -135,8 +152,8 @@ class ProfileEditForm(forms.Form): | |||
| 	pohlavi_muz = forms.ChoiceField(label='Pohlaví', | ||||
| 			choices = ((True,'muž'),(False,'žena')), required=True) | ||||
| 	email = forms.EmailField(label='E-mail',max_length=256, required=True) | ||||
| 	telefon = forms.CharField(label='Telefon',max_length=256, required=False) | ||||
| 	datum_narozeni = forms.DateField(label='Datum narození', required=False) | ||||
| 	telefon = forms.CharField(widget=TelInput(),label='Telefon',max_length=256, required=False) | ||||
| 	datum_narozeni = forms.DateField(widget=DateInput(),label='Datum narození', required=False) | ||||
| 	ulice = forms.CharField(label='Ulice', max_length=256, required=False) | ||||
| 	mesto = forms.CharField(label='Město', max_length=256, required=False) | ||||
| 	psc = forms.CharField(label='PSČ', max_length=32, required=False) | ||||
|  | @ -167,6 +184,8 @@ class ProfileEditForm(forms.Form): | |||
| 		max_value=date.today().year+8, | ||||
| 		required=True) | ||||
| 	zasilat = forms.ChoiceField(label='Kam zasílat čísla a řešení',choices = Resitel.ZASILAT_CHOICES, required=True) | ||||
| 	zasilat_cislo_emailem = forms.BooleanField(label='Chci dostávat email s upozorněním na vydání nového čísla', required=True) | ||||
| 
 | ||||
| 	spam = forms.BooleanField(label='Souhlasím se zasíláním materiálů od MFF UK', required=False) | ||||
| #	def clean_username(self): | ||||
| #		err_logger = logging.getLogger('seminar.prihlaska.problem') | ||||
|  | @ -234,7 +253,7 @@ class VlozReseniForm(forms.Form): | |||
| 	#resitele = models.ManyToManyField(Resitel, verbose_name='autoři řešení', | ||||
| 	#	help_text='Seznam autorů řešení', through='Reseni_Resitele') | ||||
| 	 | ||||
| 	cas_doruceni = forms.DateField(label="Čas doručení") | ||||
| 	cas_doruceni = forms.DateField(widget=DateInput(),label="Čas doručení") | ||||
| 
 | ||||
| 	#cas_doruceni = models.DateTimeField('čas_doručení', default=timezone.now, blank=True) | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										19
									
								
								seminar/migrations/0090_auto_20201110_1958.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								seminar/migrations/0090_auto_20201110_1958.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| # Generated by Django 2.2.12 on 2020-11-10 18:58 | ||||
| 
 | ||||
| from django.db import migrations, models | ||||
| import django.db.models.deletion | ||||
| 
 | ||||
| 
 | ||||
| class Migration(migrations.Migration): | ||||
| 
 | ||||
|     dependencies = [ | ||||
|         ('seminar', '0089_cislo_datum_preddeadline'), | ||||
|     ] | ||||
| 
 | ||||
|     operations = [ | ||||
|         migrations.AlterField( | ||||
|             model_name='textnode', | ||||
|             name='text', | ||||
|             field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='seminar.Text', verbose_name='text'), | ||||
|         ), | ||||
|     ] | ||||
							
								
								
									
										18
									
								
								seminar/migrations/0091_resitel_zasilat_cislo_emailem.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								seminar/migrations/0091_resitel_zasilat_cislo_emailem.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | |||
| # Generated by Django 2.2.17 on 2020-12-01 19:53 | ||||
| 
 | ||||
| from django.db import migrations, models | ||||
| 
 | ||||
| 
 | ||||
| class Migration(migrations.Migration): | ||||
| 
 | ||||
|     dependencies = [ | ||||
|         ('seminar', '0090_auto_20201110_1958'), | ||||
|     ] | ||||
| 
 | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='resitel', | ||||
|             name='zasilat_cislo_emailem', | ||||
|             field=models.BooleanField(default=False, help_text='True pokud chce řešitel dostávat číslo emailem', verbose_name='zasílat číslo emailem'), | ||||
|         ), | ||||
|     ] | ||||
|  | @ -260,8 +260,10 @@ class Resitel(SeminarModelBase): | |||
| 		(ZASILAT_DO_SKOLY, 'Do školy'), | ||||
| 		(ZASILAT_NIKAM, 'Nikam'), | ||||
| 		] | ||||
| 
 | ||||
| 	zasilat = models.CharField('kam zasílat', max_length=32, choices=ZASILAT_CHOICES, blank=False, default=ZASILAT_DOMU) | ||||
| 
 | ||||
| 	zasilat_cislo_emailem = models.BooleanField('zasílat číslo emailem', help_text='True pokud chce řešitel dostávat číslo emailem', default=False) | ||||
| 
 | ||||
| 	poznamka = models.TextField('neveřejná poznámka', blank=True, | ||||
| 		help_text='Neveřejná poznámka k řešiteli (plain text)') | ||||
|  | @ -309,25 +311,106 @@ class Resitel(SeminarModelBase): | |||
| 		return sum(h.body for h in list(vsechna_hodnoceni)) | ||||
| 
 | ||||
| 
 | ||||
| 	def get_titul(self, celkove_body=None): | ||||
| 		"Vrati titul" | ||||
| 		if celkove_body is None: | ||||
| 			celkove_body = self.vsechny_body() | ||||
| 	def get_titul(self, body=None): | ||||
| 		"Vrati titul jako řetězec." | ||||
| 		 | ||||
| 		if celkove_body < 10: | ||||
| 			return '' | ||||
| 		elif celkove_body < 20: | ||||
| 			return 'Bc.' | ||||
| 		elif celkove_body < 50: | ||||
| 			return 'Mgr.' | ||||
| 		elif celkove_body < 100: | ||||
| 			return 'Dr.' | ||||
| 		elif celkove_body < 200: | ||||
| 			return 'Doc.' | ||||
| 		elif celkove_body < 500: | ||||
| 			return 'Prof.' | ||||
| 		# Nejprve si zadefinujeme titul | ||||
| 		from enum import Enum | ||||
| 		from functools import total_ordering | ||||
| 		@total_ordering | ||||
| 		class Titul(Enum): | ||||
| 			""" Třída reprezentující možné tituly. Hodnoty jsou dvojice (dolní hranice, stringifikace). """ | ||||
| 			nic =  (0, '') | ||||
| 			bc =   (20, 'Bc.') | ||||
| 			mgr =  (50, 'Mgr.') | ||||
| 			dr =   (100, 'Dr.') | ||||
| 			doc =  (200, 'Doc.') | ||||
| 			prof = (500, 'Prof.') | ||||
| 			akad = (1000, 'Akad.') | ||||
| 
 | ||||
| 			def __lt__(self, other): | ||||
| 				return True if self.value[0] < other.value[0] else False | ||||
| 			def __eq__(self, other): # Měla by být implicitní, ale klidně explicitně. | ||||
| 				return True if self.value[0] == other.value[0] else False | ||||
| 
 | ||||
| 			def __str__(self): | ||||
| 				return self.value[1] | ||||
| 
 | ||||
| 			@classmethod | ||||
| 			def z_bodu(cls, body): | ||||
| 				aktualni = cls.nic | ||||
| 				# TODO: ověřit, že to funguje | ||||
| 				for titul in cls: # Kdyžtak použít __members__.items() | ||||
| 					if titul.value[0] <= body: | ||||
| 						aktualni = titul | ||||
| 					else: | ||||
| 						break | ||||
| 				return aktualni | ||||
| 
 | ||||
| 		# Hledáme body v databázi | ||||
| 		# V listopadu 2020 jsme se na filosofické schůzce shodli o změně hranic titulů: | ||||
| 		#  - body z 25. ročníku a dříve byly shledány dvakrát hodnotnějšími | ||||
| 		#  - proto se započítávají dvojnásobně a byly posunuté hranice titulů | ||||
| 		#  - staré tituly se ale nemají odebrat, pokud řešitel v t.č. minulém (26.) ročníku měl titul, má ho mít pořád. | ||||
| 		hodnoceni_do_25_rocniku = Hodnoceni.objects.filter(cislo_body__rocnik__rocnik__lte=25,reseni__in=self.reseni_set.all()) | ||||
| 		novejsi_hodnoceni = Hodnoceni.objects.filter(reseni__in=self.reseni_set.all()).difference(hodnoceni_do_25_rocniku) | ||||
| 
 | ||||
| 		def body_z_hodnoceni(hh : list): | ||||
| 			return sum(h.body for h in hh) | ||||
| 
 | ||||
| 		stare_body = body_z_hodnoceni(hodnoceni_do_25_rocniku) | ||||
| 		if body is None: | ||||
| 			nove_body = body_z_hodnoceni(novejsi_hodnoceni) | ||||
| 		else: | ||||
| 			return 'Akad.' | ||||
| 			# Zjistíme, kolik bodů jsou staré, tedy hodnotnější | ||||
| 			nove_body = max(0, body - stare_body) # Všechny body nad počet původních hodnotnějších | ||||
| 			stare_body = min(stare_body, body) # Skutečný počet hodnotnějších bodů | ||||
| 		logicke_body = 2*stare_body + nove_body | ||||
| 
 | ||||
| 	 | ||||
| 		# Titul se určí následovně: | ||||
| 		#  - Pokud se řeší body, které jsou starší, než do 26 ročníku (včetně), dáváme tituly postaru. | ||||
| 		#  - Jinak dáváme tituly po novu... | ||||
| 		#  - ... ale titul se nesmí odebrat, pokud se zmenšil. | ||||
| 		def titul_do_26_rocniku(body): | ||||
| 			""" Původní hranice bodů za tituly """ | ||||
| 			if body < 10: | ||||
| 				return Titul.nic | ||||
| 			elif body < 20: | ||||
| 				return Titul.bc | ||||
| 			elif body < 50: | ||||
| 				return Titul.mgr | ||||
| 			elif body < 100: | ||||
| 				return Titul.dr | ||||
| 			elif body < 200: | ||||
| 				return Titul.doc | ||||
| 			elif body < 500: | ||||
| 				return Titul.prof | ||||
| 			else: | ||||
| 				return Titul.akad | ||||
| 
 | ||||
| 		hodnoceni_do_26_rocniku = Hodnoceni.objects.filter(cislo_body__rocnik__rocnik__lte=26,reseni__in=self.reseni_set.all()) | ||||
| 		novejsi_body = body_z_hodnoceni( | ||||
| 			Hodnoceni.objects.filter(reseni__in=self.reseni_set.all()) | ||||
| 			.difference(hodnoceni_do_26_rocniku) | ||||
| 			) | ||||
| 		starsi_body = body_z_hodnoceni(hodnoceni_do_26_rocniku) | ||||
| 		if body is not None: | ||||
| 			# Ještě z toho vybereme ty správně staré body | ||||
| 			novejsi_body = max(0, body - starsi_body) | ||||
| 			starsi_body = min(starsi_body, body) | ||||
| 
 | ||||
| 		# Titul pro 26. ročník | ||||
| 		stary_titul = titul_do_26_rocniku(starsi_body) | ||||
| 		# Titul podle aktuálních pravidel | ||||
| 		novy_titul = Titul.z_bodu(logicke_body) | ||||
| 
 | ||||
| 		if novejsi_body == 0: | ||||
| 			# Žádné nové body -- titul podle starých pravidel | ||||
| 			return str(stary_titul) | ||||
| 		return str(max(novy_titul, stary_titul)) | ||||
| 
 | ||||
| 
 | ||||
| 	def __str__(self): | ||||
| 		return self.osoba.plne_jmeno() | ||||
| 
 | ||||
|  | @ -745,15 +828,18 @@ class Problem(SeminarModelBase,PolymorphicModel): | |||
| 	def verejne(self): | ||||
| 		# 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 | ||||
| 		#cislo_verejne = False | ||||
| 		#if (self.cislo_zadani and self.cislo_zadani.verejne()): | ||||
| 		#	cislo_verejne = True | ||||
| 		 | ||||
| 		return (stav_verejny and cislo_verejne) | ||||
| 		#return (stav_verejny and cislo_verejne) | ||||
| 	verejne.boolean = True | ||||
| 
 | ||||
| 	def verejne_url(self): | ||||
|  | @ -991,7 +1077,7 @@ def aux_generate_filename(self, filename): | |||
| 		unidecode(filename.replace('/', '-').replace('\0', '')) | ||||
| 	) | ||||
| 	datedir = timezone.now().strftime('%Y-%m') | ||||
| 	fname = "{}_{}".format( | ||||
| 	fname = "{}/{}".format( | ||||
| 		timezone.now().strftime('%Y-%m-%d-%H:%M'), | ||||
| 		clean) | ||||
| 	return os.path.join(datedir, fname) | ||||
|  | @ -1044,6 +1130,11 @@ class PrilohaReseni(SeminarModelBase): | |||
| 	def __str__(self): | ||||
| 		return str(self.soubor) | ||||
| 
 | ||||
| 	def split(self): | ||||
| 		"Vrátí cestu rozsekanou po složkách. To se hodí v templatech" | ||||
| 		# Věřím, že tohle funguje, případně použít os.path nebo pathlib. | ||||
| 		return self.soubor.url.split('/') | ||||
| 
 | ||||
| 
 | ||||
| class Pohadka(SeminarModelBase): | ||||
| 	"""Kus pohádky před/za úlohou v čísle""" | ||||
|  | @ -1480,7 +1571,7 @@ class TextNode(TreeNode): | |||
| 		verbose_name = 'Text (Node)' | ||||
| 		verbose_name_plural = 'Text (Node)' | ||||
| 	text = models.ForeignKey(Text, | ||||
| 		on_delete=models.PROTECT, | ||||
| 		on_delete=models.CASCADE, | ||||
| 		verbose_name = 'text') | ||||
| 	 | ||||
| 	def aktualizuj_nazev(self): | ||||
|  | @ -1513,7 +1604,7 @@ class ReseniNode(TreeNode): | |||
| 		verbose_name = 'reseni') | ||||
| 	 | ||||
| 	def aktualizuj_nazev(self): | ||||
| 		self.nazev = "OtisteneReseniNode: "+str(self.reseni) | ||||
| 		self.nazev = "ReseniNode: "+str(self.reseni) | ||||
| 
 | ||||
| 	def getOdkazStr(self): | ||||
| 		return str(self.reseni) | ||||
|  |  | |||
|  | @ -45,6 +45,7 @@ | |||
|           <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> | ||||
| 	  <li><a href="odmeny/{{prevcislo.rocnik.rocnik}}.{{prevcislo.poradi}}/">Odměny</a></li> | ||||
|         </ul> | ||||
|       </div> | ||||
|   {% endif %} | ||||
|  | @ -75,6 +76,7 @@ | |||
|         {% for p in problemy %} | ||||
|         <th class='border-r'><a href="{{ p.verejne_url }}">{{ p.kod_v_rocniku }}</a> | ||||
|         {% endfor %} | ||||
|         {% if ostatni %}<th class='border-r'>Ostatní {% endif %} | ||||
|         <th class='border-r'>Za číslo | ||||
|         <th class='border-r'>Za ročník | ||||
|         <th class='border-r'>Odjakživa | ||||
|  |  | |||
							
								
								
									
										15
									
								
								seminar/templates/seminar/archiv/odmeny.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								seminar/templates/seminar/archiv/odmeny.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| {% extends "base.html" %} | ||||
| 
 | ||||
| {% block content %} | ||||
|   <h1> | ||||
|     {% block nadpis1a %}{% block nadpis1b %} | ||||
|       Odměny {{ cislo }} | ||||
|     {% endblock %}{% endblock %} | ||||
|   </h1> | ||||
|   <ul> | ||||
| 	  {% for z in zmeny %} | ||||
| 	  <li> {{z.jmeno}}: {{z.ftitul}} → {{z.ttitul}}</li> | ||||
| 	  {% endfor %} | ||||
|   </ul>  | ||||
| 
 | ||||
| {% endblock content %} | ||||
							
								
								
									
										47
									
								
								seminar/templates/seminar/odevzdavatko/detail.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								seminar/templates/seminar/odevzdavatko/detail.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,47 @@ | |||
| {% extends "base.html" %} | ||||
| 
 | ||||
| {% block content %} | ||||
| 
 | ||||
| <p>Řešené problémy: {{ object.problem.all | join:", " }}</p> | ||||
| 
 | ||||
| <p>Řešitelé: {{ object.resitele.all | join:", " }}</p> | ||||
| 
 | ||||
| {# https://docs.djangoproject.com/en/3.1/ref/models/instances/#django.db.models.Model.get_FOO_display #} | ||||
| <p>Forma: {{ object.get_forma_display }}, doručeno {{ object.cas_doruceni }}</p> | ||||
| 
 | ||||
| {# Soubory: #} | ||||
| <h3>Přílohy:</h3> | ||||
| {% if object.prilohy.all %} | ||||
| <table> | ||||
| <tr><th>Soubor</th><th>Řešitelova poznámka</th><th>Datum</th></tr> | ||||
| {% for priloha in object.prilohy.all %} | ||||
| <tr> | ||||
| 	<td><a href="{{ priloha.soubor.url }}" download>{{ priloha.split | last }}</a></td> | ||||
| 	<td>{{ priloha.res_poznamka }}</td> | ||||
| 	<td>{{ priloha.vytvoreno }}</td></tr> | ||||
| 	{# TODO: Orgo-poznámka, ideálně jako formulář #} | ||||
| {% endfor %} | ||||
| </table> | ||||
| {% else %} | ||||
| <p>Žádné přílohy</p> | ||||
| {% endif %} | ||||
| 
 | ||||
| {# Hodnocení: #} | ||||
| {# FIXME: Udělat jako formulář #} | ||||
| <h3>Hodnocení:</h3> | ||||
| {% if object.hodnoceni_set.all %} | ||||
| <table> | ||||
| <tr><th>Problém</th><th>Body</th><th>Číslo pro body</th></tr> | ||||
| {% for h in object.hodnoceni_set.all %} | ||||
| <tr> | ||||
| 	<td>{{ h.problem }}</a></td> | ||||
| 	<td>{{ h.body }}</td> | ||||
| 	<td>{{ h.cislo_body }}</td></tr> | ||||
| {% endfor %} | ||||
| </table> | ||||
| {% else %} | ||||
| <p>Ještě nebylo hodnoceno</p> | ||||
| {% endif %} | ||||
| 
 | ||||
| 
 | ||||
| {% endblock %} | ||||
							
								
								
									
										11
									
								
								seminar/templates/seminar/odevzdavatko/seznam.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								seminar/templates/seminar/odevzdavatko/seznam.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| {% extends "base.html" %} | ||||
| 
 | ||||
| {% block content %} | ||||
| 
 | ||||
| <ul> | ||||
| 	{% for obj in object_list %} | ||||
| 	<li><a href="{% url 'odevzdavatko_detail_reseni' pk=obj.id %}">{{ obj }}</a> ({{ obj.get_forma_display }} {{ obj.cas_doruceni }}) | ||||
| 	{% endfor %} | ||||
| </ul> | ||||
| 
 | ||||
| {% endblock %} | ||||
							
								
								
									
										36
									
								
								seminar/templates/seminar/odevzdavatko/tabulka.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								seminar/templates/seminar/odevzdavatko/tabulka.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| {% extends "base.html" %} | ||||
| 
 | ||||
| {% load utils %} {# Možná by mohlo být někde výš v hierarchii templatů... #} | ||||
| 
 | ||||
| {% block content %} | ||||
| 
 | ||||
| <table> | ||||
| 	<tr> | ||||
| 		<td></td> {# Prázdná buňka v levém horním rohu #} | ||||
| 		{% for p in problemy %} | ||||
| 		<th> | ||||
| 			{# TODO: Přehled řešení k problému, odkázaný odsud? #} | ||||
| 			{{ p }} | ||||
| 		</th> | ||||
| 		{% endfor %} | ||||
| 	</tr> | ||||
| 	{% for resitel,hodnoty in radky%} | ||||
| 	<tr> | ||||
| 		<td> | ||||
| 			{# TODO: Chceme mít view i na řešení konkrétního řešitele ke všem problémům? #} | ||||
| 			{{ resitel }} | ||||
| 		</td> | ||||
| 		{% for hodn in hodnoty %} | ||||
| 			<td> | ||||
| 			{% if hodn %} | ||||
| 			<a href="{% url 'odevzdavatko_reseni_resitele_k_problemu' problem=hodn.problem_id resitel=hodn.resitel_id %}"> | ||||
| 				{{ hodn.pocet_reseni }} řeš.<br>{{ hodn.body }} b<br>{{ hodn.posledni_odevzdani|kratke_datum|default_if_none:"Nikdy"|default:"???"}} | ||||
| 			</a> | ||||
| 			{% endif %} | ||||
| 			</td> | ||||
| 		{% endfor %} | ||||
| 	</tr> | ||||
| 	{% endfor %} | ||||
| </table> | ||||
| 
 | ||||
| {% endblock %} | ||||
|  | @ -6,6 +6,17 @@ | |||
|     {{form.media}} | ||||
|     <script src="{% static 'seminar/prihlaska.js' %}"></script> | ||||
| {% endblock %} | ||||
| 
 | ||||
| <!-- | ||||
| 
 | ||||
| # pro přidání políčka do formuláře je potřeba | ||||
| # - mít v modelu tu položku, kterou chci upravovat | ||||
| # - přidat do views (prihlaskaView, resitelEditView) | ||||
| # - přidat do forms | ||||
| # - includovat do html | ||||
| 
 | ||||
| --> | ||||
| 
 | ||||
| {% block content %} | ||||
| <h1> | ||||
|   {% block nadpis1a %}{% block nadpis1b %} | ||||
|  | @ -73,6 +84,7 @@ | |||
|     </h4> | ||||
|      <table class="form"> | ||||
|        {% include "seminar/profil/prihlaska_field.html" with field=form.zasilat %} | ||||
|        {% include "seminar/profil/prihlaska_field.html" with field=form.zasilat_cislo_emailem %} | ||||
|      </table> | ||||
| 
 | ||||
|  <hr> | ||||
|  |  | |||
|  | @ -53,7 +53,7 @@ | |||
|               </td> | ||||
|               <td {% if field.help_text %} class="field-with-comment"{% endif %}> | ||||
|                 {{ field }} | ||||
|                   <span class="field-comment">{{ field.help_text|safe }}</span>> | ||||
|                   <span class="field-comment">{{ field.help_text|safe }}</span> | ||||
|               </td> | ||||
| 
 | ||||
|             <td><span class="field-error">{{ field.errors }}</span></td> | ||||
|  |  | |||
|  | @ -7,6 +7,16 @@ | |||
|     <script src="{% static 'seminar/prihlaska.js' %}"></script> | ||||
| {% endblock %} | ||||
| 
 | ||||
| <!-- | ||||
| 
 | ||||
| # pro přidání políčka do formuláře je potřeba | ||||
| # - mít v modelu tu položku, kterou chci upravovat | ||||
| # - přidat do views (prihlaskaView, resitelEditView) | ||||
| # - přidat do forms | ||||
| # - includovat do html | ||||
| 
 | ||||
| --> | ||||
| 
 | ||||
| {% block content %} | ||||
| <h1> | ||||
|   {% block nadpis1a %}{% block nadpis1b %} | ||||
|  | @ -77,6 +87,7 @@ | |||
|         </h4> | ||||
|          <table class="form"> | ||||
|            {% include "seminar/profil/prihlaska_field.html" with field=form.zasilat %} | ||||
|            {% include "seminar/profil/prihlaska_field.html" with field=form.zasilat_cislo_emailem %} | ||||
|          </table> | ||||
|  <hr> | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										27
									
								
								seminar/templatetags/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								seminar/templatetags/utils.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | |||
| from django import template | ||||
| from datetime import datetime, timedelta | ||||
| from pytz import timezone | ||||
| from mamweb.settings import TIME_ZONE | ||||
| import logging | ||||
| register = template.Library() | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| @register.filter(name='kratke_datum', expects_localtime=True) | ||||
| def kratke_datum(dt): | ||||
| 	# None dává None, ne-datum dává False, aby se daly použít filtry typu "default". | ||||
| 	if dt is None: | ||||
| 		return None | ||||
| 	if not isinstance(dt, datetime): | ||||
| 		logger.warning(f"Špatné volání filtru {__name__}: {dt}") | ||||
| 		return False | ||||
| 	naive_now = datetime.now() | ||||
| 	tz = timezone(TIME_ZONE) | ||||
| 	now = tz.localize(naive_now) | ||||
| 	delta = now - dt | ||||
| 	if delta <= timedelta(days=1): | ||||
| 		return dt.strftime("%k:%M") | ||||
| 	if delta <= timedelta(days=365):	# Timedelta neumí vyjádřit 1 rok | ||||
| 		return dt.strftime("%d. %m.") | ||||
| 	return dt.strftime("%d. %m. %Y") | ||||
| 
 | ||||
|  | @ -200,6 +200,8 @@ def gen_organizatori(rnd, osoby, last_rocnik): | |||
| 				os.user = user | ||||
| 				os.save() | ||||
| 				os.user.user_permissions.add(org_perm) | ||||
| 				os.user.is_staff = True | ||||
| 				os.user.save() | ||||
| 			organizatori.append(Organizator.objects.create(osoba=os, | ||||
| 				organizuje_od=od, organizuje_do=do, strucny_popis_organizatora = popis_orga)) | ||||
| 	return organizatori | ||||
|  |  | |||
|  | @ -13,8 +13,8 @@ urlpatterns = [ | |||
| 	path('co-je-MaM/organizatori/organizovali/', views.CojemamOrganizatoriStariView.as_view(), name='stari_organizatori'), | ||||
| 
 | ||||
| 	# Archiv | ||||
| 	path('archiv/rocniky/', views.ArchivView.as_view()), | ||||
| 	path('archiv/temata/', views.ArchivTemataView.as_view()), | ||||
| 	path('archiv/rocniky/', views.ArchivView.as_view(), name="seninar_archiv_rocniky"), | ||||
| 	path('archiv/temata/', views.ArchivTemataView.as_view(), name="seninar_archiv_temata"), | ||||
| 
 | ||||
| 	path('rocnik/<int:rocnik>/', views.RocnikView.as_view(), name='seminar_rocnik'), | ||||
| 	path('cislo/<int:rocnik>.<str:cislo>/', views.CisloView.as_view(), name='seminar_cislo'), | ||||
|  | @ -90,17 +90,17 @@ urlpatterns = [ | |||
| 		name='seminar_rocnik_vysledkovka' | ||||
| 	), | ||||
| 	path( | ||||
| 		'cislo/<int:rocnik>.<int:cislo>/vysledkovka.tex', | ||||
| 		'cislo/<int:rocnik>.<str:cislo>/vysledkovka.tex', | ||||
| 		org_required(views.CisloVysledkovkaView.as_view()), | ||||
| 		name='seminar_cislo_vysledkovka' | ||||
| 	), | ||||
| 	path( | ||||
| 		'cislo/<int:rocnik>.<int:cislo>/obalky.pdf', | ||||
| 		'cislo/<int:rocnik>.<str:cislo>/obalky.pdf', | ||||
| 		org_required(views.cisloObalkyView), | ||||
| 		name='seminar_cislo_obalky' | ||||
| 	), | ||||
| 	path( | ||||
| 		'cislo/<int:rocnik>.<int:cislo>/tituly.tex', | ||||
| 		'cislo/<int:rocnik>.<str:cislo>/tituly.tex', | ||||
| 		org_required(views.TitulyView), | ||||
| 		name='seminar_cislo_titul' | ||||
| 	), | ||||
|  | @ -110,10 +110,14 @@ urlpatterns = [ | |||
| 		name='stav_databaze' | ||||
| 	), | ||||
| 	path( | ||||
| 		'cislo/<int:rocnik>.<int:cislo>/obalkovani', | ||||
| 		'cislo/<int:rocnik>.<str:cislo>/obalkovani', | ||||
| 		org_required(views.ObalkovaniView.as_view()), | ||||
| 		name='seminar_cislo_resitel_obalkovani' | ||||
| 	), | ||||
| 	path( | ||||
| 		'cislo/<int:trocnik>.<str:tcislo>/odmeny/<int:frocnik>.<str:fcislo>/', | ||||
| 		org_required(views.OdmenyView.as_view()), | ||||
| 		name="seminar_archiv_odmeny"), | ||||
| 	path( | ||||
| 		'soustredeni/<int:soustredeni>/obalky.pdf', | ||||
| 		org_required(views.soustredeniObalkyView), | ||||
|  | @ -168,5 +172,10 @@ urlpatterns = [ | |||
| 	# org_member_required(views.OrganizatorAutocomplete.as_view()), | ||||
| 	# name='seminar_autocomplete_organizator') | ||||
| 
 | ||||
| 	path('temp/reseni/', org_required(views.TabulkaOdevzdanychReseniView.as_view()), name='odevzdavatko_tabulka'), | ||||
| 	path('temp/reseni/<int:problem>/<int:resitel>/', org_required(views.ReseniProblemuView.as_view()), name='odevzdavatko_reseni_resitele_k_problemu'), | ||||
| 	path('temp/reseni/<int:pk>', org_required(views.DetailReseniView.as_view()), name='odevzdavatko_detail_reseni'), | ||||
| 	path('temp/reseni/all', org_required(views.SeznamReseniView.as_view())), | ||||
| 	path('temp/reseni/akt', org_required(views.SeznamAktualnichReseniView.as_view())), | ||||
| 
 | ||||
| ] | ||||
|  |  | |||
|  | @ -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 * | ||||
|  |  | |||
|  | @ -34,7 +34,9 @@ class OdevzdatelnyProblemAutocomplete(autocomplete.Select2QuerySetView): | |||
| 		rocnik = nastaveni.aktualni_rocnik | ||||
| 		temata = m.Tema.objects.filter(rocnik=rocnik, stav=m.Problem.STAV_ZADANY) | ||||
| 		ulohy = m.Uloha.objects.filter(cislo_deadline__rocnik = rocnik) | ||||
| 		clanky = m.Clanek.objects.filter(cislo__rocnik = rocnik, stav=m.Problem.STAV_ZADANY) # FIXME: Je tohle to, co chceme? | ||||
| 		ulohy.union(temata) | ||||
| 		ulohy.union(clanky) | ||||
| 		qs = ulohy | ||||
| 		if self.q: | ||||
| 			qs = qs.filter( | ||||
|  |  | |||
							
								
								
									
										129
									
								
								seminar/views/odevzdavatko.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								seminar/views/odevzdavatko.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,129 @@ | |||
| from django.views.generic import ListView, DetailView | ||||
| from django.views.generic.base import TemplateView | ||||
| 
 | ||||
| from dataclasses import dataclass | ||||
| import datetime | ||||
| 
 | ||||
| import seminar.models as m | ||||
| from seminar.utils import aktivniResitele, resi_v_rocniku | ||||
| 
 | ||||
| # Co chceme? | ||||
| # - "Tabulku" aktuální řešitelé x zveřejněné problémy, v buňkách počet řešení | ||||
| # 	- TabulkaOdevzdanychReseniView | ||||
| # - Detail konkrétního problému a řešitele -- přehled všech řešení odevzdaných k tomuto problému | ||||
| # 	- ReseniProblemuView | ||||
| # - Detail konkrétního řešení -- všechny soubory, datum, ... | ||||
| # 	- DetailReseniView | ||||
| # | ||||
| # Taky se může hodit: | ||||
| # - Tabulka všech řešitelů x všech problémů? | ||||
| 
 | ||||
| @dataclass | ||||
| class SouhrnReseni: | ||||
| 	"""Dataclass reprezentující data o odevzdaných řešeních pro zobrazení v tabulce.""" | ||||
| 	pocet_reseni : int | ||||
| 	posledni_odevzdani : datetime.datetime | ||||
| 	body : float | ||||
| 
 | ||||
| 
 | ||||
| class TabulkaOdevzdanychReseniView(ListView): | ||||
| 	template_name = 'seminar/odevzdavatko/tabulka.html' | ||||
| 	model = m.Hodnoceni | ||||
| 
 | ||||
| 	def get_queryset(self): | ||||
| 		# FIXME: Tenhle blok nemůže být přímo ve třídě, protože před vyrobením databáze neexistuje Nastavení. | ||||
| 		self.akt_rocnik = m.Nastaveni.get_solo().aktualni_rocnik	# .get_solo() vrátí tu jedinou instanci, asi... | ||||
| 		self.resitele = resi_v_rocniku(self.akt_rocnik) | ||||
| 		# NOTE: Protože řešení odkazuje přímo na Problém a QuerySet na Hodnocení je nepolymorfní, musíme porovnávat taky s nepolymorfními Problémy. | ||||
| 		self.zadane_problemy = m.Problem.objects.filter(stav=m.Problem.STAV_ZADANY).non_polymorphic() | ||||
| 
 | ||||
| 		qs = super().get_queryset() | ||||
| 		qs = qs.filter(problem__in=self.zadane_problemy).select_related('reseni', 'problem').prefetch_related('reseni__resitele__osoba') | ||||
| 		return qs | ||||
| 
 | ||||
| 	def get_context_data(self, *args, **kwargs): | ||||
| 		# FIXME: Tenhle blok nemůže být přímo ve třídě, protože před vyrobením databáze neexistuje Nastavení. | ||||
| 		self.akt_rocnik = m.Nastaveni.get_solo().aktualni_rocnik	# .get_solo() vrátí tu jedinou instanci, asi... | ||||
| 		self.resitele = resi_v_rocniku(self.akt_rocnik) | ||||
| 		# NOTE: Protože řešení odkazuje přímo na Problém a QuerySet na Hodnocení je nepolymorfní, musíme porovnávat taky s nepolymorfními Problémy. | ||||
| 		self.zadane_problemy = m.Problem.objects.filter(stav=m.Problem.STAV_ZADANY).non_polymorphic() | ||||
| 
 | ||||
| 		ctx = super().get_context_data(*args, **kwargs) | ||||
| 		ctx['problemy'] = self.zadane_problemy | ||||
| 		ctx['resitele'] = self.resitele | ||||
| 		tabulka = dict() | ||||
| 
 | ||||
| 		def pridej_reseni(problem, resitel, body, cas): | ||||
| 			if problem not in tabulka: | ||||
| 				tabulka[problem] = dict() | ||||
| 			if resitel not in tabulka[problem]: | ||||
| 				tabulka[problem][resitel] = SouhrnReseni(pocet_reseni=1, posledni_odevzdani=cas, body=body) | ||||
| 			else: | ||||
| 				tabulka[problem][resitel].posledni_odevzdani = max(tabulka[problem][resitel].posledni_odevzdani, cas) | ||||
| 				tabulka[problem][resitel].body = max(tabulka[problem][resitel].body, body, | ||||
| 					key=lambda x: x if x is not None else -1 # None je malé číslo | ||||
| 					# FIXME: Možná dává smysl i mít None jako velké číslo -- jakože "TODO: zadat body" | ||||
| 					) | ||||
| 				tabulka[problem][resitel].pocet_reseni += 1 | ||||
| 			# Pro jednoduchost template si ještě poznamenáme ID problému a řešitele | ||||
| 			tabulka[problem][resitel].problem_id = problem.id | ||||
| 			tabulka[problem][resitel].resitel_id = resitel.id | ||||
| 		 | ||||
| 		for hodnoceni in self.get_queryset(): | ||||
| 			for resitel in hodnoceni.reseni.resitele.all(): | ||||
| 				pridej_reseni(hodnoceni.problem, resitel, hodnoceni.body, hodnoceni.reseni.cas_doruceni) | ||||
| 
 | ||||
| 		hodnoty = [] | ||||
| 		for resitel in self.resitele: | ||||
| 			resiteluv_radek = [] | ||||
| 			for problem in self.zadane_problemy: | ||||
| 				if problem in tabulka and resitel in tabulka[problem]: | ||||
| 					resiteluv_radek.append(tabulka[problem][resitel]) | ||||
| 				else: | ||||
| 					resiteluv_radek.append(None) | ||||
| 			hodnoty.append(resiteluv_radek) | ||||
| 		ctx['radky'] = list(zip(self.resitele, hodnoty)) | ||||
| 
 | ||||
| 		return ctx | ||||
| 
 | ||||
| class ReseniProblemuView(ListView): | ||||
| 	model = m.Reseni | ||||
| 	template_name = 'seminar/odevzdavatko/seznam.html' | ||||
| 	 | ||||
| 	def get_queryset(self): | ||||
| 		qs = super().get_queryset() | ||||
| 		resitel_id = self.kwargs['resitel'] | ||||
| 		if resitel_id is None: | ||||
| 			raise ValueError("Nemám řešitele!") | ||||
| 		problem_id = self.kwargs['problem'] | ||||
| 		if problem_id is None: | ||||
| 			raise ValueError("Nemám problém! (To je problém!)") | ||||
| 		 | ||||
| 		resitel = m.Resitel.objects.get(id=resitel_id) | ||||
| 		problem = m.Problem.objects.get(id=problem_id) | ||||
| 		qs = qs.filter( | ||||
| 			problem__in=[problem], | ||||
| 			resitele__in=[resitel], | ||||
| 			) | ||||
| 		return qs | ||||
| 	 | ||||
| 	# Kontext automaticky? | ||||
| 
 | ||||
| class DetailReseniView(DetailView): | ||||
| 	model = m.Reseni | ||||
| 	template_name = 'seminar/odevzdavatko/detail.html' | ||||
| 	# To je všechno? Najde se to podle pk... | ||||
| 
 | ||||
| # Přehled všech řešení kvůli debugování | ||||
| 
 | ||||
| class SeznamReseniView(ListView): | ||||
| 	model = m.Reseni | ||||
| 	template_name = 'seminar/odevzdavatko/seznam.html' | ||||
| 
 | ||||
| class SeznamAktualnichReseniView(SeznamReseniView): | ||||
| 	def get_queryset(self): | ||||
| 		qs = super().get_queryset() | ||||
| 		akt_rocnik = m.Nastaveni.get_solo().aktualni_rocnik	# .get_solo() vrátí tu jedinou instanci, asi... | ||||
| 		resitele = resi_v_rocniku(akt_rocnik) | ||||
| 		qs = qs.filter(resitele__in=resitele)	# FIXME: Najde řešení i ze starých ročníků, která odevzdal alespoň jeden aktuální řešitel | ||||
| 		return qs | ||||
|  | @ -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 | ||||
|  | @ -17,6 +17,7 @@ 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 | ||||
|  | @ -120,15 +121,57 @@ class TNLData(object): | |||
| 			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 from_treenode(cls,anode,parent=None,index=None): | ||||
| 		out = cls(anode,parent,index) | ||||
| 		for (idx,ch) in enumerate(treelib.all_children(anode)): | ||||
| 			# FIXME přidat filtrování na veřejnost | ||||
| 			outitem = cls.from_treenode(ch,out,idx) | ||||
| 	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 | ||||
|  | @ -195,7 +238,7 @@ class TreeNodeView(generic.DetailView): | |||
| 
 | ||||
| 	def get_context_data(self,**kwargs): | ||||
| 		context = super().get_context_data(**kwargs) | ||||
| 		context['tnldata'] = TNLData.from_treenode(self.object) | ||||
| 		context['tnldata'] = TNLData.from_treenode(self.object,self.request.user) | ||||
| 		return context | ||||
| 
 | ||||
| class TreeNodeJSONView(generic.DetailView): | ||||
|  | @ -203,7 +246,7 @@ class TreeNodeJSONView(generic.DetailView): | |||
| 
 | ||||
| 	def get(self,request,*args, **kwargs): | ||||
| 		self.object = self.get_object() | ||||
| 		data = TNLData.from_treenode(self.object).to_json() | ||||
| 		data = TNLData.from_treenode(self.object,self.request.user).to_json() | ||||
| 		return JsonResponse(data) | ||||
| 
 | ||||
| 
 | ||||
|  | @ -332,6 +375,7 @@ class ProblemView(generic.DetailView): | |||
| 
 | ||||
| 	def get_context_data(self, **kwargs): | ||||
| 		context = super().get_context_data(**kwargs) | ||||
| 		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: | ||||
|  | @ -339,11 +383,11 @@ class ProblemView(generic.DetailView): | |||
| 			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) | ||||
| 			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) | ||||
| 			tnl_vzorak = TNLData.from_treenode(self.object.ulohavzoraknode) | ||||
| 			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 | ||||
|  | @ -385,16 +429,16 @@ class AktualniZadaniView(generic.TemplateView): | |||
| #			) | ||||
| # | ||||
| 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) | ||||
| 	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) | ||||
|  | @ -945,19 +989,31 @@ def secti_body_za_cislo(cislo, aktivni_resitele, hlavni_problemy=None): | |||
| 	if hlavni_problemy is None: | ||||
| 		hlavni_problemy = hlavni_problemy_cisla(cislo) | ||||
| 
 | ||||
| 	def ne_clanek_ne_konfera(problem): | ||||
| 		return not(isinstance(problem.get_real_instance(), m.Clanek) or isinstance(problem.get_real_instance(), m.Konfera)) | ||||
| 
 | ||||
| 	temata_a_spol = list(filter(ne_clanek_ne_konfera, hlavni_problemy)) | ||||
| 
 | ||||
| 	def cosi(problem): | ||||
| 		return problem.id | ||||
| 
 | ||||
| 	hlavni_problemy_slovnik = {} | ||||
| 	for hp in hlavni_problemy: | ||||
| 	for hp in temata_a_spol: | ||||
| 		hlavni_problemy_slovnik[hp.id] = {} | ||||
| 
 | ||||
| 	hlavni_problemy_slovnik[-1] = {} | ||||
| 
 | ||||
| 	# zakládání prázdných záznamů pro řešitele | ||||
| 	cislobody = {} | ||||
| 	for ar in aktivni_resitele: | ||||
| 		# řešitele převedeme na řetězec pomocí unikátního id | ||||
| 		cislobody[ar.id] = "" | ||||
| 		for hp in hlavni_problemy: | ||||
| 		for hp in temata_a_spol: | ||||
| 			slovnik = hlavni_problemy_slovnik[hp.id] | ||||
| 			slovnik[ar.id] = "" | ||||
| 
 | ||||
| 		hlavni_problemy_slovnik[-1][ar.id] = "" | ||||
| 	 | ||||
| 	# vezmeme všechna řešení s body do daného čísla | ||||
| 	reseni_do_cisla = Reseni.objects.prefetch_related('problem', 'resitele',  | ||||
| 				'hodnoceni_set').filter(hodnoceni__cislo_body=cislo) | ||||
|  | @ -969,7 +1025,10 @@ def secti_body_za_cislo(cislo, aktivni_resitele, hlavni_problemy=None): | |||
| 		# řešení může řešit více problémů | ||||
| 		for prob in list(reseni.problem.all()): | ||||
| 			nadproblem = hlavni_problem(prob) | ||||
| 			nadproblem_slovnik = hlavni_problemy_slovnik[nadproblem.id] | ||||
| 			if ne_clanek_ne_konfera(nadproblem): | ||||
| 				nadproblem_slovnik = hlavni_problemy_slovnik[nadproblem.id] | ||||
| 			else: | ||||
| 				nadproblem_slovnik = hlavni_problemy_slovnik[-1] | ||||
| 			 | ||||
| 			# a mít více hodnocení | ||||
| 			for hodn in list(reseni.hodnoceni_set.all()): | ||||
|  | @ -1014,11 +1073,26 @@ def vysledkovka_cisla(cislo, context=None): | |||
| 	# vytvoříme jednotlivé sloupce výsledkovky | ||||
| 	radky_vysledkovky = [] | ||||
| 	i = 0 | ||||
| 
 | ||||
| 	def ne_clanek_ne_konfera(problem): | ||||
| 		return not(isinstance(problem.get_real_instance(), m.Clanek) or isinstance(problem.get_real_instance(), m.Konfera)) | ||||
| 
 | ||||
| 	temata_a_spol = list(filter(ne_clanek_ne_konfera, hlavni_problemy)) | ||||
| 
 | ||||
| 	# def not_empty(value): | ||||
| 	# 	return value != '' | ||||
| 	# | ||||
| 	# je_nejake_ostatni = any(filter(not_empty, hlavni_problemy_slovnik[-1].values())) > 0 | ||||
| 
 | ||||
| 	je_nejake_ostatni = len(hlavni_problemy) - len(temata_a_spol) > 0 | ||||
| 
 | ||||
| 	for ar_id in setrizeni_resitele_id: | ||||
| 		# získáme seznam bodů za problémy pro daného řešitele | ||||
| 		problemy = [] 		 | ||||
| 		for hp in hlavni_problemy: | ||||
| 		for hp in temata_a_spol: | ||||
| 			problemy.append(hlavni_problemy_slovnik[hp.id][ar_id]) | ||||
| 		if je_nejake_ostatni: | ||||
| 			problemy.append(hlavni_problemy_slovnik[-1][ar_id]) | ||||
| 		# vytáhneme informace pro daného řešitele | ||||
| 		radek = RadekVysledkovkyCisla( | ||||
| 			poradi[i], # pořadí | ||||
|  | @ -1034,7 +1108,8 @@ def vysledkovka_cisla(cislo, context=None): | |||
| 	# vytahané informace předáváme do kontextu | ||||
| 	context['cislo'] = cislo | ||||
| 	context['radky_vysledkovky'] = radky_vysledkovky | ||||
| 	context['problemy'] = hlavni_problemy | ||||
| 	context['problemy'] = temata_a_spol | ||||
| 	context['ostatni'] = je_nejake_ostatni | ||||
| 	#context['v_cisle_zadane'] = TODO | ||||
| 	#context['resene_problemy'] = resene_problemy | ||||
| 	return context | ||||
|  | @ -1063,6 +1138,7 @@ class CisloView(generic.DetailView): | |||
| 		context = super(CisloView, self).get_context_data(**kwargs) | ||||
| 
 | ||||
| 		cislo = context['cislo'] | ||||
| 		context['prevcislo'] = Cislo.objects.filter((Q(rocnik__lt=self.object.rocnik) | Q(poradi__lt=self.object.poradi))&Q(rocnik__lte=self.object.rocnik)).first() | ||||
| 		# vrátíme context (aktuálně obsahuje jen věci ohledně výsledkovky | ||||
| 		return vysledkovka_cisla(cislo, context) | ||||
| 
 | ||||
|  | @ -1079,6 +1155,30 @@ class ArchivTemataView(generic.ListView): | |||
| 			ctx['rocniky'][rocnik] = list(temata) | ||||
| 		return ctx | ||||
| 
 | ||||
| class OdmenyView(generic.TemplateView): | ||||
| 	template_name = 'seminar/archiv/odmeny.html' | ||||
| 
 | ||||
| 	def get_context_data(self, **kwargs): | ||||
| 		context = super().get_context_data(**kwargs) | ||||
| 		fromcislo = Cislo.objects.get(rocnik=self.kwargs.get('frocnik'), poradi=self.kwargs.get('fcislo')) | ||||
| 		tocislo = Cislo.objects.get(rocnik=self.kwargs.get('trocnik'), poradi=self.kwargs.get('tcislo')) | ||||
| 		resitele = aktivniResitele(tocislo) | ||||
| 		frombody = body_resitelu(resitele, fromcislo) | ||||
| 		tobody = body_resitelu(resitele, tocislo) | ||||
| 		outlist = [] | ||||
| 		for (aid, tbody) in tobody.items(): | ||||
| 			fbody = frombody.get(aid,0) | ||||
| 			resitel = Resitel.objects.get(pk=aid) | ||||
| 			ftitul = resitel.get_titul(fbody) | ||||
| 			ttitul = resitel.get_titul(tbody) | ||||
| 			if ftitul != ttitul: | ||||
| 				outlist.append({'jmeno': resitel.osoba.plne_jmeno(), 'ftitul': ftitul, 'ttitul': ttitul}) | ||||
| 		context['zmeny'] = outlist | ||||
| 		return context | ||||
| 		 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| ### Generovani vysledkovky | ||||
| 
 | ||||
| class CisloVysledkovkaView(CisloView): | ||||
|  | @ -1332,6 +1432,12 @@ class ResitelView(LoginRequiredMixin,generic.DetailView): | |||
| 
 | ||||
| ### Formulare | ||||
| 
 | ||||
| # pro přidání políčka do formuláře je potřeba | ||||
| # - mít v modelu tu položku, kterou chci upravovat | ||||
| # - přidat do views (prihlaskaView, resitelEditView) | ||||
| # - přidat do forms | ||||
| # - includovat do html | ||||
| 
 | ||||
| class AddSolutionView(LoginRequiredMixin, FormView): | ||||
| 	template_name = 'seminar/org/vloz_reseni.html' | ||||
| 	form_class = f.VlozReseniForm | ||||
|  | @ -1409,7 +1515,7 @@ def prihlaska_log_gdpr_safe(logger, gdpr_logger, msg, form_data): | |||
| from django.forms.models import model_to_dict | ||||
| def resitelEditView(request): | ||||
| 	err_logger = logging.getLogger('seminar.prihlaska.problem') | ||||
| 	## Načtení objektu Osoba a Resitel, patrici k aktuálně přihlášenému uživately | ||||
| 	## Načtení objektů Osoba a Resitel patřících k aktuálně přihlášenému uživateli | ||||
| 	u = request.user | ||||
| 	osoba_edit = Osoba.objects.get(user=u) | ||||
| 	resitel_edit = osoba_edit.resitel | ||||
|  | @ -1448,6 +1554,7 @@ def resitelEditView(request): | |||
| 			resitel_edit.skola = fcd['skola'] | ||||
| 			resitel_edit.rok_maturity = fcd['rok_maturity'] | ||||
| 			resitel_edit.zasilat = fcd['zasilat'] | ||||
| 			resitel_edit.zasilat_cislo_emailem = fcd['zasilat_cislo_emailem'] | ||||
| 			if fcd.get('skola'): | ||||
| 				resitel_edit.skola = fcd['skola'] | ||||
| 			else: | ||||
|  | @ -1511,7 +1618,8 @@ def prihlaskaView(request): | |||
| 
 | ||||
| 				r = Resitel( | ||||
| 					rok_maturity = fcd['rok_maturity'], | ||||
| 					zasilat = fcd['zasilat'] | ||||
| 					zasilat = fcd['zasilat'], | ||||
| 					zasilat_cislo_emailem = fcd['zasilat_cislo_emailem'] | ||||
| 					) | ||||
| 				 | ||||
| 				r.save() | ||||
|  |  | |||
|  | @ -6,24 +6,26 @@ from seminar import treelib | |||
| 
 | ||||
| DEFAULT_NODE_DEPTH = 2 | ||||
| 
 | ||||
| 
 | ||||
| class TextSerializer(serializers.ModelSerializer): | ||||
| 	class Meta: | ||||
| 		model = m.Text | ||||
| 		fields = '__all__' | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class UlohaVzorakNodeSerializer(serializers.ModelSerializer): | ||||
| class ProblemSerializer(serializers.ModelSerializer): | ||||
| 	class Meta: | ||||
| 		model = m.UlohaVzorakNode | ||||
| 		model = m.Problem | ||||
| 		fields = '__all__' | ||||
| 		depth = DEFAULT_NODE_DEPTH | ||||
| 
 | ||||
| class UlohaZadaniNodeSerializer(serializers.ModelSerializer): | ||||
| class UlohaSerializer(serializers.ModelSerializer): | ||||
| 	class Meta: | ||||
| 		model = m.UlohaZadaniNode | ||||
| 		model = m.Uloha | ||||
| 		fields = '__all__' | ||||
| 
 | ||||
| class ReseniSerializer(serializers.ModelSerializer): | ||||
| 	class Meta: | ||||
| 		model = m.Reseni | ||||
| 		fields = '__all__' | ||||
| 		depth = DEFAULT_NODE_DEPTH | ||||
| 	 | ||||
| class RocnikNodeSerializer(serializers.ModelSerializer): | ||||
| 	class Meta: | ||||
|  | @ -138,12 +140,161 @@ class CastNodeCreateSerializer(serializers.ModelSerializer): | |||
| 		fields = ('nadpis','where','refnode') | ||||
| 		depth = DEFAULT_NODE_DEPTH | ||||
| 
 | ||||
| class UlohaZadaniNodeSerializer(serializers.ModelSerializer): | ||||
| 	class Meta: | ||||
| 		model = m.UlohaZadaniNode | ||||
| 		fields = '__all__' | ||||
| 		depth = DEFAULT_NODE_DEPTH | ||||
| 
 | ||||
| class UlohaZadaniNodeWriteSerializer(serializers.ModelSerializer): | ||||
| 	uloha = UlohaSerializer() | ||||
| 
 | ||||
| 	def update(self,node,validated_data): | ||||
| 		node.uloha.max_body = validated_data.get('uloha').get('max_body') | ||||
| 		node.uloha.kod = validated_data.get('uloha').get('kod') | ||||
| 		node.uloha.nazev = validated_data.get('uloha').get('nazev') | ||||
| 		node.uloha.save() | ||||
| 		return node | ||||
| 
 | ||||
| 	class Meta: | ||||
| 		model = m.TextNode | ||||
| 		fields = ('id','uloha') | ||||
| 		depth = DEFAULT_NODE_DEPTH | ||||
| 
 | ||||
| class UlohaZadaniNodeCreateSerializer(serializers.ModelSerializer): | ||||
| 	uloha = UlohaSerializer() | ||||
| 	refnode = serializers.IntegerField() | ||||
| 	where = serializers.CharField() | ||||
| 
 | ||||
| 	def create(self,validated_data): | ||||
| 		# text_zadani = validated_data.pop('text_zadani') | ||||
| 		temp_uloha = validated_data.pop('uloha') | ||||
| 		where = validated_data.pop('where') | ||||
| 		refnode_id = validated_data.pop('refnode') | ||||
| 		refnode = m.TreeNode.objects.get(pk=refnode_id) | ||||
| 
 | ||||
| 		# Z cesty ke koreni stromu zjistime, v jakem jsme tematu a v jakem cisle | ||||
| 		cislo = None | ||||
| 		tema = None | ||||
| 		travelnode = refnode | ||||
| 		while travelnode is not None: | ||||
| 			if isinstance(travelnode, m.TemaVCisleNode): | ||||
| 				tema = travelnode.tema | ||||
| 			if isinstance(travelnode, m.CisloNode): | ||||
| 				cislo = travelnode.cislo | ||||
| 			travelnode = treelib.get_parent(travelnode) | ||||
| 		# Vyrobime ulohu | ||||
| 		uloha = m.Uloha.objects.create(cislo_zadani=cislo, nadproblem = tema, **temp_uloha) | ||||
| 		 | ||||
| 		# A vyrobime UlohaZadaniNode | ||||
| 		if where == 'syn': | ||||
| 			node = treelib.create_child(refnode,m.UlohaZadaniNode,uloha = uloha) | ||||
| 		elif where == 'za': | ||||
| 			node = treelib.create_node_after(refnode,m.UlohaZadaniNode,uloha = uloha) | ||||
| 		elif where == 'pred': | ||||
| 			node = treelib.create_node_before(refnode,m.UlohaZadaniNode,uloha = uloha) | ||||
| 		node.where = None | ||||
| 		node.refnode = None | ||||
| 		node.max_body = None | ||||
| 		node.kod = None | ||||
| 		return node | ||||
| 
 | ||||
| 	class Meta: | ||||
| 		model = m.UlohaZadaniNode | ||||
| 		fields = ('uloha','where','refnode') | ||||
| 		depth = DEFAULT_NODE_DEPTH | ||||
| 
 | ||||
| class UlohaVzorakNodeSerializer(serializers.ModelSerializer): | ||||
| 	class Meta: | ||||
| 		model = m.UlohaVzorakNode | ||||
| 		fields = '__all__' | ||||
| 		depth = DEFAULT_NODE_DEPTH | ||||
| 	 | ||||
| class UlohaVzorakNodeWriteSerializer(serializers.ModelSerializer): | ||||
| 	uloha = serializers.PrimaryKeyRelatedField(queryset=m.Uloha.objects.all(), many=False, read_only=False) | ||||
| 
 | ||||
| 	class Meta: | ||||
| 		model = m.UlohaVzorakNode | ||||
| 		fields = ('id','uloha') | ||||
| 		depth = DEFAULT_NODE_DEPTH | ||||
| 	 | ||||
| 
 | ||||
| class UlohaVzorakNodeCreateSerializer(serializers.ModelSerializer): | ||||
| 	uloha_id = serializers.IntegerField() | ||||
| 	refnode = serializers.IntegerField() | ||||
| 	where = serializers.CharField() | ||||
| 
 | ||||
| 	def create(self, validated_data): | ||||
| 		uloha_id = validated_data.pop('uloha_id') | ||||
| 		uloha = m.Uloha.objects.get(pk=uloha_id) | ||||
| 		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.UlohaVzorakNode,uloha = uloha) | ||||
| 		elif where == 'za': | ||||
| 			node = treelib.create_node_after(refnode,m.UlohaVzorakNode,uloha = uloha) | ||||
| 		elif where == 'pred': | ||||
| 			node = treelib.create_node_before(refnode,m.UlohaVzorakNode,uloha = uloha) | ||||
| 		node.refnode = None | ||||
| 		node.where = None | ||||
| 		node.uloha_id = None | ||||
| 		 | ||||
| 		return node | ||||
| 
 | ||||
| 	class Meta: | ||||
| 		model = m.UlohaVzorakNode | ||||
| 		fields = ('refnode', 'uloha_id', 'where') | ||||
| 		depth = DEFAULT_NODE_DEPTH | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class ReseniNodeSerializer(serializers.ModelSerializer): | ||||
| 	class Meta: | ||||
| 		model = m.ReseniNode | ||||
| 		fields = '__all__' | ||||
| 		depth = DEFAULT_NODE_DEPTH | ||||
| 
 | ||||
| class ReseniNodeWriteSerializer(serializers.ModelSerializer): | ||||
| 	reseni = serializers.PrimaryKeyRelatedField(queryset=m.Reseni.objects.all(), many=False, read_only=False) | ||||
| 
 | ||||
| 	class Meta: | ||||
| 		model = m.ReseniNode | ||||
| 		fields = ('id','reseni') | ||||
| 		depth = DEFAULT_NODE_DEPTH | ||||
| 
 | ||||
| class ReseniNodeCreateSerializer(serializers.ModelSerializer): | ||||
| 	reseni_id = serializers.IntegerField() | ||||
| 	refnode = serializers.IntegerField() | ||||
| 	where = serializers.CharField() | ||||
| 
 | ||||
| 	def create(self,validated_data): | ||||
| 		# text_zadani = validated_data.pop('text_zadani') | ||||
| 		reseni_id = validated_data.pop('reseni_id') | ||||
| 		reseni = m.Reseni.objects.get(pk=reseni_id) | ||||
| 		where = validated_data.pop('where') | ||||
| 		refnode_id = validated_data.pop('refnode') | ||||
| 		refnode = m.TreeNode.objects.get(pk=refnode_id) | ||||
| 		 | ||||
| 		# A vyrobime UlohaZadaniNode | ||||
| 		if where == 'syn': | ||||
| 			node = treelib.create_child(refnode,m.ReseniNode,reseni = reseni) | ||||
| 		elif where == 'za': | ||||
| 			node = treelib.create_node_after(refnode,m.ReseniNode,reseni = reseni) | ||||
| 		elif where == 'pred': | ||||
| 			node = treelib.create_node_before(refnode,m.ReseniNode,reseni = reseni) | ||||
| 		node.where = None | ||||
| 		node.refnode = None | ||||
| 		node.reseni_id = None | ||||
| 		return node | ||||
| 
 | ||||
| 	class Meta: | ||||
| 		model = m.ReseniNode | ||||
| 		fields = ('reseni_id','where','refnode') | ||||
| 		depth = DEFAULT_NODE_DEPTH | ||||
| 
 | ||||
| 
 | ||||
| class TreeNodeSerializer(PolymorphicSerializer): | ||||
| 	model_serializer_mapping = { | ||||
|  |  | |||
|  | @ -1,4 +1,7 @@ | |||
| from rest_framework import viewsets,filters | ||||
| from rest_framework import status | ||||
| from rest_framework.response import Response | ||||
| from django.core.exceptions import PermissionDenied | ||||
| from rest_framework.permissions import BasePermission, AllowAny | ||||
| from . import models as m | ||||
| from . import views | ||||
|  | @ -18,31 +21,6 @@ class PermissionMixin(object): | |||
| 		# návštěvník nemusí být zalogován, aby si prohlížel obsah | ||||
| 		return [permission() for permission in permission_classes] | ||||
| 
 | ||||
| 	def verejne_nad(self, node): | ||||
| 		""" Returns output of verejne for closest Rocnik, Cislo or Problem above. | ||||
| 		(All of them have method verejne.)""" | ||||
| 		parent = get_parent(node) | ||||
| 		while True: | ||||
| 			rocnik = isinstance(parent, RocnikNode) | ||||
| 			cislo = isinstance(parent, CisloNode) | ||||
| 			problem = isinstance(parent, ProblemNode) | ||||
| 
 | ||||
| 			if (rocnik or cislo or problem): | ||||
| 				break | ||||
| 			else: | ||||
| 				parent = get_parent(parent) | ||||
| 		if rocnik: | ||||
| 			return parent.rocnik.verejne() | ||||
| 		elif cislo: | ||||
| 			return parent.cislo.verejne() | ||||
| 		elif problem: | ||||
| 			return parent.problem.verjne() | ||||
| 
 | ||||
| 	def has_object_permission(self, request, view, obj): | ||||
| 		# test that obj is Node | ||||
| 		assert isinstance(obj, Node) | ||||
| 		return verejne_nad(node) | ||||
| 
 | ||||
| class ReadWriteSerializerMixin(object): | ||||
| 	""" | ||||
| 	Overrides get_serializer_class to choose the read serializer | ||||
|  | @ -87,10 +65,6 @@ class ReadWriteSerializerMixin(object): | |||
| 		) | ||||
| 		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 | ||||
|  | @ -107,12 +81,91 @@ class CastNodeViewSet(PermissionMixin, ReadWriteSerializerMixin,viewsets.ModelVi | |||
| 	write_serializer_class = views.CastNodeSerializer | ||||
| 	create_serializer_class = views.CastNodeCreateSerializer | ||||
| 
 | ||||
| class UlohaVzorakNodeViewSet(PermissionMixin, viewsets.ModelViewSet): | ||||
| 	serializer_class = views.UlohaVzorakNodeSerializer | ||||
| 	def destroy(self, request, *args, **kwargs): | ||||
| 		obj = self.get_object() | ||||
| 		print(obj) | ||||
| 		if obj.first_child is None: | ||||
| 			return super().destroy(request,*args,**kwargs) | ||||
| 		raise PermissionDenied('Nelze smazat CastNode, který má děti!') | ||||
| 
 | ||||
| 
 | ||||
| class UlohaVzorakNodeViewSet(PermissionMixin, ReadWriteSerializerMixin, viewsets.ModelViewSet): | ||||
| 	read_serializer_class = views.UlohaVzorakNodeSerializer | ||||
| 	write_serializer_class = views.UlohaVzorakNodeWriteSerializer | ||||
| 	create_serializer_class = views.UlohaVzorakNodeCreateSerializer | ||||
| 
 | ||||
| 	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) | ||||
| 
 | ||||
| 		nadproblem = self.request.query_params.get('nadproblem',None) | ||||
| 		if nadproblem is not None: | ||||
| 			queryset = queryset.filter(nadproblem__pk = nadproblem) | ||||
| 		return queryset | ||||
| 
 | ||||
| class ReseniViewSet(viewsets.ModelViewSet): | ||||
| 	serializer_class = views.ReseniSerializer | ||||
| 
 | ||||
| 	def get_queryset(self): | ||||
| 		queryset = m.Reseni.objects.all() | ||||
| 		#FIXME upravit nazvy dle skutecnych polozek reseni | ||||
| 		nazev = self.request.query_params.get('nazev',None) | ||||
| 		if nazev is not None: | ||||
| 			queryset = queryset.filter(nazev__contains=nazev) | ||||
| 		nadproblem = self.request.query_params.get('nadproblem',None) | ||||
| 		if nadproblem is not None: | ||||
| 			queryset = queryset.filter(nadproblem__pk = nadproblem) | ||||
| 		return queryset | ||||
| 
 | ||||
| class UlohaViewSet(viewsets.ModelViewSet): | ||||
| 	serializer_class = views.UlohaSerializer | ||||
| 
 | ||||
| 	def get_queryset(self): | ||||
| 		queryset = m.Uloha.objects.all() | ||||
| 		nazev = self.request.query_params.get('nazev',None) | ||||
| 		if nazev is not None: | ||||
| 			queryset = queryset.filter(nazev__contains=nazev) | ||||
| 		nadproblem = self.request.query_params.get('nadproblem',None) | ||||
| 		if nadproblem is not None: | ||||
| 			queryset = queryset.filter(nadproblem__pk = nadproblem) | ||||
| 		return queryset | ||||
| 
 | ||||
| class UlohaZadaniNodeViewSet(ReadWriteSerializerMixin, viewsets.ModelViewSet): | ||||
| 	queryset = m.UlohaZadaniNode.objects.all() | ||||
| 	read_serializer_class = views.UlohaZadaniNodeSerializer | ||||
| 	write_serializer_class = views.UlohaZadaniNodeWriteSerializer | ||||
| 	create_serializer_class = views.UlohaZadaniNodeCreateSerializer | ||||
| 
 | ||||
| class ReseniNodeViewSet(ReadWriteSerializerMixin, viewsets.ModelViewSet): | ||||
| 	queryset = m.ReseniNode.objects.all() | ||||
| 	read_serializer_class = views.ReseniNodeSerializer | ||||
| 	write_serializer_class = views.ReseniNodeWriteSerializer | ||||
| 	create_serializer_class = views.ReseniNodeCreateSerializer | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class ProblemViewSet(viewsets.ModelViewSet): | ||||
| 	serializer_class = views.ProblemSerializer | ||||
| 
 | ||||
| 	def get_queryset(self): | ||||
| 		queryset = m.Problem.objects.all() | ||||
| 		ucel = self.request.query_params.get('ucel',None) | ||||
| 		rocnik = self.request.query_params.get('rocnik',None) | ||||
| 		tema = self.request.query_params.get('tema',None) | ||||
| 
 | ||||
| 		if rocnik is not None: | ||||
| 			queryset = queryset.filter(rocnik=rocnik) | ||||
| 
 | ||||
| 		#if tema is not None: | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 		return queryset | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,12 +1,12 @@ | |||
| <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('castNode')" v-on:click="selected='castNode'" :disabled="selected && selected !== 'castNode'">Nadpis části</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> | ||||
| 			<component :is='selected' :item='null' :where="where" :refnode="refnode" :tema="tema" create></component> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </template> | ||||
|  | @ -24,6 +24,7 @@ export default { | |||
| 		types: Array, | ||||
| 		where: String, | ||||
| 		refnode: Object, | ||||
| 		tema: Object, | ||||
| 	}, | ||||
| 	data: () => ({ | ||||
| 		selected: null, | ||||
|  |  | |||
|  | @ -7,7 +7,9 @@ | |||
| 			<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> | ||||
| 		<h4>{{ currentText }} </h4> | ||||
| 		<button v-if="editorMode" v-on:click="editorShow = !editorShow">Upravit</button> | ||||
| 		<button v-if="editorMode" v-on:click="deleteCast" class="delete">Smazat</button> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </template> | ||||
|  | @ -25,6 +27,7 @@ export default { | |||
| 	props: { | ||||
| 		item: Object, | ||||
| 		editorShow: Boolean, | ||||
| 		editorMode: Boolean, | ||||
| 		create: Boolean, | ||||
| 		where: String, | ||||
| 		refnode: Object | ||||
|  | @ -76,11 +79,27 @@ export default { | |||
| 			} | ||||
| 
 | ||||
| 			this.editorShow = false; | ||||
| 		}, | ||||
| 		deleteCast: function() { | ||||
| 			console.log("Deleting cast"); | ||||
| 			axios.delete('/api/castnode/'+this.item.node.id+'/' | ||||
| 			).then( () => { | ||||
| 				this.loading = false; | ||||
| 				this.$root.$emit('updateData',"castNode delete update"); | ||||
| 			}).catch(e => { | ||||
| 				this.errors.push(e); | ||||
| 			}); | ||||
| 		} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <!-- Add "scoped" attribute to limit CSS to this component only --> | ||||
| <style scoped> | ||||
| .changed { | ||||
| 	background-color: yellow; | ||||
| } | ||||
| .delete { | ||||
| 	background-color:  #ff6666; | ||||
| } | ||||
| </style> | ||||
|  |  | |||
|  | @ -23,7 +23,8 @@ | |||
| 
 | ||||
| 		<template v-else v-bind:class="changedObject"> | ||||
| 			<p v-html="currentText"></p> | ||||
| 			<button v-if="editorMode" v-on:click="editorShow=!editorShow">Upravit</button> | ||||
| 			<button v-if="editorMode" v-on:click="editorShow = !editorShow">Upravit</button> | ||||
| 			<button v-if="editorMode" v-on:click="deleteText" class="delete">Smazat</button> | ||||
| 		</template> | ||||
| 	</div> | ||||
| </template> | ||||
|  | @ -162,6 +163,16 @@ export default { | |||
| 			} | ||||
| 			this.editorShow = false; | ||||
| 		}, | ||||
| 		deleteText: function() { | ||||
| 			console.log("Deleting text"); | ||||
| 			axios.delete('/api/textnode/'+this.item.node.id+'/' | ||||
| 			).then( () => { | ||||
| 				this.loading = false; | ||||
| 				this.$root.$emit('updateData',"textNode delete update"); | ||||
| 			}).catch(e => { | ||||
| 				this.errors.push(e); | ||||
| 			}); | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
| </script> | ||||
|  | @ -169,6 +180,9 @@ export default { | |||
| <!-- Add "scoped" attribute to limit CSS to this component only --> | ||||
| <style scoped> | ||||
| .changed { | ||||
| 	background-color: 'yellow'; | ||||
| 	background-color: yellow; | ||||
| } | ||||
| .delete { | ||||
| 	background-color:  #ff6666; | ||||
| } | ||||
| </style> | ||||
|  |  | |||
|  | @ -1,16 +1,19 @@ | |||
| <template> | ||||
| 
 | ||||
| 	<div :class="editorMode ? 'treenode-org' : 'treenode'"> | ||||
| 	<!--b v-if="v_tematu">v tematu</b> | ||||
| 	<!--b v-if="tema">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' | ||||
| 								:tema="temaOut" | ||||
| 								: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>Tema: {{ tema }}</pre> | ||||
| 		<pre>TemaOut: {{ temaOut }}</pre> | ||||
| 		<pre>{{ item.node.polymorphic_ctype.model }}</pre> | ||||
| 		<pre>{{ item }}</pre> | ||||
| 	</div> | ||||
|  | @ -18,31 +21,31 @@ | |||
| 	<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" /> | ||||
| 			<addnewnode :types="item.appendable_children" :refnode="item.node" :tema="temaOut" 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" />  | ||||
| 			<addnewnode :types="item.children[0].appendable_siblings" :refnode="item.children[0].node" :tema="temaOut" 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}}--> | ||||
| 					<!--Hide: {{hideNode(chld)}}, v tematu: {{tema}}, 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" | ||||
| 							<TreeNode :item="chld" :tema="temaOut" | ||||
| 									:force_visible="showChildren" | ||||
| 									:editorMode="editorMode" | ||||
| 									:debugMode="debugMode"> | ||||
| 							</TreeNode> | ||||
| 						</div> | ||||
| 						<div v-else> | ||||
| 							<TreeNode :item="chld" :v_tematu="true" | ||||
| 							<TreeNode :item="chld" :tema="temaOut" | ||||
| 									:force_visible="showChildren" | ||||
| 									:editorMode="editorMode" | ||||
| 									:debugMode="debugMode"> | ||||
|  | @ -51,7 +54,7 @@ | |||
| 						<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" />  | ||||
| 							<addnewnode :types="chld.appendable_siblings" :refnode="chld.node" :tema="temaOut" where="za" />  | ||||
| 						</div> | ||||
| 					</div> | ||||
| 				</div> | ||||
|  | @ -60,16 +63,16 @@ | |||
| 			</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 v-if="tema !== null && chld.node.polymorphic_ctype.model==='ulohazadaninode'"> | ||||
| 						<div> Tady možná něco je </div> | ||||
| 						<TreeNode :item="chld" :v_tematu="v_tematu" | ||||
| 						<TreeNode :item="chld" :tema="temaOut" | ||||
| 								:force_visible="force_visible" | ||||
| 								:editorMode="editorMode" | ||||
| 								:debugMode="debugMode"> | ||||
| 						</TreeNode> | ||||
| 					</div> | ||||
| 					<div v-else> | ||||
| 						<TreeNode :item="chld" :v_tematu="v_tematu" | ||||
| 						<TreeNode :item="chld" :tema="temaOut" | ||||
| 								:force_visible="force_visible" | ||||
| 								:editorMode="editorMode" | ||||
| 								:debugMode="debugMode"> | ||||
|  | @ -78,7 +81,7 @@ | |||
| 					<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" />  | ||||
| 						<addnewnode :types="chld.appendable_siblings" :refnode="chld.node" :tema="temaOut" where="za" />  | ||||
| 					</div> | ||||
| 				</div> | ||||
| 			</div> | ||||
|  | @ -112,14 +115,15 @@ export default { | |||
| 	}, | ||||
| 	data: () => ({ | ||||
| 		debugShow: false, | ||||
| 		showChildren: false | ||||
| 		showChildren: false, | ||||
| 		temaOut: null, | ||||
| 	}), | ||||
| 	computed: { | ||||
| 	}, | ||||
| 	props: { | ||||
| 		item: Object, | ||||
| 		force_visible: Boolean, | ||||
| 		v_tematu: Boolean, | ||||
| 		tema: Object, | ||||
| 		editorMode: Boolean, | ||||
| 		debugMode: Boolean, | ||||
| 	}, | ||||
|  | @ -132,7 +136,17 @@ export default { | |||
| 				return false; | ||||
| 			} | ||||
| 			return true; | ||||
| 		}, | ||||
| 	}, | ||||
| 	mounted: function(){ | ||||
| 		if (this.item.node.polymorphic_ctype.model === 'temavcislenode'){ | ||||
| 			this.temaOut = this.item.node.tema; | ||||
| 			console.log(this.temaOut); | ||||
| 		} else { | ||||
| 			this.temaOut = this.tema; | ||||
| 		} | ||||
| 		 | ||||
| 
 | ||||
| 	} | ||||
| } | ||||
| </script> | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ | |||
| 		<div id="loading" v-if="loading"> | ||||
| 		Loading... | ||||
| 		</div> | ||||
| 		<div v-else> | ||||
| 		<!--pre> | ||||
| 		{{item}} | ||||
| 		</pre--> | ||||
|  | @ -11,6 +12,7 @@ | |||
| 		<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> | ||||
| 	</div> | ||||
| </template> | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,18 +1,23 @@ | |||
| <template> | ||||
| 	<div class="ulohavzoraknode"> | ||||
| 		<template v-if="editorShow"> | ||||
| 		<!--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> | ||||
| 		<!--h5 v-if="!editorMode">Řešení {{item.node.uloha.cislo_zadani.poradi}}.{{ item.node.uloha.kod }}: {{ item.node.uloha.nazev }}</h5--> | ||||
| 				<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> | ||||
| 				<button v-if="create" v-on:click="createNode">Vytvořit vzorák</button> | ||||
| 				<button v-if="!create" v-on:click="saveNode">Uložit</button> | ||||
| 		</template> | ||||
| 		<template v-else> | ||||
| 			<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="editorShow = !editorShow">Upravit</button> | ||||
| 		</template> | ||||
| 	</div> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -26,31 +31,58 @@ export default { | |||
| 			isResult: false, | ||||
| 			searchQuery: '', | ||||
| 			searchResults: [], | ||||
| 			showSelect: false, | ||||
| 			selected: null, | ||||
| 			selected_id: null | ||||
| 			selected_id: null, | ||||
| 			editorShow: false, | ||||
| 		} | ||||
| 	}, | ||||
| 	props: { | ||||
| 		item: Object, | ||||
| 		create: Boolean, | ||||
| 		showSelect: Boolean, | ||||
| 		editorMode: Boolean, | ||||
| 		editorShow: Boolean, | ||||
| 		tema: Object, | ||||
| 		refnode: Object, | ||||
| 		where: String, | ||||
| 	}, | ||||
| 	mounted: function(){ | ||||
| 		if (this.item.node.uloha === null){ | ||||
| 		axios.defaults.headers.common['X-CSRFToken'] = this.getCookie('csrftoken'); | ||||
| 		if (this.create){ | ||||
| 			this.editorShow = true; | ||||
| 			this.searchQuery = ""; | ||||
| 			this.selected_id = null; | ||||
| 		} else { | ||||
| 			this.searchQuery = this.item.node.uloha.nazev; | ||||
| 			this.selected_id = this.item.node.uloha.id; | ||||
| 			this.selected = this.item.node.uloha; | ||||
| 		} | ||||
| 		if (this.item !== null && this.item.node.uloha === null){ | ||||
| 			console.log("Uloha je null!"); | ||||
| 			console.log(this.item); | ||||
| 		} | ||||
| 		if (this.create){ | ||||
| 			this.showSelect = true; | ||||
| 			console.log('Creating'); | ||||
| 		} | ||||
| 	}, | ||||
| 	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; | ||||
| 		}, | ||||
| 		submitSearch: function(){ | ||||
| 			if (this.searchQuery.length < 3) { return;} | ||||
| 			var reqURL = "/api/ulohavzoraknode/?nazev="+this.searchQuery; | ||||
| 			let nazev = "nazev="+this.searchQuery+"&"; | ||||
| 			let nadproblem = "nadproblem="+this.tema.id+"&"; | ||||
| 			var reqURL = "/api/uloha/?"+nazev+nadproblem; | ||||
| 			axios.get(reqURL).then( (response) => { | ||||
| 				this.searchResults = response.data.results; | ||||
| 				this.isResult = true; | ||||
|  | @ -63,6 +95,38 @@ export default { | |||
| 		setSelected: function(res){ | ||||
| 			this.searchQuery = res.nazev | ||||
| 			this.selected_id = res.id | ||||
| 			this.selected = res | ||||
| 		}, | ||||
| 		createNode: function(){ | ||||
| 			console.log('Creating UlohaVzorakNode'); | ||||
| 			this.loading = true | ||||
| 			axios.post('/api/ulohavzoraknode/',{ | ||||
| 				refnode: this.refnode.id, | ||||
| 				where: this.where, | ||||
| 				uloha_id: this.selected_id, | ||||
| 			}).then(response => { | ||||
| 				console.log(response.data); | ||||
| 				this.loading=false; | ||||
| 				this.$root.$emit('updateData',"ulohaZadaniNode create update"); | ||||
| 			}).catch( e => { | ||||
| 				console.log(e); | ||||
| 			}); | ||||
| 		}, | ||||
| 		saveNode: function(){ | ||||
| 			console.log('Saving UlohaVzorakNode'); | ||||
| 			this.loading = true | ||||
| 			axios.put('/api/ulohavzoraknode/'+this.item.node.id+'/',{ | ||||
| 				id: this.item.node.id, | ||||
| 				uloha: this.selected_id | ||||
| 			}).then(response => { | ||||
| 				console.log(response.data); | ||||
| 				this.loading=false; | ||||
| 				this.item.node = response.data; | ||||
| 				this.$root.$emit('updateData',"ulohaZadaniNode save update"); | ||||
| 			}).catch( e => { | ||||
| 				console.log(e); | ||||
| 			}); | ||||
| 			this.editorShow = false; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -1,23 +1,113 @@ | |||
| <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> | ||||
| 		<template v-if="editorShow"> | ||||
| 			Název: <input tpye="text" v-model="nazev"><br> | ||||
| 			Počet bodů: <input type="number" min="0" max="20" v-model="max_body"><br> | ||||
| 			Kód: <input type="text" v-model="kod"><br> | ||||
| 			<!--Autor: FIXME!<br--> | ||||
| 			<button v-if="create" v-on:click="createNode">Vytvořit úlohu</button> | ||||
| 			<button v-if="!create" v-on:click="saveNode">Uložit</button> | ||||
| 		</template> | ||||
| 		<template v-else> | ||||
| 			<h5>Zadání {{item.node.uloha.cislo_zadani.poradi}}.{{ item.node.uloha.kod }}: {{ item.node.uloha.nazev }} ({{item.node.uloha.max_body}} b)</h5> | ||||
| 			<button v-if="editorMode" v-on:click="editorShow = !editorShow">Upravit</button> | ||||
| 			<!--button v-if="editorMode" v-on:click="deleteText" class="delete">Smazat</button--> | ||||
| 		</template> | ||||
| 	</div> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| import axios from 'axios' | ||||
| 
 | ||||
| export default { | ||||
| 	name: 'UlohaZadaniNode', | ||||
| 	data: () => ({ | ||||
| 		max_body: 0, | ||||
| 		kod: "", | ||||
| 		editorShow: false, | ||||
| 		editorMode: false, | ||||
| 	}), | ||||
| 	props: { | ||||
| 		item: Object, | ||||
| 		created: Boolean | ||||
| 	, | ||||
| 		editorShow: Boolean, | ||||
| 		editorMode: Boolean, | ||||
| 		create: Boolean, | ||||
| 		where: String, | ||||
| 		refnode: Object | ||||
| 	}, | ||||
| 	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; | ||||
| 		}, | ||||
| 		createNode: function(){ | ||||
| 			console.log('Creating UlohaZadaniNode'); | ||||
| 			this.loading = true | ||||
| 			axios.post('/api/ulohazadaninode/',{ | ||||
| 				refnode: this.refnode.id, | ||||
| 				where: this.where, | ||||
| 				uloha: { | ||||
| 					max_body: this.max_body, | ||||
| 					kod: this.kod, | ||||
| 					nazev: this.nazev, | ||||
| 				}, | ||||
| 			}).then(response => { | ||||
| 				console.log(response.data); | ||||
| 				this.loading=false; | ||||
| 				this.$root.$emit('updateData',"ulohaZadaniNode create update"); | ||||
| 			}).catch( e => { | ||||
| 				console.log(e); | ||||
| 			});}, | ||||
| 		saveNode: function(){ | ||||
| 			console.log('Saving UlohaZadaniNode'); | ||||
| 			this.loading = true | ||||
| 			axios.put('/api/ulohazadaninode/'+this.item.node.id+'/',{ | ||||
| 				uloha: { | ||||
| 					max_body: this.max_body, | ||||
| 					kod: this.kod, | ||||
| 					nazev: this.nazev, | ||||
| 				} | ||||
| 			}).then(response => { | ||||
| 				console.log(response.data); | ||||
| 				this.loading=false; | ||||
| 				this.item.node = response.data; | ||||
| 				this.$root.$emit('updateData',"ulohaZadaniNode save update"); | ||||
| 			}).catch( e => { | ||||
| 				console.log(e); | ||||
| 			}); | ||||
| 			this.editorShow = false; | ||||
| 
 | ||||
| 		}, | ||||
| 			 | ||||
| 	}, | ||||
| 	mounted: function(){ | ||||
| 		axios.defaults.headers.common['X-CSRFToken'] = this.getCookie('csrftoken'); | ||||
| 		if (this.create){ | ||||
| 			this.editorShow = true; | ||||
| 			this.max_body = 0; | ||||
| 			this.nazev = ""; | ||||
| 			this.kod = ""; | ||||
| 		} else { | ||||
| 			this.max_body = this.item.node.uloha.max_body; | ||||
| 			this.nazev = this.item.node.uloha.nazev; | ||||
| 			this.kod = this.item.node.uloha.kod; | ||||
| 		} | ||||
| 		if (this.item.node.uloha === null){ | ||||
| 			console.log("Uloha je null!"); | ||||
| 			console.log(this.item); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| } | ||||
| </script> | ||||
|  |  | |||
|  | @ -20,7 +20,7 @@ export default new Router({ | |||
| 	}, { | ||||
| 		path: '/zadani/aktualni', | ||||
| 		name: 'treenode_zadani', | ||||
| 		props: {'tnid': 23}, | ||||
| 		props: {'tnid': 1655}, | ||||
| 		component: TreeNodeRoot | ||||
| 	}, { | ||||
| 		path: '/cislo/:cislo', | ||||
|  |  | |||
|  | @ -15,7 +15,7 @@ const pages = { | |||
| module.exports = { | ||||
|     pages: pages, | ||||
|     filenameHashing: false, | ||||
|     productionSourceMap: false, | ||||
|     productionSourceMap: true, | ||||
|     publicPath: process.env.NODE_ENV === 'production' | ||||
|         ? '/static/seminar/vue/' | ||||
|         : 'http://localhost:8080/', | ||||
|  | @ -23,6 +23,7 @@ module.exports = { | |||
| 
 | ||||
|     chainWebpack: config => { | ||||
| 
 | ||||
| 	config.optimization.minimize(false) | ||||
|         config.optimization | ||||
|             .splitChunks({ | ||||
|                 cacheGroups: { | ||||
|  | @ -33,7 +34,7 @@ module.exports = { | |||
|                         priority: 1 | ||||
|                     }, | ||||
|                 }, | ||||
|             }); | ||||
|             }).minimize(false); | ||||
| 
 | ||||
|         Object.keys(pages).forEach(page => { | ||||
|             config.plugins.delete(`html-${page}`); | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Pavel "LEdoian" Turinsky
						Pavel "LEdoian" Turinsky