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 | from . import views | ||||||
| 
 | 
 | ||||||
| urlpatterns = [ | urlpatterns = [ | ||||||
| 	path('korektury/', org_required(views.KorekturyAktualniListView.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-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/<int:pdf>/', org_required(views.KorekturyView.as_view()), name='korektury'), | ||||||
| 	path('korektury/help/', org_required(views.KorekturyHelpView.as_view()), name='korektury-help'), | 	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 = routers.DefaultRouter() | ||||||
| 
 | 
 | ||||||
| router.register(r'ulohavzoraknode', vs.UlohaVzorakNodeViewSet,basename='ulohavzoraknode') | 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'text', vs.TextViewSet) | ||||||
| router.register(r'textnode', vs.TextNodeViewSet) | router.register(r'textnode', vs.TextNodeViewSet) | ||||||
| router.register(r'castnode', vs.CastNodeViewSet) | 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 | # MaM specific | ||||||
| 
 | 
 | ||||||
| SEMINAR_RESENI_DIR = os.path.join('reseni') | SEMINAR_RESENI_DIR = os.path.join('reseni') | ||||||
|  |  | ||||||
|  | @ -28,7 +28,7 @@ INTERNAL_IPS = ['127.0.0.1'] | ||||||
| 
 | 
 | ||||||
| TEMPLATES[0]['OPTIONS']['debug'] = True | TEMPLATES[0]['OPTIONS']['debug'] = True | ||||||
| 
 | 
 | ||||||
| ALLOWED_HOSTS = ['127.0.0.1'] | ALLOWED_HOSTS = ['127.0.0.1', '192.168.43.34'] | ||||||
| 
 | 
 | ||||||
| # Database | # Database | ||||||
| # https://docs.djangoproject.com/en/1.7/ref/settings/#databases | # https://docs.djangoproject.com/en/1.7/ref/settings/#databases | ||||||
|  |  | ||||||
|  | @ -347,6 +347,10 @@ div.zadani_azad_termin { | ||||||
| 	bottom: 0px; | 	bottom: 0px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #footer p.license a { | ||||||
|  | 	color: #333; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| p.license-mobile { | p.license-mobile { | ||||||
| 	display: none; | 	display: none; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,4 +1,5 @@ | ||||||
| from django.contrib import admin | from django.contrib import admin | ||||||
|  | from django.contrib.auth.models import Permission | ||||||
| 
 | 
 | ||||||
| from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter | from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter | ||||||
| from reversion.admin import VersionAdmin | from reversion.admin import VersionAdmin | ||||||
|  | @ -18,7 +19,7 @@ admin.site.register(m.Soustredeni) | ||||||
| 
 | 
 | ||||||
| @admin.register(m.Osoba) | @admin.register(m.Osoba) | ||||||
| class OsobaAdmin(admin.ModelAdmin): | class OsobaAdmin(admin.ModelAdmin): | ||||||
| 	actions = ['synchronizuj_maily'] | 	actions = ['synchronizuj_maily', 'udelej_orgem'] | ||||||
| 
 | 
 | ||||||
| 	def synchronizuj_maily(self, request, queryset): | 	def synchronizuj_maily(self, request, queryset): | ||||||
| 		for o in queryset: | 		for o in queryset: | ||||||
|  | @ -29,6 +30,20 @@ class OsobaAdmin(admin.ModelAdmin): | ||||||
| 		self.message_user(request, "E-maily synchronizovány.") | 		self.message_user(request, "E-maily synchronizovány.") | ||||||
| 	synchronizuj_maily.short_description = "Synchronizuj vybraným osobám e-maily do uživatelů" | 	synchronizuj_maily.short_description = "Synchronizuj vybraným osobám e-maily do uživatelů" | ||||||
| 
 | 
 | ||||||
|  | 	def udelej_orgem(self,request,queryset): | ||||||
|  | 		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) | @admin.register(m.Problem) | ||||||
| class ProblemAdmin(PolymorphicParentModelAdmin): | class ProblemAdmin(PolymorphicParentModelAdmin): | ||||||
| 	base_model = m.Problem | 	base_model = m.Problem | ||||||
|  |  | ||||||
|  | @ -10,6 +10,21 @@ import seminar.models as m | ||||||
| from datetime import date | from datetime import date | ||||||
| import logging | 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): | class LoginForm(forms.Form): | ||||||
| 	username = forms.CharField(label='Přihlašovací jméno',  | 	username = forms.CharField(label='Přihlašovací jméno',  | ||||||
| 			max_length=256,  | 			max_length=256,  | ||||||
|  | @ -42,8 +57,8 @@ class PrihlaskaForm(forms.Form): | ||||||
| 	pohlavi_muz = forms.ChoiceField(label='Pohlaví', | 	pohlavi_muz = forms.ChoiceField(label='Pohlaví', | ||||||
| 			choices = ((True,'muž'),(False,'žena')), required=True) | 			choices = ((True,'muž'),(False,'žena')), required=True) | ||||||
| 	email = forms.EmailField(label='E-mail',max_length=256, required=True) | 	email = forms.EmailField(label='E-mail',max_length=256, required=True) | ||||||
| 	telefon = forms.CharField(label='Telefon',max_length=256, required=False) | 	telefon = forms.CharField(widget=TelInput(),label='Telefon',max_length=256, required=False) | ||||||
| 	datum_narozeni = forms.DateField(label='Datum narození', required=False) | 	datum_narozeni = forms.DateField(widget=DateInput(),label='Datum narození', required=False) | ||||||
| 	ulice = forms.CharField(label='Ulice', max_length=256, required=False) | 	ulice = forms.CharField(label='Ulice', max_length=256, required=False) | ||||||
| 	mesto = forms.CharField(label='Město', 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) | 	psc = forms.CharField(label='PSČ', max_length=32, required=False) | ||||||
|  | @ -74,6 +89,8 @@ class PrihlaskaForm(forms.Form): | ||||||
| 		max_value=date.today().year+8, | 		max_value=date.today().year+8, | ||||||
| 		required=True) | 		required=True) | ||||||
| 	zasilat = forms.ChoiceField(label='Kam zasílat čísla a řešení',choices = Resitel.ZASILAT_CHOICES, required=True) | 	zasilat = forms.ChoiceField(label='Kam zasílat čísla a řešení',choices = Resitel.ZASILAT_CHOICES, required=True) | ||||||
|  | 	zasilat_cislo_emailem = forms.BooleanField(label='Chci dostávat 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) | 	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) | 	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í', | 	pohlavi_muz = forms.ChoiceField(label='Pohlaví', | ||||||
| 			choices = ((True,'muž'),(False,'žena')), required=True) | 			choices = ((True,'muž'),(False,'žena')), required=True) | ||||||
| 	email = forms.EmailField(label='E-mail',max_length=256, required=True) | 	email = forms.EmailField(label='E-mail',max_length=256, required=True) | ||||||
| 	telefon = forms.CharField(label='Telefon',max_length=256, required=False) | 	telefon = forms.CharField(widget=TelInput(),label='Telefon',max_length=256, required=False) | ||||||
| 	datum_narozeni = forms.DateField(label='Datum narození', required=False) | 	datum_narozeni = forms.DateField(widget=DateInput(),label='Datum narození', required=False) | ||||||
| 	ulice = forms.CharField(label='Ulice', max_length=256, required=False) | 	ulice = forms.CharField(label='Ulice', max_length=256, required=False) | ||||||
| 	mesto = forms.CharField(label='Město', 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) | 	psc = forms.CharField(label='PSČ', max_length=32, required=False) | ||||||
|  | @ -167,6 +184,8 @@ class ProfileEditForm(forms.Form): | ||||||
| 		max_value=date.today().year+8, | 		max_value=date.today().year+8, | ||||||
| 		required=True) | 		required=True) | ||||||
| 	zasilat = forms.ChoiceField(label='Kam zasílat čísla a řešení',choices = Resitel.ZASILAT_CHOICES, required=True) | 	zasilat = forms.ChoiceField(label='Kam zasílat čísla a řešení',choices = Resitel.ZASILAT_CHOICES, required=True) | ||||||
|  | 	zasilat_cislo_emailem = forms.BooleanField(label='Chci dostávat email s upozorněním na vydání nového čísla', required=True) | ||||||
|  | 
 | ||||||
| 	spam = forms.BooleanField(label='Souhlasím se zasíláním materiálů od MFF UK', required=False) | 	spam = forms.BooleanField(label='Souhlasím se zasíláním materiálů od MFF UK', required=False) | ||||||
| #	def clean_username(self): | #	def clean_username(self): | ||||||
| #		err_logger = logging.getLogger('seminar.prihlaska.problem') | #		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í', | 	#resitele = models.ManyToManyField(Resitel, verbose_name='autoři řešení', | ||||||
| 	#	help_text='Seznam autorů řešení', through='Reseni_Resitele') | 	#	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) | 	#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_DO_SKOLY, 'Do školy'), | ||||||
| 		(ZASILAT_NIKAM, 'Nikam'), | 		(ZASILAT_NIKAM, 'Nikam'), | ||||||
| 		] | 		] | ||||||
|  | 
 | ||||||
| 	zasilat = models.CharField('kam zasílat', max_length=32, choices=ZASILAT_CHOICES, blank=False, default=ZASILAT_DOMU) | 	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, | 	poznamka = models.TextField('neveřejná poznámka', blank=True, | ||||||
| 		help_text='Neveřejná poznámka k řešiteli (plain text)') | 		help_text='Neveřejná poznámka k řešiteli (plain text)') | ||||||
|  | @ -309,25 +311,106 @@ class Resitel(SeminarModelBase): | ||||||
| 		return sum(h.body for h in list(vsechna_hodnoceni)) | 		return sum(h.body for h in list(vsechna_hodnoceni)) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 	def get_titul(self, celkove_body=None): | 	def get_titul(self, body=None): | ||||||
| 		"Vrati titul" | 		"Vrati titul jako řetězec." | ||||||
| 		if celkove_body is None: |  | ||||||
| 			celkove_body = self.vsechny_body() |  | ||||||
| 		 | 		 | ||||||
| 		if celkove_body < 10: | 		# Nejprve si zadefinujeme titul | ||||||
| 			return '' | 		from enum import Enum | ||||||
| 		elif celkove_body < 20: | 		from functools import total_ordering | ||||||
| 			return 'Bc.' | 		@total_ordering | ||||||
| 		elif celkove_body < 50: | 		class Titul(Enum): | ||||||
| 			return 'Mgr.' | 			""" Třída reprezentující možné tituly. Hodnoty jsou dvojice (dolní hranice, stringifikace). """ | ||||||
| 		elif celkove_body < 100: | 			nic =  (0, '') | ||||||
| 			return 'Dr.' | 			bc =   (20, 'Bc.') | ||||||
| 		elif celkove_body < 200: | 			mgr =  (50, 'Mgr.') | ||||||
| 			return 'Doc.' | 			dr =   (100, 'Dr.') | ||||||
| 		elif celkove_body < 500: | 			doc =  (200, 'Doc.') | ||||||
| 			return 'Prof.' | 			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: | 		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): | 	def __str__(self): | ||||||
| 		return self.osoba.plne_jmeno() | 		return self.osoba.plne_jmeno() | ||||||
| 
 | 
 | ||||||
|  | @ -745,15 +828,18 @@ class Problem(SeminarModelBase,PolymorphicModel): | ||||||
| 	def verejne(self): | 	def verejne(self): | ||||||
| 		# aktuálně podle stavu problému | 		# aktuálně podle stavu problému | ||||||
| 		# FIXME pro některé problémy možná chceme override | 		# FIXME pro některé problémy možná chceme override | ||||||
|  | 		# FIXME vrací veřejnost čistě problému, nezávisle na čísle, ve kterém je.  | ||||||
|  | 		# Je to tak správně? | ||||||
| 		stav_verejny = False | 		stav_verejny = False | ||||||
| 		if self.stav == 'zadany' or self.stav == 'vyreseny': | 		if self.stav == 'zadany' or self.stav == 'vyreseny': | ||||||
| 			stav_verejny = True | 			stav_verejny = True | ||||||
|  | 		return stav_verejny | ||||||
| 		 | 		 | ||||||
| 		cislo_verejne = False | 		#cislo_verejne = False | ||||||
| 		if (self.cislo_zadani and self.cislo_zadani.verejne()): | 		#if (self.cislo_zadani and self.cislo_zadani.verejne()): | ||||||
| 			cislo_verejne = True | 		#	cislo_verejne = True | ||||||
| 		 | 		 | ||||||
| 		return (stav_verejny and cislo_verejne) | 		#return (stav_verejny and cislo_verejne) | ||||||
| 	verejne.boolean = True | 	verejne.boolean = True | ||||||
| 
 | 
 | ||||||
| 	def verejne_url(self): | 	def verejne_url(self): | ||||||
|  | @ -991,7 +1077,7 @@ def aux_generate_filename(self, filename): | ||||||
| 		unidecode(filename.replace('/', '-').replace('\0', '')) | 		unidecode(filename.replace('/', '-').replace('\0', '')) | ||||||
| 	) | 	) | ||||||
| 	datedir = timezone.now().strftime('%Y-%m') | 	datedir = timezone.now().strftime('%Y-%m') | ||||||
| 	fname = "{}_{}".format( | 	fname = "{}/{}".format( | ||||||
| 		timezone.now().strftime('%Y-%m-%d-%H:%M'), | 		timezone.now().strftime('%Y-%m-%d-%H:%M'), | ||||||
| 		clean) | 		clean) | ||||||
| 	return os.path.join(datedir, fname) | 	return os.path.join(datedir, fname) | ||||||
|  | @ -1044,6 +1130,11 @@ class PrilohaReseni(SeminarModelBase): | ||||||
| 	def __str__(self): | 	def __str__(self): | ||||||
| 		return str(self.soubor) | 		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): | class Pohadka(SeminarModelBase): | ||||||
| 	"""Kus pohádky před/za úlohou v čísle""" | 	"""Kus pohádky před/za úlohou v čísle""" | ||||||
|  | @ -1480,7 +1571,7 @@ class TextNode(TreeNode): | ||||||
| 		verbose_name = 'Text (Node)' | 		verbose_name = 'Text (Node)' | ||||||
| 		verbose_name_plural = 'Text (Node)' | 		verbose_name_plural = 'Text (Node)' | ||||||
| 	text = models.ForeignKey(Text, | 	text = models.ForeignKey(Text, | ||||||
| 		on_delete=models.PROTECT, | 		on_delete=models.CASCADE, | ||||||
| 		verbose_name = 'text') | 		verbose_name = 'text') | ||||||
| 	 | 	 | ||||||
| 	def aktualizuj_nazev(self): | 	def aktualizuj_nazev(self): | ||||||
|  | @ -1513,7 +1604,7 @@ class ReseniNode(TreeNode): | ||||||
| 		verbose_name = 'reseni') | 		verbose_name = 'reseni') | ||||||
| 	 | 	 | ||||||
| 	def aktualizuj_nazev(self): | 	def aktualizuj_nazev(self): | ||||||
| 		self.nazev = "OtisteneReseniNode: "+str(self.reseni) | 		self.nazev = "ReseniNode: "+str(self.reseni) | ||||||
| 
 | 
 | ||||||
| 	def getOdkazStr(self): | 	def getOdkazStr(self): | ||||||
| 		return str(self.reseni) | 		return str(self.reseni) | ||||||
|  |  | ||||||
|  | @ -45,6 +45,7 @@ | ||||||
|           <li><a href="tituly.tex">Tituly (TeX)</a></li> |           <li><a href="tituly.tex">Tituly (TeX)</a></li> | ||||||
|           <li><a href="vysledkovka.tex">Výsledkovka (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="obalkovani">Obálkování</a></li> | ||||||
|  | 	  <li><a href="odmeny/{{prevcislo.rocnik.rocnik}}.{{prevcislo.poradi}}/">Odměny</a></li> | ||||||
|         </ul> |         </ul> | ||||||
|       </div> |       </div> | ||||||
|   {% endif %} |   {% endif %} | ||||||
|  | @ -75,6 +76,7 @@ | ||||||
|         {% for p in problemy %} |         {% for p in problemy %} | ||||||
|         <th class='border-r'><a href="{{ p.verejne_url }}">{{ p.kod_v_rocniku }}</a> |         <th class='border-r'><a href="{{ p.verejne_url }}">{{ p.kod_v_rocniku }}</a> | ||||||
|         {% endfor %} |         {% endfor %} | ||||||
|  |         {% if ostatni %}<th class='border-r'>Ostatní {% endif %} | ||||||
|         <th class='border-r'>Za číslo |         <th class='border-r'>Za číslo | ||||||
|         <th class='border-r'>Za ročník |         <th class='border-r'>Za ročník | ||||||
|         <th class='border-r'>Odjakživa |         <th class='border-r'>Odjakživa | ||||||
|  |  | ||||||
							
								
								
									
										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}} |     {{form.media}} | ||||||
|     <script src="{% static 'seminar/prihlaska.js' %}"></script> |     <script src="{% static 'seminar/prihlaska.js' %}"></script> | ||||||
| {% endblock %} | {% 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 %} | {% block content %} | ||||||
| <h1> | <h1> | ||||||
|   {% block nadpis1a %}{% block nadpis1b %} |   {% block nadpis1a %}{% block nadpis1b %} | ||||||
|  | @ -73,6 +84,7 @@ | ||||||
|     </h4> |     </h4> | ||||||
|      <table class="form"> |      <table class="form"> | ||||||
|        {% include "seminar/profil/prihlaska_field.html" with field=form.zasilat %} |        {% include "seminar/profil/prihlaska_field.html" with field=form.zasilat %} | ||||||
|  |        {% include "seminar/profil/prihlaska_field.html" with field=form.zasilat_cislo_emailem %} | ||||||
|      </table> |      </table> | ||||||
| 
 | 
 | ||||||
|  <hr> |  <hr> | ||||||
|  |  | ||||||
|  | @ -53,7 +53,7 @@ | ||||||
|               </td> |               </td> | ||||||
|               <td {% if field.help_text %} class="field-with-comment"{% endif %}> |               <td {% if field.help_text %} class="field-with-comment"{% endif %}> | ||||||
|                 {{ field }} |                 {{ field }} | ||||||
|                   <span class="field-comment">{{ field.help_text|safe }}</span>> |                   <span class="field-comment">{{ field.help_text|safe }}</span> | ||||||
|               </td> |               </td> | ||||||
| 
 | 
 | ||||||
|             <td><span class="field-error">{{ field.errors }}</span></td> |             <td><span class="field-error">{{ field.errors }}</span></td> | ||||||
|  |  | ||||||
|  | @ -7,6 +7,16 @@ | ||||||
|     <script src="{% static 'seminar/prihlaska.js' %}"></script> |     <script src="{% static 'seminar/prihlaska.js' %}"></script> | ||||||
| {% endblock %} | {% 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 %} | {% block content %} | ||||||
| <h1> | <h1> | ||||||
|   {% block nadpis1a %}{% block nadpis1b %} |   {% block nadpis1a %}{% block nadpis1b %} | ||||||
|  | @ -77,6 +87,7 @@ | ||||||
|         </h4> |         </h4> | ||||||
|          <table class="form"> |          <table class="form"> | ||||||
|            {% include "seminar/profil/prihlaska_field.html" with field=form.zasilat %} |            {% include "seminar/profil/prihlaska_field.html" with field=form.zasilat %} | ||||||
|  |            {% include "seminar/profil/prihlaska_field.html" with field=form.zasilat_cislo_emailem %} | ||||||
|          </table> |          </table> | ||||||
|  <hr> |  <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.user = user | ||||||
| 				os.save() | 				os.save() | ||||||
| 				os.user.user_permissions.add(org_perm) | 				os.user.user_permissions.add(org_perm) | ||||||
|  | 				os.user.is_staff = True | ||||||
|  | 				os.user.save() | ||||||
| 			organizatori.append(Organizator.objects.create(osoba=os, | 			organizatori.append(Organizator.objects.create(osoba=os, | ||||||
| 				organizuje_od=od, organizuje_do=do, strucny_popis_organizatora = popis_orga)) | 				organizuje_od=od, organizuje_do=do, strucny_popis_organizatora = popis_orga)) | ||||||
| 	return organizatori | 	return organizatori | ||||||
|  |  | ||||||
|  | @ -13,8 +13,8 @@ urlpatterns = [ | ||||||
| 	path('co-je-MaM/organizatori/organizovali/', views.CojemamOrganizatoriStariView.as_view(), name='stari_organizatori'), | 	path('co-je-MaM/organizatori/organizovali/', views.CojemamOrganizatoriStariView.as_view(), name='stari_organizatori'), | ||||||
| 
 | 
 | ||||||
| 	# Archiv | 	# Archiv | ||||||
| 	path('archiv/rocniky/', views.ArchivView.as_view()), | 	path('archiv/rocniky/', views.ArchivView.as_view(), name="seninar_archiv_rocniky"), | ||||||
| 	path('archiv/temata/', views.ArchivTemataView.as_view()), | 	path('archiv/temata/', views.ArchivTemataView.as_view(), name="seninar_archiv_temata"), | ||||||
| 
 | 
 | ||||||
| 	path('rocnik/<int:rocnik>/', views.RocnikView.as_view(), name='seminar_rocnik'), | 	path('rocnik/<int:rocnik>/', views.RocnikView.as_view(), name='seminar_rocnik'), | ||||||
| 	path('cislo/<int:rocnik>.<str:cislo>/', views.CisloView.as_view(), name='seminar_cislo'), | 	path('cislo/<int:rocnik>.<str:cislo>/', views.CisloView.as_view(), name='seminar_cislo'), | ||||||
|  | @ -90,17 +90,17 @@ urlpatterns = [ | ||||||
| 		name='seminar_rocnik_vysledkovka' | 		name='seminar_rocnik_vysledkovka' | ||||||
| 	), | 	), | ||||||
| 	path( | 	path( | ||||||
| 		'cislo/<int:rocnik>.<int:cislo>/vysledkovka.tex', | 		'cislo/<int:rocnik>.<str:cislo>/vysledkovka.tex', | ||||||
| 		org_required(views.CisloVysledkovkaView.as_view()), | 		org_required(views.CisloVysledkovkaView.as_view()), | ||||||
| 		name='seminar_cislo_vysledkovka' | 		name='seminar_cislo_vysledkovka' | ||||||
| 	), | 	), | ||||||
| 	path( | 	path( | ||||||
| 		'cislo/<int:rocnik>.<int:cislo>/obalky.pdf', | 		'cislo/<int:rocnik>.<str:cislo>/obalky.pdf', | ||||||
| 		org_required(views.cisloObalkyView), | 		org_required(views.cisloObalkyView), | ||||||
| 		name='seminar_cislo_obalky' | 		name='seminar_cislo_obalky' | ||||||
| 	), | 	), | ||||||
| 	path( | 	path( | ||||||
| 		'cislo/<int:rocnik>.<int:cislo>/tituly.tex', | 		'cislo/<int:rocnik>.<str:cislo>/tituly.tex', | ||||||
| 		org_required(views.TitulyView), | 		org_required(views.TitulyView), | ||||||
| 		name='seminar_cislo_titul' | 		name='seminar_cislo_titul' | ||||||
| 	), | 	), | ||||||
|  | @ -110,10 +110,14 @@ urlpatterns = [ | ||||||
| 		name='stav_databaze' | 		name='stav_databaze' | ||||||
| 	), | 	), | ||||||
| 	path( | 	path( | ||||||
| 		'cislo/<int:rocnik>.<int:cislo>/obalkovani', | 		'cislo/<int:rocnik>.<str:cislo>/obalkovani', | ||||||
| 		org_required(views.ObalkovaniView.as_view()), | 		org_required(views.ObalkovaniView.as_view()), | ||||||
| 		name='seminar_cislo_resitel_obalkovani' | 		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( | 	path( | ||||||
| 		'soustredeni/<int:soustredeni>/obalky.pdf', | 		'soustredeni/<int:soustredeni>/obalky.pdf', | ||||||
| 		org_required(views.soustredeniObalkyView), | 		org_required(views.soustredeniObalkyView), | ||||||
|  | @ -168,5 +172,10 @@ urlpatterns = [ | ||||||
| 	# org_member_required(views.OrganizatorAutocomplete.as_view()), | 	# org_member_required(views.OrganizatorAutocomplete.as_view()), | ||||||
| 	# name='seminar_autocomplete_organizator') | 	# 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: | 	if cislo is None: | ||||||
| 		# filtrujeme pouze podle ročníku | 		# 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 | 	else:  # filtrujeme podle ročníku i čísla | ||||||
| 		letosni_reseni = m.Reseni.objects.filter(hodnoceni__cislo_body__rocnik=rocnik, | 		return m.Resitel.objects.filter(rok_maturity__gte=rocnik.druhy_rok(), | ||||||
| 												 hodnoceni__cislo_body__poradi__lte=cislo.poradi) | 										reseni__hodnoceni__cislo_body__rocnik=rocnik, | ||||||
| 
 | 										reseni__hodnoceni__cislo_body__poradi__lte=cislo.poradi).distinct() | ||||||
| 	# 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() |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def aktivniResitele(cislo, pouze_letosni=False): | def aktivniResitele(cislo, pouze_letosni=False): | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
| from .views_all import * | from .views_all import * | ||||||
| from .autocomplete import * | from .autocomplete import * | ||||||
| from .views_rest import * | from .views_rest import * | ||||||
|  | from .odevzdavatko import * | ||||||
|  |  | ||||||
|  | @ -34,7 +34,9 @@ class OdevzdatelnyProblemAutocomplete(autocomplete.Select2QuerySetView): | ||||||
| 		rocnik = nastaveni.aktualni_rocnik | 		rocnik = nastaveni.aktualni_rocnik | ||||||
| 		temata = m.Tema.objects.filter(rocnik=rocnik, stav=m.Problem.STAV_ZADANY) | 		temata = m.Tema.objects.filter(rocnik=rocnik, stav=m.Problem.STAV_ZADANY) | ||||||
| 		ulohy = m.Uloha.objects.filter(cislo_deadline__rocnik = rocnik) | 		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(temata) | ||||||
|  | 		ulohy.union(clanky) | ||||||
| 		qs = ulohy | 		qs = ulohy | ||||||
| 		if self.q: | 		if self.q: | ||||||
| 			qs = qs.filter( | 			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.shortcuts import get_object_or_404, render, redirect | ||||||
| from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, JsonResponse | 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.contrib.auth.mixins import LoginRequiredMixin | ||||||
| from django.db import transaction | from django.db import transaction | ||||||
| from django.core import serializers | from django.core import serializers | ||||||
|  | from django.core.exceptions import PermissionDenied | ||||||
| from django.forms.models import model_to_dict | from django.forms.models import model_to_dict | ||||||
| 
 | 
 | ||||||
| import seminar.models as s | import seminar.models as s | ||||||
|  | @ -120,15 +121,57 @@ class TNLData(object): | ||||||
| 			self.appendable_siblings = tnltt.appendableChildren(self.parent) | 			self.appendable_siblings = tnltt.appendableChildren(self.parent) | ||||||
| 		else: | 		else: | ||||||
| 			self.appendable_siblings = [] | 			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 | 	@classmethod | ||||||
| 	def from_treenode(cls,anode,parent=None,index=None): | 	def all_public_children(cls, anode): | ||||||
| 		out = cls(anode,parent,index) | 		for ch in treelib.all_children(anode): | ||||||
| 		for (idx,ch) in enumerate(treelib.all_children(anode)): | 			if TNLData.public_above(ch): | ||||||
| 			# FIXME přidat filtrování na veřejnost | 				yield ch | ||||||
| 			outitem = cls.from_treenode(ch,out,idx) | 			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.children.append(outitem) | ||||||
| 		out.add_edit_options() | 		out.add_edit_options() | ||||||
| 		return out | 		return out | ||||||
|  | @ -195,7 +238,7 @@ class TreeNodeView(generic.DetailView): | ||||||
| 
 | 
 | ||||||
| 	def get_context_data(self,**kwargs): | 	def get_context_data(self,**kwargs): | ||||||
| 		context = super().get_context_data(**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 | 		return context | ||||||
| 
 | 
 | ||||||
| class TreeNodeJSONView(generic.DetailView): | class TreeNodeJSONView(generic.DetailView): | ||||||
|  | @ -203,7 +246,7 @@ class TreeNodeJSONView(generic.DetailView): | ||||||
| 
 | 
 | ||||||
| 	def get(self,request,*args, **kwargs): | 	def get(self,request,*args, **kwargs): | ||||||
| 		self.object = self.get_object() | 		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) | 		return JsonResponse(data) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -332,6 +375,7 @@ class ProblemView(generic.DetailView): | ||||||
| 
 | 
 | ||||||
| 	def get_context_data(self, **kwargs): | 	def get_context_data(self, **kwargs): | ||||||
| 		context = super().get_context_data(**kwargs) | 		context = super().get_context_data(**kwargs) | ||||||
|  | 		user = self.request.user | ||||||
| 		# Teď potřebujeme doplnit tnldata do kontextu. | 		# Teď potřebujeme doplnit tnldata do kontextu. | ||||||
| 		# Ošklivý type switch, hezčí by bylo udělat to polymorfni. FIXME. | 		# Ošklivý type switch, hezčí by bylo udělat to polymorfni. FIXME. | ||||||
| 		if False: | 		if False: | ||||||
|  | @ -339,11 +383,11 @@ class ProblemView(generic.DetailView): | ||||||
| 			pass | 			pass | ||||||
| 		elif isinstance(self.object, s.Clanek) or  isinstance(self.object, s.Konfera): | 		elif isinstance(self.object, s.Clanek) or  isinstance(self.object, s.Konfera): | ||||||
| 			# Tyhle Problémy mají ŘešeníNode | 			# 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): | 		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 | 			# 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_zadani = TNLData.from_treenode(self.object.ulohazadaninode,user) | ||||||
| 			tnl_vzorak = TNLData.from_treenode(self.object.ulohavzoraknode) | 			tnl_vzorak = TNLData.from_treenode(self.object.ulohavzoraknode,user) | ||||||
| 			context['tnldata'] = TNLData.from_tnldata_list([tnl_zadani, tnl_vzorak]) | 			context['tnldata'] = TNLData.from_tnldata_list([tnl_zadani, tnl_vzorak]) | ||||||
| 		elif isinstance(self.object, s.Tema): | 		elif isinstance(self.object, s.Tema): | ||||||
| 			rocniknode = self.object.rocnik.rocniknode | 			rocniknode = self.object.rocnik.rocniknode | ||||||
|  | @ -385,16 +429,16 @@ class AktualniZadaniView(generic.TemplateView): | ||||||
| #			) | #			) | ||||||
| # | # | ||||||
| def ZadaniTemataView(request): | def ZadaniTemataView(request): | ||||||
|         nastaveni = get_object_or_404(Nastaveni) | 	nastaveni = get_object_or_404(Nastaveni) | ||||||
|         verejne = nastaveni.aktualni_cislo.verejne() | 	verejne = nastaveni.aktualni_cislo.verejne() | ||||||
|         akt_rocnik = nastaveni.aktualni_cislo.rocnik | 	akt_rocnik = nastaveni.aktualni_cislo.rocnik | ||||||
|         temata = s.Tema.objects.filter(rocnik=akt_rocnik, stav='zadany') | 	temata = s.Tema.objects.filter(rocnik=akt_rocnik, stav='zadany') | ||||||
|         return render(request, 'seminar/tematka/rozcestnik.html', | 	return render(request, 'seminar/tematka/rozcestnik.html', | ||||||
|                         { | 			{ | ||||||
|                          'tematka': temata, | 			 'tematka': temata, | ||||||
|                          'verejne': verejne, | 			 'verejne': verejne, | ||||||
|                                 }, | 				}, | ||||||
|                         ) | 			) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| #	nastaveni = get_object_or_404(Nastaveni) | #	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: | 	if hlavni_problemy is None: | ||||||
| 		hlavni_problemy = hlavni_problemy_cisla(cislo) | 		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 = {} | 	hlavni_problemy_slovnik = {} | ||||||
| 	for hp in hlavni_problemy: | 	for hp in temata_a_spol: | ||||||
| 		hlavni_problemy_slovnik[hp.id] = {} | 		hlavni_problemy_slovnik[hp.id] = {} | ||||||
| 
 | 
 | ||||||
|  | 	hlavni_problemy_slovnik[-1] = {} | ||||||
|  | 
 | ||||||
| 	# zakládání prázdných záznamů pro řešitele | 	# zakládání prázdných záznamů pro řešitele | ||||||
| 	cislobody = {} | 	cislobody = {} | ||||||
| 	for ar in aktivni_resitele: | 	for ar in aktivni_resitele: | ||||||
| 		# řešitele převedeme na řetězec pomocí unikátního id | 		# řešitele převedeme na řetězec pomocí unikátního id | ||||||
| 		cislobody[ar.id] = "" | 		cislobody[ar.id] = "" | ||||||
| 		for hp in hlavni_problemy: | 		for hp in temata_a_spol: | ||||||
| 			slovnik = hlavni_problemy_slovnik[hp.id] | 			slovnik = hlavni_problemy_slovnik[hp.id] | ||||||
| 			slovnik[ar.id] = "" | 			slovnik[ar.id] = "" | ||||||
| 
 | 
 | ||||||
|  | 		hlavni_problemy_slovnik[-1][ar.id] = "" | ||||||
|  | 	 | ||||||
| 	# vezmeme všechna řešení s body do daného čísla | 	# vezmeme všechna řešení s body do daného čísla | ||||||
| 	reseni_do_cisla = Reseni.objects.prefetch_related('problem', 'resitele',  | 	reseni_do_cisla = Reseni.objects.prefetch_related('problem', 'resitele',  | ||||||
| 				'hodnoceni_set').filter(hodnoceni__cislo_body=cislo) | 				'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ů | 		# řešení může řešit více problémů | ||||||
| 		for prob in list(reseni.problem.all()): | 		for prob in list(reseni.problem.all()): | ||||||
| 			nadproblem = hlavni_problem(prob) | 			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í | 			# a mít více hodnocení | ||||||
| 			for hodn in list(reseni.hodnoceni_set.all()): | 			for hodn in list(reseni.hodnoceni_set.all()): | ||||||
|  | @ -1014,11 +1073,26 @@ def vysledkovka_cisla(cislo, context=None): | ||||||
| 	# vytvoříme jednotlivé sloupce výsledkovky | 	# vytvoříme jednotlivé sloupce výsledkovky | ||||||
| 	radky_vysledkovky = [] | 	radky_vysledkovky = [] | ||||||
| 	i = 0 | 	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: | 	for ar_id in setrizeni_resitele_id: | ||||||
| 		# získáme seznam bodů za problémy pro daného řešitele | 		# získáme seznam bodů za problémy pro daného řešitele | ||||||
| 		problemy = [] 		 | 		problemy = [] 		 | ||||||
| 		for hp in hlavni_problemy: | 		for hp in temata_a_spol: | ||||||
| 			problemy.append(hlavni_problemy_slovnik[hp.id][ar_id]) | 			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 | 		# vytáhneme informace pro daného řešitele | ||||||
| 		radek = RadekVysledkovkyCisla( | 		radek = RadekVysledkovkyCisla( | ||||||
| 			poradi[i], # pořadí | 			poradi[i], # pořadí | ||||||
|  | @ -1034,7 +1108,8 @@ def vysledkovka_cisla(cislo, context=None): | ||||||
| 	# vytahané informace předáváme do kontextu | 	# vytahané informace předáváme do kontextu | ||||||
| 	context['cislo'] = cislo | 	context['cislo'] = cislo | ||||||
| 	context['radky_vysledkovky'] = radky_vysledkovky | 	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['v_cisle_zadane'] = TODO | ||||||
| 	#context['resene_problemy'] = resene_problemy | 	#context['resene_problemy'] = resene_problemy | ||||||
| 	return context | 	return context | ||||||
|  | @ -1063,6 +1138,7 @@ class CisloView(generic.DetailView): | ||||||
| 		context = super(CisloView, self).get_context_data(**kwargs) | 		context = super(CisloView, self).get_context_data(**kwargs) | ||||||
| 
 | 
 | ||||||
| 		cislo = context['cislo'] | 		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 | 		# vrátíme context (aktuálně obsahuje jen věci ohledně výsledkovky | ||||||
| 		return vysledkovka_cisla(cislo, context) | 		return vysledkovka_cisla(cislo, context) | ||||||
| 
 | 
 | ||||||
|  | @ -1079,6 +1155,30 @@ class ArchivTemataView(generic.ListView): | ||||||
| 			ctx['rocniky'][rocnik] = list(temata) | 			ctx['rocniky'][rocnik] = list(temata) | ||||||
| 		return ctx | 		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 | ### Generovani vysledkovky | ||||||
| 
 | 
 | ||||||
| class CisloVysledkovkaView(CisloView): | class CisloVysledkovkaView(CisloView): | ||||||
|  | @ -1332,6 +1432,12 @@ class ResitelView(LoginRequiredMixin,generic.DetailView): | ||||||
| 
 | 
 | ||||||
| ### Formulare | ### 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): | class AddSolutionView(LoginRequiredMixin, FormView): | ||||||
| 	template_name = 'seminar/org/vloz_reseni.html' | 	template_name = 'seminar/org/vloz_reseni.html' | ||||||
| 	form_class = f.VlozReseniForm | 	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 | from django.forms.models import model_to_dict | ||||||
| def resitelEditView(request): | def resitelEditView(request): | ||||||
| 	err_logger = logging.getLogger('seminar.prihlaska.problem') | 	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 | 	u = request.user | ||||||
| 	osoba_edit = Osoba.objects.get(user=u) | 	osoba_edit = Osoba.objects.get(user=u) | ||||||
| 	resitel_edit = osoba_edit.resitel | 	resitel_edit = osoba_edit.resitel | ||||||
|  | @ -1448,6 +1554,7 @@ def resitelEditView(request): | ||||||
| 			resitel_edit.skola = fcd['skola'] | 			resitel_edit.skola = fcd['skola'] | ||||||
| 			resitel_edit.rok_maturity = fcd['rok_maturity'] | 			resitel_edit.rok_maturity = fcd['rok_maturity'] | ||||||
| 			resitel_edit.zasilat = fcd['zasilat'] | 			resitel_edit.zasilat = fcd['zasilat'] | ||||||
|  | 			resitel_edit.zasilat_cislo_emailem = fcd['zasilat_cislo_emailem'] | ||||||
| 			if fcd.get('skola'): | 			if fcd.get('skola'): | ||||||
| 				resitel_edit.skola = fcd['skola'] | 				resitel_edit.skola = fcd['skola'] | ||||||
| 			else: | 			else: | ||||||
|  | @ -1511,7 +1618,8 @@ def prihlaskaView(request): | ||||||
| 
 | 
 | ||||||
| 				r = Resitel( | 				r = Resitel( | ||||||
| 					rok_maturity = fcd['rok_maturity'], | 					rok_maturity = fcd['rok_maturity'], | ||||||
| 					zasilat = fcd['zasilat'] | 					zasilat = fcd['zasilat'], | ||||||
|  | 					zasilat_cislo_emailem = fcd['zasilat_cislo_emailem'] | ||||||
| 					) | 					) | ||||||
| 				 | 				 | ||||||
| 				r.save() | 				r.save() | ||||||
|  |  | ||||||
|  | @ -6,24 +6,26 @@ from seminar import treelib | ||||||
| 
 | 
 | ||||||
| DEFAULT_NODE_DEPTH = 2 | DEFAULT_NODE_DEPTH = 2 | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class TextSerializer(serializers.ModelSerializer): | class TextSerializer(serializers.ModelSerializer): | ||||||
| 	class Meta: | 	class Meta: | ||||||
| 		model = m.Text | 		model = m.Text | ||||||
| 		fields = '__all__' | 		fields = '__all__' | ||||||
| 
 | 
 | ||||||
| 
 | class ProblemSerializer(serializers.ModelSerializer): | ||||||
| 
 |  | ||||||
| class UlohaVzorakNodeSerializer(serializers.ModelSerializer): |  | ||||||
| 	class Meta: | 	class Meta: | ||||||
| 		model = m.UlohaVzorakNode | 		model = m.Problem | ||||||
| 		fields = '__all__' | 		fields = '__all__' | ||||||
| 		depth = DEFAULT_NODE_DEPTH |  | ||||||
| 
 | 
 | ||||||
| class UlohaZadaniNodeSerializer(serializers.ModelSerializer): | class UlohaSerializer(serializers.ModelSerializer): | ||||||
| 	class Meta: | 	class Meta: | ||||||
| 		model = m.UlohaZadaniNode | 		model = m.Uloha | ||||||
|  | 		fields = '__all__' | ||||||
|  | 
 | ||||||
|  | class ReseniSerializer(serializers.ModelSerializer): | ||||||
|  | 	class Meta: | ||||||
|  | 		model = m.Reseni | ||||||
| 		fields = '__all__' | 		fields = '__all__' | ||||||
| 		depth = DEFAULT_NODE_DEPTH |  | ||||||
| 	 | 	 | ||||||
| class RocnikNodeSerializer(serializers.ModelSerializer): | class RocnikNodeSerializer(serializers.ModelSerializer): | ||||||
| 	class Meta: | 	class Meta: | ||||||
|  | @ -138,12 +140,161 @@ class CastNodeCreateSerializer(serializers.ModelSerializer): | ||||||
| 		fields = ('nadpis','where','refnode') | 		fields = ('nadpis','where','refnode') | ||||||
| 		depth = DEFAULT_NODE_DEPTH | 		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 ReseniNodeSerializer(serializers.ModelSerializer): | ||||||
| 	class Meta: | 	class Meta: | ||||||
| 		model = m.ReseniNode | 		model = m.ReseniNode | ||||||
| 		fields = '__all__' | 		fields = '__all__' | ||||||
| 		depth = DEFAULT_NODE_DEPTH | 		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): | class TreeNodeSerializer(PolymorphicSerializer): | ||||||
| 	model_serializer_mapping = { | 	model_serializer_mapping = { | ||||||
|  |  | ||||||
|  | @ -1,4 +1,7 @@ | ||||||
| from rest_framework import viewsets,filters | 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 rest_framework.permissions import BasePermission, AllowAny | ||||||
| from . import models as m | from . import models as m | ||||||
| from . import views | 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 | 		# návštěvník nemusí být zalogován, aby si prohlížel obsah | ||||||
| 		return [permission() for permission in permission_classes] | 		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): | class ReadWriteSerializerMixin(object): | ||||||
| 	""" | 	""" | ||||||
| 	Overrides get_serializer_class to choose the read serializer | 	Overrides get_serializer_class to choose the read serializer | ||||||
|  | @ -87,10 +65,6 @@ class ReadWriteSerializerMixin(object): | ||||||
| 		) | 		) | ||||||
| 		return self.create_serializer_class | 		return self.create_serializer_class | ||||||
| 
 | 
 | ||||||
| class UlohaVzorakNodeViewSet(PermissionMixin, viewsets.ModelViewSet): |  | ||||||
| 	queryset = m.UlohaVzorakNode.objects.all() |  | ||||||
| 	serializer_class = views.UlohaVzorakNodeSerializer |  | ||||||
| 
 |  | ||||||
| class TextViewSet(PermissionMixin, viewsets.ModelViewSet): | class TextViewSet(PermissionMixin, viewsets.ModelViewSet): | ||||||
| 	queryset = m.Text.objects.all() | 	queryset = m.Text.objects.all() | ||||||
| 	serializer_class = views.TextSerializer | 	serializer_class = views.TextSerializer | ||||||
|  | @ -107,12 +81,91 @@ class CastNodeViewSet(PermissionMixin, ReadWriteSerializerMixin,viewsets.ModelVi | ||||||
| 	write_serializer_class = views.CastNodeSerializer | 	write_serializer_class = views.CastNodeSerializer | ||||||
| 	create_serializer_class = views.CastNodeCreateSerializer | 	create_serializer_class = views.CastNodeCreateSerializer | ||||||
| 
 | 
 | ||||||
| class UlohaVzorakNodeViewSet(PermissionMixin, viewsets.ModelViewSet): | 	def destroy(self, request, *args, **kwargs): | ||||||
| 	serializer_class = views.UlohaVzorakNodeSerializer | 		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): | 	def get_queryset(self): | ||||||
| 		queryset = m.UlohaVzorakNode.objects.all() | 		queryset = m.UlohaVzorakNode.objects.all() | ||||||
| 		nazev = self.request.query_params.get('nazev',None) | 		nazev = self.request.query_params.get('nazev',None) | ||||||
| 		if nazev is not None: | 		if nazev is not None: | ||||||
| 			queryset = queryset.filter(nazev__contains=nazev) | 			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 | 		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> | <template> | ||||||
| 	<div class="addnewnode"> | 	<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('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('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('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> | 		<button v-if="types.includes('ulohaVzorakNode')" v-on:click="selected='ulohaVzorakNode'" :disabled="selected && selected !== 'ulohaVzorakNode'">Vzorák</button> | ||||||
| 		<div v-if="selected"> | 		<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> | ||||||
| 	</div> | 	</div> | ||||||
| </template> | </template> | ||||||
|  | @ -24,6 +24,7 @@ export default { | ||||||
| 		types: Array, | 		types: Array, | ||||||
| 		where: String, | 		where: String, | ||||||
| 		refnode: Object, | 		refnode: Object, | ||||||
|  | 		tema: Object, | ||||||
| 	}, | 	}, | ||||||
| 	data: () => ({ | 	data: () => ({ | ||||||
| 		selected: null, | 		selected: null, | ||||||
|  |  | ||||||
|  | @ -7,7 +7,9 @@ | ||||||
| 			<button v-on:click="currentText = originalText;editorShow=!editorShow;">Zahodit úpravy</button> | 			<button v-on:click="currentText = originalText;editorShow=!editorShow;">Zahodit úpravy</button> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div v-else> | 		<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> | ||||||
| 	</div> | 	</div> | ||||||
| </template> | </template> | ||||||
|  | @ -25,6 +27,7 @@ export default { | ||||||
| 	props: { | 	props: { | ||||||
| 		item: Object, | 		item: Object, | ||||||
| 		editorShow: Boolean, | 		editorShow: Boolean, | ||||||
|  | 		editorMode: Boolean, | ||||||
| 		create: Boolean, | 		create: Boolean, | ||||||
| 		where: String, | 		where: String, | ||||||
| 		refnode: Object | 		refnode: Object | ||||||
|  | @ -76,11 +79,27 @@ export default { | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			this.editorShow = false; | 			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> | </script> | ||||||
| 
 | 
 | ||||||
| <!-- Add "scoped" attribute to limit CSS to this component only --> | <!-- Add "scoped" attribute to limit CSS to this component only --> | ||||||
| <style scoped> | <style scoped> | ||||||
|  | .changed { | ||||||
|  | 	background-color: yellow; | ||||||
|  | } | ||||||
|  | .delete { | ||||||
|  | 	background-color:  #ff6666; | ||||||
|  | } | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
|  | @ -23,7 +23,8 @@ | ||||||
| 
 | 
 | ||||||
| 		<template v-else v-bind:class="changedObject"> | 		<template v-else v-bind:class="changedObject"> | ||||||
| 			<p v-html="currentText"></p> | 			<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> | 		</template> | ||||||
| 	</div> | 	</div> | ||||||
| </template> | </template> | ||||||
|  | @ -162,6 +163,16 @@ export default { | ||||||
| 			} | 			} | ||||||
| 			this.editorShow = false; | 			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> | </script> | ||||||
|  | @ -169,6 +180,9 @@ export default { | ||||||
| <!-- Add "scoped" attribute to limit CSS to this component only --> | <!-- Add "scoped" attribute to limit CSS to this component only --> | ||||||
| <style scoped> | <style scoped> | ||||||
| .changed { | .changed { | ||||||
| 	background-color: 'yellow'; | 	background-color: yellow; | ||||||
|  | } | ||||||
|  | .delete { | ||||||
|  | 	background-color:  #ff6666; | ||||||
| } | } | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
|  | @ -1,16 +1,19 @@ | ||||||
| <template> | <template> | ||||||
| 
 | 
 | ||||||
| 	<div :class="editorMode ? 'treenode-org' : 'treenode'"> | 	<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> | 	<b v-if="visible">visible</b> | ||||||
| 	Force visible: {{String(force_visible)}}--> | 	Force visible: {{String(force_visible)}}--> | ||||||
| 	<component :is='item.node.polymorphic_ctype.model' :item='item' :key='item.node.id' | 	<component :is='item.node.polymorphic_ctype.model' :item='item' :key='item.node.id' | ||||||
|  | 								:tema="temaOut" | ||||||
| 								:editorMode="editorMode" | 								:editorMode="editorMode" | ||||||
| 								:debugMode="debugMode"></component> | 								: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 --> | 	<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"> | 	<div v-if="debugShow"> | ||||||
|  | 		<pre>Tema: {{ tema }}</pre> | ||||||
|  | 		<pre>TemaOut: {{ temaOut }}</pre> | ||||||
| 		<pre>{{ item.node.polymorphic_ctype.model }}</pre> | 		<pre>{{ item.node.polymorphic_ctype.model }}</pre> | ||||||
| 		<pre>{{ item }}</pre> | 		<pre>{{ item }}</pre> | ||||||
| 	</div> | 	</div> | ||||||
|  | @ -18,31 +21,31 @@ | ||||||
| 	<div v-if="item.children.length === 0"> | 	<div v-if="item.children.length === 0"> | ||||||
| 		<div v-if="item.appendable_children.length > 0 && editorMode"> | 		<div v-if="item.appendable_children.length > 0 && editorMode"> | ||||||
| 			<b>Vložit jako syna: </b> | 			<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> | 	</div> | ||||||
| 
 | 
 | ||||||
| 	<div v-else :class="editorMode ? 'children-org' : 'children'"> <!-- bude tu nějaký if na class="children" --> | 	<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"> | 		<div v-if="item.children.length > 0 && item.children[0].appendable_siblings.length > 0 && editorMode"> | ||||||
| 			<b>Vložit před: </b> | 			<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> | ||||||
| 			<div v-if="item.node.polymorphic_ctype.model==='temavcislenode'"> | 			<div v-if="item.node.polymorphic_ctype.model==='temavcislenode'"> | ||||||
| 				<!--Children: {{String(showChildren)}}--> | 				<!--Children: {{String(showChildren)}}--> | ||||||
| 				<div v-for="(chld, index) in item.children" v-bind:key="chld.nazev" > | 				<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="!hideNode(chld)"> | ||||||
| 						<div v-if="chld.node.polymorphic_ctype.model==='ulohazadaninode'"> | 						<div v-if="chld.node.polymorphic_ctype.model==='ulohazadaninode'"> | ||||||
| 							<button v-if="showChildren" v-on:click="showChildren=!showChildren"> Schovat </button> | 							<button v-if="showChildren" v-on:click="showChildren=!showChildren"> Schovat </button> | ||||||
| 							<button v-else v-on:click="showChildren=!showChildren"> Rozbalit </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" | 									:force_visible="showChildren" | ||||||
| 									:editorMode="editorMode" | 									:editorMode="editorMode" | ||||||
| 									:debugMode="debugMode"> | 									:debugMode="debugMode"> | ||||||
| 							</TreeNode> | 							</TreeNode> | ||||||
| 						</div> | 						</div> | ||||||
| 						<div v-else> | 						<div v-else> | ||||||
| 							<TreeNode :item="chld" :v_tematu="true" | 							<TreeNode :item="chld" :tema="temaOut" | ||||||
| 									:force_visible="showChildren" | 									:force_visible="showChildren" | ||||||
| 									:editorMode="editorMode" | 									:editorMode="editorMode" | ||||||
| 									:debugMode="debugMode"> | 									:debugMode="debugMode"> | ||||||
|  | @ -51,7 +54,7 @@ | ||||||
| 						<div v-if="chld.appendable_siblings.length > 0 && editorMode" > | 						<div v-if="chld.appendable_siblings.length > 0 && editorMode" > | ||||||
| 							<b v-if="index < (item.children.length - 1)">Vložit mezi: </b> | 							<b v-if="index < (item.children.length - 1)">Vložit mezi: </b> | ||||||
| 							<b v-else>Vložit za: </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> | 					</div> | ||||||
| 				</div> | 				</div> | ||||||
|  | @ -60,16 +63,16 @@ | ||||||
| 			</div> | 			</div> | ||||||
| 			<div v-else> | 			<div v-else> | ||||||
| 				<div v-for="(chld, index) in item.children" v-bind:key="chld.nazev" > | 				<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> | 						<div> Tady možná něco je </div> | ||||||
| 						<TreeNode :item="chld" :v_tematu="v_tematu" | 						<TreeNode :item="chld" :tema="temaOut" | ||||||
| 								:force_visible="force_visible" | 								:force_visible="force_visible" | ||||||
| 								:editorMode="editorMode" | 								:editorMode="editorMode" | ||||||
| 								:debugMode="debugMode"> | 								:debugMode="debugMode"> | ||||||
| 						</TreeNode> | 						</TreeNode> | ||||||
| 					</div> | 					</div> | ||||||
| 					<div v-else> | 					<div v-else> | ||||||
| 						<TreeNode :item="chld" :v_tematu="v_tematu" | 						<TreeNode :item="chld" :tema="temaOut" | ||||||
| 								:force_visible="force_visible" | 								:force_visible="force_visible" | ||||||
| 								:editorMode="editorMode" | 								:editorMode="editorMode" | ||||||
| 								:debugMode="debugMode"> | 								:debugMode="debugMode"> | ||||||
|  | @ -78,7 +81,7 @@ | ||||||
| 					<div v-if="chld.appendable_siblings.length > 0 && editorMode" > | 					<div v-if="chld.appendable_siblings.length > 0 && editorMode" > | ||||||
| 						<b v-if="index < (item.children.length - 1)">Vložit mezi: </b> | 						<b v-if="index < (item.children.length - 1)">Vložit mezi: </b> | ||||||
| 						<b v-else>Vložit za: </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> | 				</div> | ||||||
| 			</div> | 			</div> | ||||||
|  | @ -112,14 +115,15 @@ export default { | ||||||
| 	}, | 	}, | ||||||
| 	data: () => ({ | 	data: () => ({ | ||||||
| 		debugShow: false, | 		debugShow: false, | ||||||
| 		showChildren: false | 		showChildren: false, | ||||||
|  | 		temaOut: null, | ||||||
| 	}), | 	}), | ||||||
| 	computed: { | 	computed: { | ||||||
| 	}, | 	}, | ||||||
| 	props: { | 	props: { | ||||||
| 		item: Object, | 		item: Object, | ||||||
| 		force_visible: Boolean, | 		force_visible: Boolean, | ||||||
| 		v_tematu: Boolean, | 		tema: Object, | ||||||
| 		editorMode: Boolean, | 		editorMode: Boolean, | ||||||
| 		debugMode: Boolean, | 		debugMode: Boolean, | ||||||
| 	}, | 	}, | ||||||
|  | @ -132,7 +136,17 @@ export default { | ||||||
| 				return false; | 				return false; | ||||||
| 			} | 			} | ||||||
| 			return true; | 			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> | </script> | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ | ||||||
| 		<div id="loading" v-if="loading"> | 		<div id="loading" v-if="loading"> | ||||||
| 		Loading... | 		Loading... | ||||||
| 		</div> | 		</div> | ||||||
|  | 		<div v-else> | ||||||
| 		<!--pre> | 		<!--pre> | ||||||
| 		{{item}} | 		{{item}} | ||||||
| 		</pre--> | 		</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 = false">Vypnout ladicí mód</button> | ||||||
| 		<button v-show="!debugMode" v-on:click="debugMode = true">Zapnout 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"/> | 		<TreeNode :item="item" :editorMode="editorMode" :debugMode="debugMode"/> | ||||||
|  | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,18 +1,23 @@ | ||||||
| <template> | <template> | ||||||
| 	<div class="ulohavzoraknode"> | 	<div class="ulohavzoraknode"> | ||||||
|  | 		<template v-if="editorShow"> | ||||||
| 		<!--pre>UlohaVzorakNode {{item}} {{typeof(item)}}</pre--> | 		<!--pre>UlohaVzorakNode {{item}} {{typeof(item)}}</pre--> | ||||||
| 		<h5>Řešení {{item.node.uloha.cislo_zadani.poradi}}.{{ item.node.uloha.kod }}: {{ item.node.uloha.nazev }}</h5> | 		<!--h5 v-if="!editorMode">Ř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> | 				<form class="searchForm" v-on:submit.prevent="submitSearch"> | ||||||
| 		<div v-if="showSelect"> | 					<input type="text" v-model="searchQuery" placeholder="Napište název" @keyup="submitSearch"> | ||||||
| 			<form class="searchForm" v-on:submit.prevent="submitSearch"> | 				</form> | ||||||
| 				<input type="text" v-model="searchQuery" placeholder="Napište název" @keyup="submitSearch"> | 				<div class="searchResult" v-show="isResult"> | ||||||
| 			</form> | 					<ul> | ||||||
| 			<div class="searchResult" v-show="isResult"> | 						<li v-for="res in searchResults" :key="res.id" v-on:click="setSelected(res)">{{res.nazev}}</li> | ||||||
| 				<ul> | 					</ul> | ||||||
| 					<li v-for="res in searchResults" :key="res.id" v-on:click="setSelected(res)">{{res.nazev}}</li> | 				</div> | ||||||
| 				</ul> | 				<button v-if="create" v-on:click="createNode">Vytvořit vzorák</button> | ||||||
| 			</div> | 				<button v-if="!create" v-on:click="saveNode">Uložit</button> | ||||||
| 		</div> | 		</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> | 	</div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
|  | @ -26,31 +31,58 @@ export default { | ||||||
| 			isResult: false, | 			isResult: false, | ||||||
| 			searchQuery: '', | 			searchQuery: '', | ||||||
| 			searchResults: [], | 			searchResults: [], | ||||||
| 			showSelect: false, |  | ||||||
| 			selected: null, | 			selected: null, | ||||||
| 			selected_id: null | 			selected_id: null, | ||||||
|  | 			editorShow: false, | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
| 	props: { | 	props: { | ||||||
| 		item: Object, | 		item: Object, | ||||||
| 		create: Boolean, | 		create: Boolean, | ||||||
| 		showSelect: Boolean, |  | ||||||
| 		editorMode: Boolean, | 		editorMode: Boolean, | ||||||
|  | 		editorShow: Boolean, | ||||||
|  | 		tema: Object, | ||||||
|  | 		refnode: Object, | ||||||
|  | 		where: String, | ||||||
| 	}, | 	}, | ||||||
| 	mounted: function(){ | 	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("Uloha je null!"); | ||||||
| 			console.log(this.item); | 			console.log(this.item); | ||||||
| 		} | 		} | ||||||
| 		if (this.create){ |  | ||||||
| 			this.showSelect = true; |  | ||||||
| 			console.log('Creating'); |  | ||||||
| 		} |  | ||||||
| 	}, | 	}, | ||||||
| 	methods: { | 	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(){ | 		submitSearch: function(){ | ||||||
| 			if (this.searchQuery.length < 3) { return;} | 			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) => { | 			axios.get(reqURL).then( (response) => { | ||||||
| 				this.searchResults = response.data.results; | 				this.searchResults = response.data.results; | ||||||
| 				this.isResult = true; | 				this.isResult = true; | ||||||
|  | @ -63,6 +95,38 @@ export default { | ||||||
| 		setSelected: function(res){ | 		setSelected: function(res){ | ||||||
| 			this.searchQuery = res.nazev | 			this.searchQuery = res.nazev | ||||||
| 			this.selected_id = res.id | 			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> | <template> | ||||||
| 	<div class="ulohazadaninode"> | 	<div class="ulohazadaninode"> | ||||||
| 		<!--pre>UlohaZadaniNode {{item.node.uloha}} {{typeof(item)}}</pre--> | 		<template v-if="editorShow"> | ||||||
| 		<h5>Zadání {{item.node.uloha.cislo_zadani.poradi}}.{{ item.node.uloha.kod }}: {{ item.node.uloha.nazev }}</h5> | 			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> | 	</div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script> | <script> | ||||||
|  | import axios from 'axios' | ||||||
|  | 
 | ||||||
| export default { | export default { | ||||||
| 	name: 'UlohaZadaniNode', | 	name: 'UlohaZadaniNode', | ||||||
|  | 	data: () => ({ | ||||||
|  | 		max_body: 0, | ||||||
|  | 		kod: "", | ||||||
|  | 		editorShow: false, | ||||||
|  | 		editorMode: false, | ||||||
|  | 	}), | ||||||
| 	props: { | 	props: { | ||||||
| 		item: Object, | 		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(){ | 	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){ | 		if (this.item.node.uloha === null){ | ||||||
| 			console.log("Uloha je null!"); | 			console.log("Uloha je null!"); | ||||||
| 			console.log(this.item); | 			console.log(this.item); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| } |  | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | @ -20,7 +20,7 @@ export default new Router({ | ||||||
| 	}, { | 	}, { | ||||||
| 		path: '/zadani/aktualni', | 		path: '/zadani/aktualni', | ||||||
| 		name: 'treenode_zadani', | 		name: 'treenode_zadani', | ||||||
| 		props: {'tnid': 23}, | 		props: {'tnid': 1655}, | ||||||
| 		component: TreeNodeRoot | 		component: TreeNodeRoot | ||||||
| 	}, { | 	}, { | ||||||
| 		path: '/cislo/:cislo', | 		path: '/cislo/:cislo', | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ const pages = { | ||||||
| module.exports = { | module.exports = { | ||||||
|     pages: pages, |     pages: pages, | ||||||
|     filenameHashing: false, |     filenameHashing: false, | ||||||
|     productionSourceMap: false, |     productionSourceMap: true, | ||||||
|     publicPath: process.env.NODE_ENV === 'production' |     publicPath: process.env.NODE_ENV === 'production' | ||||||
|         ? '/static/seminar/vue/' |         ? '/static/seminar/vue/' | ||||||
|         : 'http://localhost:8080/', |         : 'http://localhost:8080/', | ||||||
|  | @ -23,6 +23,7 @@ module.exports = { | ||||||
| 
 | 
 | ||||||
|     chainWebpack: config => { |     chainWebpack: config => { | ||||||
| 
 | 
 | ||||||
|  | 	config.optimization.minimize(false) | ||||||
|         config.optimization |         config.optimization | ||||||
|             .splitChunks({ |             .splitChunks({ | ||||||
|                 cacheGroups: { |                 cacheGroups: { | ||||||
|  | @ -33,7 +34,7 @@ module.exports = { | ||||||
|                         priority: 1 |                         priority: 1 | ||||||
|                     }, |                     }, | ||||||
|                 }, |                 }, | ||||||
|             }); |             }).minimize(false); | ||||||
| 
 | 
 | ||||||
|         Object.keys(pages).forEach(page => { |         Object.keys(pages).forEach(page => { | ||||||
|             config.plugins.delete(`html-${page}`); |             config.plugins.delete(`html-${page}`); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Pavel "LEdoian" Turinsky
						Pavel "LEdoian" Turinsky