Merge branch 'develop' into test
This commit is contained in:
		
						commit
						26854d6fe2
					
				
					 100 changed files with 3714 additions and 3516 deletions
				
			
		
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							|  | @ -102,7 +102,7 @@ deploy_prod: venv_check | ||||||
| sync_prod_flatpages: venv_check  | sync_prod_flatpages: venv_check  | ||||||
| 	@echo Downloading current version of flatpages from mamweb-prod. | 	@echo Downloading current version of flatpages from mamweb-prod. | ||||||
| 	ssh mam-web@gimli.ms.mff.cuni.cz \
 | 	ssh mam-web@gimli.ms.mff.cuni.cz \
 | ||||||
| 	"cd /akce/mam/www/mamweb-prod; ./manage.py dumpdata flatpages --indent=2 > flat.json" | 	"cd /akce/mam/www/mamweb-prod; . env/bin/activate; ./manage.py dumpdata flatpages --indent=2 > flat.json" | ||||||
| 	rsync -ave ssh mam-web@gimli.ms.mff.cuni.cz:/akce/mam/www/mamweb-prod/flat.json ./flat.json | 	rsync -ave ssh mam-web@gimli.ms.mff.cuni.cz:/akce/mam/www/mamweb-prod/flat.json ./flat.json | ||||||
| 	@echo "Applying downloaded flatpages." | 	@echo "Applying downloaded flatpages." | ||||||
| 	./manage.py loaddata flat.json | 	./manage.py loaddata flat.json | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ from django.utils.encoding import force_text | ||||||
| 
 | 
 | ||||||
| from .utils import default_ovvpfile | from .utils import default_ovvpfile | ||||||
| from seminar.models import Rocnik, Soustredeni | from seminar.models import Rocnik, Soustredeni | ||||||
| from seminar.views import vysledkovka | from vysledkovky import utils | ||||||
| from seminar.utils import aktivniResitele | from seminar.utils import aktivniResitele | ||||||
| 
 | 
 | ||||||
| class ExportIndexView(generic.View): | class ExportIndexView(generic.View): | ||||||
|  | @ -66,8 +66,8 @@ class ExportRocnikView(generic.View): | ||||||
| 		rocnik = get_object_or_404(Rocnik, prvni_rok=pr, exportovat=True) | 		rocnik = get_object_or_404(Rocnik, prvni_rok=pr, exportovat=True) | ||||||
| 		cislo = rocnik.posledni_zverejnena_vysledkovka_cislo() | 		cislo = rocnik.posledni_zverejnena_vysledkovka_cislo() | ||||||
| 		resitele = aktivniResitele(cislo, True) | 		resitele = aktivniResitele(cislo, True) | ||||||
| 		slovnik_body = vysledkovka.secti_body_za_rocnik(cislo, resitele, False) | 		slovnik_body = utils.secti_body_za_rocnik(cislo, resitele, False) | ||||||
| 		setrizeni_resitele, body = vysledkovka.setrid_resitele_a_body(slovnik_body) | 		setrizeni_resitele, body = utils.setrid_resitele_a_body(slovnik_body) | ||||||
| 
 | 
 | ||||||
| 		of = default_ovvpfile('MaM.rocnik', rocnik) | 		of = default_ovvpfile('MaM.rocnik', rocnik) | ||||||
| 		of.headers['comment'] = u'MaM-Web export aktivnich resitelu rocniku {rocnik} do cisla {cislo}'.format(rocnik=rocnik, cislo=cislo) | 		of.headers['comment'] = u'MaM-Web export aktivnich resitelu rocniku {rocnik} do cisla {cislo}'.format(rocnik=rocnik, cislo=cislo) | ||||||
|  |  | ||||||
|  | @ -138,6 +138,11 @@ INSTALLED_APPS = ( | ||||||
|     'various.autentizace', |     'various.autentizace', | ||||||
|     'api', |     'api', | ||||||
|     'aesop', |     'aesop', | ||||||
|  |     'odevzdavatko', | ||||||
|  |     'vysledkovky', | ||||||
|  |     'personalni', | ||||||
|  |     'soustredeni', | ||||||
|  |     'treenode', | ||||||
| 
 | 
 | ||||||
|     # Admin upravy: |     # Admin upravy: | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ from django.views.generic.base import TemplateView | ||||||
| from django import views | from django import views | ||||||
| from django.urls import path # As per docs. | from django.urls import path # As per docs. | ||||||
| 
 | 
 | ||||||
| from .routers import router | from treenode.routers import router | ||||||
| 
 | 
 | ||||||
| urlpatterns = [ | urlpatterns = [ | ||||||
| 
 | 
 | ||||||
|  | @ -17,18 +17,31 @@ urlpatterns = [ | ||||||
| 	# Seminarova aplikace (ma vlastni podadresare) | 	# Seminarova aplikace (ma vlastni podadresare) | ||||||
| 	path('', include('seminar.urls')), | 	path('', include('seminar.urls')), | ||||||
| 
 | 
 | ||||||
|  | 	# Odevzdavatko (ma vlastni podadresare) | ||||||
|  | 	path('', include('odevzdavatko.urls')), | ||||||
|  | 	 | ||||||
| 	# Korekturovaci aplikace (ma vlastni podadresare) | 	# Korekturovaci aplikace (ma vlastni podadresare) | ||||||
| 	path('', include('korektury.urls')), | 	path('', include('korektury.urls')), | ||||||
| 
 | 
 | ||||||
| 	# Prednaskova aplikace (ma vlastni podadresare) | 	# Prednaskova aplikace (ma vlastni podadresare) | ||||||
| 	path('', include('prednasky.urls')), | 	path('', include('prednasky.urls')), | ||||||
| 
 | 
 | ||||||
|  | 	# Soustredkova aplikace (ma vlastni podadresare) | ||||||
|  | 	path('', include('soustredeni.urls')), | ||||||
|  | 
 | ||||||
|  | 	# Personalni aplikace (ma vlastni podadresare) | ||||||
|  | 	# (profil, osobní údaje, ..., ne autentizace, viz dále) | ||||||
|  | 	path('', include('personalni.urls')), | ||||||
|  | 
 | ||||||
| 	# Autentizační aplikace (ma vlastni podadresare) | 	# Autentizační aplikace (ma vlastni podadresare) | ||||||
| 	path('', include('various.autentizace.urls')), | 	path('', include('various.autentizace.urls')), | ||||||
| 
 | 
 | ||||||
| 	# Api (ma vlastni podadresare) (autocomplete apod.) | 	# Api (ma vlastni podadresare) (autocomplete apod.) | ||||||
| 	path('', include('api.urls')), | 	path('', include('api.urls')), | ||||||
| 
 | 
 | ||||||
|  | 	# treenode (ma vlastni podadresare) | ||||||
|  | 	path('', include('treenode.urls')), | ||||||
|  | 
 | ||||||
| 	# Aesop (ma vlastni podadresare) | 	# Aesop (ma vlastni podadresare) | ||||||
| 	path('', include('aesop.urls')), | 	path('', include('aesop.urls')), | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										11
									
								
								odevzdavatko/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								odevzdavatko/__init__.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | ||||||
|  | """ | ||||||
|  | Obsahuje vše, co se týká odevzdávání (+ nahrávání) a opravování řešení řešitelů. | ||||||
|  | 
 | ||||||
|  | Slovníček: | ||||||
|  |     Moje řešení = Přehled řešení = Řešení, která odevzdal aktuálního uživatel sám. | ||||||
|  |     Došlá řešení = Tabulka + seznam + detail + ... = Řešení, která poslal někdo jiný. | ||||||
|  |     Poslat řešení = Odevdat mé řešení. (Tj. řešení se vztahem k aktuálnímu uživateli.) | ||||||
|  |     Nahrát řešení = Nahrání řešení bez vztahu k aktuálnímu uživateli. | ||||||
|  | 
 | ||||||
|  | TODO: Místo vložit řešení v nahrávání a posílání řešení dát něco jiného? | ||||||
|  | """ | ||||||
							
								
								
									
										28
									
								
								odevzdavatko/admin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								odevzdavatko/admin.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,28 @@ | ||||||
|  | from django.contrib import admin | ||||||
|  | from django_reverse_admin import ReverseModelAdmin | ||||||
|  | import seminar.models as m | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class PrilohaReseniInline(admin.TabularInline): | ||||||
|  | 	model = m.PrilohaReseni | ||||||
|  | 	extra = 1 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Reseni_ResiteleInline(admin.TabularInline): | ||||||
|  | 	model = m.Reseni_Resitele | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @admin.register(m.Reseni) | ||||||
|  | class ReseniAdmin(ReverseModelAdmin): | ||||||
|  | 	base_model = m.Reseni | ||||||
|  | 	inline_type = 'tabular' | ||||||
|  | 	# inline_reverse = ['text_cely','resitele'] TODO vrátit zpět a zrychlit dotaz | ||||||
|  | 	inline_reverse = ['resitele'] | ||||||
|  | 	exclude = ['text_zkraceny', 'text_zkraceny_set'] | ||||||
|  | 	inlines = [PrilohaReseniInline] | ||||||
|  | # FAIL in template | ||||||
|  | #	inlines = [PrilohaReseniInline,Reseni_ResiteleInline] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | admin.site.register(m.PrilohaReseni) | ||||||
|  | admin.site.register(m.Hodnoceni) | ||||||
							
								
								
									
										5
									
								
								odevzdavatko/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								odevzdavatko/apps.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | from django.apps import AppConfig | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class OdevzdavatkoConfig(AppConfig): | ||||||
|  |     name = 'odevzdavatko' | ||||||
							
								
								
									
										218
									
								
								odevzdavatko/forms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										218
									
								
								odevzdavatko/forms.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,218 @@ | ||||||
|  | from django import forms | ||||||
|  | from dal import autocomplete | ||||||
|  | from django.forms import formset_factory | ||||||
|  | from django.forms.models import inlineformset_factory | ||||||
|  | 
 | ||||||
|  | from seminar.models import Resitel | ||||||
|  | import seminar.models as m | ||||||
|  | 
 | ||||||
|  | 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 PosliReseniForm(forms.Form): | ||||||
|  | 	#FIXME jen podproblémy daného problému | ||||||
|  | 	problem = forms.ModelChoiceField(label='Problém',queryset=m.Problem.objects.all()) | ||||||
|  | 	# to_field_name | ||||||
|  | 	#problem = models.ManyToManyField(Problem, verbose_name='problém', help_text='Problém', | ||||||
|  | 	#	through='Hodnoceni') | ||||||
|  | 
 | ||||||
|  | 	# FIXME pridat vice resitelu | ||||||
|  | 	resitel = forms.ModelChoiceField(label="Řešitel", | ||||||
|  | 		queryset=Resitel.objects.all(), | ||||||
|  | 		widget=autocomplete.ModelSelect2( | ||||||
|  | 			url='autocomplete_resitel', | ||||||
|  | 			attrs = {'data-placeholder--id': '-1', | ||||||
|  | 				'data-placeholder--text' : '---', | ||||||
|  | 				'data-allow-clear': 'true'}) | ||||||
|  |     		) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 	#resitele = models.ManyToManyField(Resitel, verbose_name='autoři řešení', | ||||||
|  | 	#	help_text='Seznam autorů řešení', through='Reseni_Resitele') | ||||||
|  | 	 | ||||||
|  | 	cas_doruceni = forms.DateField(widget=DateInput(),label="Čas doručení") | ||||||
|  | 
 | ||||||
|  | 	#cas_doruceni = models.DateTimeField('čas_doručení', default=timezone.now, blank=True) | ||||||
|  | 
 | ||||||
|  | 	forma = forms.ChoiceField(label="Forma řešení",choices = m.Reseni.FORMA_CHOICES) | ||||||
|  | 	#forma = models.CharField('forma řešení', max_length=16, choices=FORMA_CHOICES, blank=False, | ||||||
|  | 	#	 default=FORMA_EMAIL) | ||||||
|  | 
 | ||||||
|  | 	poznamka = forms.CharField(label='Neveřejná poznámka', required=False) | ||||||
|  | 	#poznamka = models.TextField('neveřejná poznámka', blank=True, | ||||||
|  | 	#	help_text='Neveřejná poznámka k řešení (plain text)') | ||||||
|  | 
 | ||||||
|  | 	#TODO body do cisla | ||||||
|  | 	#TODO prilohy | ||||||
|  | 
 | ||||||
|  | 	##def __init__(self, *args, **kwargs): | ||||||
|  | 	##	super().__init__(*args, **kwargs) | ||||||
|  | 	##	#self.fields['favorite_color'] = forms.ChoiceField(choices=[(color.id, color.name) for color in Resitel.objects.all()]) | ||||||
|  | 
 | ||||||
|  | class NahrajReseniForm(forms.ModelForm): | ||||||
|  | 	class Meta: | ||||||
|  | 		model = m.Reseni | ||||||
|  | 		fields = ('problem',) | ||||||
|  | 		help_texts = {'problem':''} # Nezobrazovat help text ve formuláři | ||||||
|  | 		 | ||||||
|  | 		widgets = {'problem': | ||||||
|  | 				autocomplete.ModelSelect2Multiple( | ||||||
|  | 					url='autocomplete_problem_odevzdatelny', | ||||||
|  | 					attrs = {'data-placeholder--id': '-1', | ||||||
|  | 						'data-placeholder--text' : '---', | ||||||
|  | 						'data-allow-clear': 'true'}, | ||||||
|  | 				) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | ReseniSPrilohamiFormSet = inlineformset_factory(m.Reseni,m.PrilohaReseni,  | ||||||
|  | 		form = NahrajReseniForm, | ||||||
|  | 		fields = ('soubor','res_poznamka'), | ||||||
|  | 		widgets = {'res_poznamka':forms.TextInput()}, | ||||||
|  | 		extra = 1, | ||||||
|  | 		can_delete = False, | ||||||
|  | 
 | ||||||
|  | 		) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class JednoHodnoceniForm(forms.ModelForm): | ||||||
|  | 	class Meta: | ||||||
|  | 		model = m.Hodnoceni | ||||||
|  | 		fields = ('problem', 'body', 'cislo_body') | ||||||
|  | 		widgets = { | ||||||
|  | 			'problem': autocomplete.ModelSelect2( | ||||||
|  | 				url='autocomplete_problem_odevzdatelny',   # FIXME: Dovolit i starší? | ||||||
|  | 				) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | OhodnoceniReseniFormSet = formset_factory(JednoHodnoceniForm, | ||||||
|  | 		extra = 0, | ||||||
|  | 		) | ||||||
|  | 
 | ||||||
|  | class PoznamkaReseniForm(forms.ModelForm): | ||||||
|  | 	class Meta: | ||||||
|  | 		model = m.Reseni | ||||||
|  | 		fields = ('poznamka',) | ||||||
|  | 
 | ||||||
|  | # FIXME: Ideálně by mělo být součástí třídy níž, ale neumím to udělat | ||||||
|  | DATE_FORMAT = '%Y-%m-%d' | ||||||
|  | 
 | ||||||
|  | class OdevzdavatkoTabulkaFiltrForm(forms.Form): | ||||||
|  | 	"""Form pro filtrování přehledové odevzdávátkové tabulky | ||||||
|  | 
 | ||||||
|  | 	Inspirováno https://kam.mff.cuni.cz/mffzoom/""" | ||||||
|  | 
 | ||||||
|  | 	# Věci definované níž se importují i ve views pro odevzdávátko (Inspirováno https://docs.djangoproject.com/en/3.1/ref/models/fields/#field-choices) | ||||||
|  | 
 | ||||||
|  | 	RESITELE_RELEVANTNI = 'relevantni' | ||||||
|  | 	RESITELE_NEODMATUROVAVSI = 'neodmaturovavsi' | ||||||
|  | 	RESITELE_CHOICES = [ | ||||||
|  | 		(RESITELE_RELEVANTNI, 'Relevantní řešitelé'), # I.e. nezobrazovat prázdné řádky tabulky | ||||||
|  | 		(RESITELE_NEODMATUROVAVSI, 'Všichni bez maturity'), | ||||||
|  | 		# Možná: všechny vč. historických? | ||||||
|  | 		] | ||||||
|  | 
 | ||||||
|  | 	PROBLEMY_MOJE = 'moje' | ||||||
|  | 	PROBLEMY_LETOSNI = 'letosni' | ||||||
|  | 	PROBLEMY_CHOICES = [ | ||||||
|  | 		(PROBLEMY_MOJE, 'Moje problémy'), # Letošní problémy, které mají v sobě nebo v nadproblémech přiřazeného daného orga | ||||||
|  | 		(PROBLEMY_LETOSNI, 'Všechny letošní'), | ||||||
|  | 		# TODO: *hlavní problémy, možná všechny... | ||||||
|  | 		# XXX: Chtělo by to i "aktuálně zadané... | ||||||
|  | 		] | ||||||
|  | 
 | ||||||
|  | 	# TODO: Typy problémů (problémy, úlohy, ostatní, všechny)? Jen některá řešení (obodovaná/neobodovaná, víc řešitelů, ...)? | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 	@classmethod | ||||||
|  | 	def gen_terminy(cls, rocnik=None): | ||||||
|  | 		import datetime | ||||||
|  | 		from time import strftime | ||||||
|  | 		 | ||||||
|  | 		from django.db.utils import OperationalError | ||||||
|  | 		try: | ||||||
|  | 			aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik | ||||||
|  | 			aktualni_cislo = m.Nastaveni.get_solo().aktualni_cislo | ||||||
|  | 		except OperationalError: | ||||||
|  | 			# django.db.utils.OperationalError: no such table: seminar_nastaveni | ||||||
|  | 			# Nemáme databázi, takže to selhalo. Pro jistotu vrátíme aspoň dvě možnosti, ať to nepadá dál | ||||||
|  | 			logger = logging.getLogger(__name__) | ||||||
|  | 			logger.error("Rozbitá databáze (před počátečními migracemi?)") | ||||||
|  | 			return [('broken', 'Je to rozbitý'), ('fubar', 'Nefunguje to')] | ||||||
|  | 
 | ||||||
|  | 		# FIXME: Tohle je hnusný monkey patch, mělo by to být nějak zahrnuto výš. | ||||||
|  | 		if rocnik is not None: | ||||||
|  | 			aktualni_rocnik = rocnik | ||||||
|  | 			aktualni_cislo = m.Cislo.objects.filter(rocnik=rocnik).order_by('poradi').last() | ||||||
|  | 
 | ||||||
|  | 		result = [] | ||||||
|  | 
 | ||||||
|  | 		for cislo in m.Cislo.objects.filter( | ||||||
|  | 				rocnik=aktualni_rocnik, | ||||||
|  | 				poradi__lte=aktualni_cislo.poradi, | ||||||
|  | 				).reverse():	# Standardně se řadí od nejnovějšího čísla | ||||||
|  | 			# Předem je mi líto kohokoliv, kdo tyhle řádky bude číst... | ||||||
|  | 			if cislo.datum_vydani is not None and cislo.datum_vydani <= datetime.date.today(): | ||||||
|  | 				result.append(( | ||||||
|  | 					strftime(DATE_FORMAT, cislo.datum_vydani.timetuple()), | ||||||
|  | 					f"Vydání {cislo.poradi}. čísla")) | ||||||
|  | 			if cislo.datum_preddeadline is not None and cislo.datum_preddeadline <= datetime.date.today(): | ||||||
|  | 				result.append(( | ||||||
|  | 					strftime(DATE_FORMAT, cislo.datum_preddeadline.timetuple()), | ||||||
|  | 					f"Předdeadline {cislo.poradi}. čísla")) | ||||||
|  | 			if cislo.datum_deadline_soustredeni is not None and cislo.datum_deadline_soustredeni <= datetime.date.today(): | ||||||
|  | 				result.append(( | ||||||
|  | 					strftime(DATE_FORMAT, cislo.datum_deadline_soustredeni.timetuple()), | ||||||
|  | 					f"Sous. deadline {cislo.poradi}. čísla")) | ||||||
|  | 			if cislo.datum_deadline is not None and cislo.datum_deadline <= datetime.date.today(): | ||||||
|  | 				result.append(( | ||||||
|  | 					strftime(DATE_FORMAT, cislo.datum_deadline.timetuple()), | ||||||
|  | 					f"Finální deadline {cislo.poradi}. čísla")) | ||||||
|  | 		result.append(( | ||||||
|  | 			strftime(DATE_FORMAT, datetime.date.today().timetuple()), f"Dnes")) | ||||||
|  | 
 | ||||||
|  | 		return result | ||||||
|  | 
 | ||||||
|  | 	@classmethod | ||||||
|  | 	def gen_initial(cls, rocnik=None): | ||||||
|  | 		terminy = cls.gen_terminy(rocnik) | ||||||
|  | 		initial = { | ||||||
|  | 			'resitele': cls.RESITELE_RELEVANTNI, | ||||||
|  | 			'problemy': cls.PROBLEMY_MOJE, | ||||||
|  | 			# Pokud chceme neaktuální ročník, tak nás nejspíš zajímají všechna řešení… | ||||||
|  | 			'reseni_od': terminy[-2] if rocnik is None else terminy[0], | ||||||
|  | 			'reseni_do': terminy[-1], | ||||||
|  | 			'neobodovane': False, | ||||||
|  | 		} | ||||||
|  | 		return initial | ||||||
|  | 
 | ||||||
|  | 	def __init__(self, *args, rocnik=None, **kwargs): | ||||||
|  | 		if 'initial' not in kwargs: | ||||||
|  | 			super().__init__(initial=self.gen_initial(rocnik), *args, **kwargs) | ||||||
|  | 		else: | ||||||
|  | 			super().__init__(*args, **kwargs) | ||||||
|  | 		# choices jako parametr Select widgetu neumí brát callable, jen iterable, takže si pro jednoduchost můžu rovnou uložit výsledek sem... | ||||||
|  | 		# A "sem" znamená do libovolné metody, protože jinak se jedná o kód, který django spustí při inicializaci a protože potřebujeme databázi, tak by spadnul při vyrábění testdat... | ||||||
|  | 		self.terminy = self.gen_terminy(rocnik) | ||||||
|  | 		self.fields['reseni_od'].widget = forms.Select(choices=self.gen_terminy(rocnik)) | ||||||
|  | 		# Pokud chceme neaktuální ročník, tak nás nejspíš zajímají všechna řešení… | ||||||
|  | 		self.fields['reseni_od'].initial = self.terminy[-2] if rocnik is None else self.terminy[0] | ||||||
|  | 		self.fields['reseni_do'].widget = forms.Select(choices=self.gen_terminy(rocnik)) | ||||||
|  | 		self.fields['reseni_do'].initial = self.terminy[-1] | ||||||
|  | 
 | ||||||
|  | 	# NOTE: Initial definuji pro jednotlivé fieldy, aby to bylo tady a nebylo potřeba to řešit ve views... | ||||||
|  | 	resitele = forms.ChoiceField(choices=RESITELE_CHOICES) | ||||||
|  | 	problemy = forms.ChoiceField(choices=PROBLEMY_CHOICES) | ||||||
|  | 	 | ||||||
|  | 	reseni_od = forms.DateField(input_formats=[DATE_FORMAT]) | ||||||
|  | 	reseni_do = forms.DateField(input_formats=[DATE_FORMAT]) | ||||||
|  | 	neobodovane = forms.BooleanField(required=False) | ||||||
							
								
								
									
										0
									
								
								odevzdavatko/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								odevzdavatko/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							| Before Width: | Height: | Size: 717 B After Width: | Height: | Size: 717 B | 
| Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB | 
|  | @ -4,7 +4,7 @@ | ||||||
| 
 | 
 | ||||||
| {% block content %} | {% block content %} | ||||||
| 
 | 
 | ||||||
| {# FIXME: Necopypastovat! Tohle je zkopírované ze static/seminar/dynamic_formsets.js #} | {# FIXME: Necopypastovat! Tohle je zkopírované ze static/odevzdavatko/dynamic_formsets.js #} | ||||||
| <script type='text/javascript'> | <script type='text/javascript'> | ||||||
| // Credit https://medium.com/all-about-django/adding-forms-dynamically-to-a-django-formset-375f1090c2b0 | // Credit https://medium.com/all-about-django/adding-forms-dynamically-to-a-django-formset-375f1090c2b0 | ||||||
| function updateElementIndex(el, prefix, ndx) { | function updateElementIndex(el, prefix, ndx) { | ||||||
|  | @ -104,14 +104,14 @@ $(document).ready(function(){ | ||||||
| 		<td>{{ subform.problem }}</td> | 		<td>{{ subform.problem }}</td> | ||||||
| 		<td>{{ subform.body }}</td> | 		<td>{{ subform.body }}</td> | ||||||
| 		<td>{{ subform.cislo_body }}</td> | 		<td>{{ subform.cislo_body }}</td> | ||||||
| 		<td><a href="#" class="smazat_hodnoceni" id="id_{{subform.prefix}}-jsremove"><img src="{% static "seminar/cross.png" %}" alt="Smazat"></a></td> | 		<td><a href="#" class="smazat_hodnoceni" id="id_{{subform.prefix}}-jsremove"><img src="{% static "odevzdavatko/cross.png" %}" alt="Smazat"></a></td> | ||||||
| 	</tr> | 	</tr> | ||||||
|     </tbody> |     </tbody> | ||||||
| {% endfor %} | {% endfor %} | ||||||
| </table> | </table> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| <a href="#"> <img src="{% static "seminar/plus.png" %}" id="pridat_hodnoceni" alt="Přidat hodnocení"></a> </br> | <a href="#"> <img src="{% static "odevzdavatko/plus.png" %}" id="pridat_hodnoceni" alt="Přidat hodnocení"></a> </br> | ||||||
| <input type=submit value="Uložit"></form> | <input type=submit value="Uložit"></form> | ||||||
| 
 | 
 | ||||||
| <table id="empty_form" style="display: none;"> | <table id="empty_form" style="display: none;"> | ||||||
|  | @ -119,7 +119,7 @@ $(document).ready(function(){ | ||||||
| 		<td>{{ form.empty_form.problem }}</td> | 		<td>{{ form.empty_form.problem }}</td> | ||||||
| 		<td>{{ form.empty_form.body }}</td> | 		<td>{{ form.empty_form.body }}</td> | ||||||
| 		<td>{{ form.empty_form.cislo_body }}</td> | 		<td>{{ form.empty_form.cislo_body }}</td> | ||||||
| 		<td><a href="#" class="smazat_hodnoceni" id="id_{{subform.prefix}}-jsremove"><img src="{% static "seminar/cross.png" %}" alt="Smazat"></a></td> | 		<td><a href="#" class="smazat_hodnoceni" id="id_{{subform.prefix}}-jsremove"><img src="{% static "odevzdavatko/cross.png" %}" alt="Smazat"></a></td> | ||||||
| 	</tr> | 	</tr> | ||||||
| </table> | </table> | ||||||
| 
 | 
 | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| {% extends "base.html" %} | {% extends "base.html" %} | ||||||
| {% load staticfiles %} | {% load staticfiles %} | ||||||
| {% block script %} | {% block script %} | ||||||
|     <script src="{% static 'seminar/dynamic_formsets.js' %}"></script> |     <script src="{% static 'odevzdavatko/dynamic_formsets.js' %}"></script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
| 
 | 
 | ||||||
| {% block content %} | {% block content %} | ||||||
|  | @ -3,7 +3,6 @@ | ||||||
| {% block script %} | {% block script %} | ||||||
|     <!--script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script!--> |     <!--script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script!--> | ||||||
|     {{form.media}} |     {{form.media}} | ||||||
|     <script src="{% static 'seminar/prihlaska.js' %}"></script> |  | ||||||
| {% endblock %} | {% endblock %} | ||||||
| 
 | 
 | ||||||
| {% block content %} | {% block content %} | ||||||
|  | @ -4,7 +4,7 @@ | ||||||
| 
 | 
 | ||||||
| {% block content %} | {% block content %} | ||||||
| 
 | 
 | ||||||
| <form method=get action=.> | <form method=get action=../odevzdavatko> | ||||||
| {{ filtr.resitele }} | {{ filtr.resitele }} | ||||||
| {{ filtr.problemy }} | {{ filtr.problemy }} | ||||||
| Od: {{ filtr.reseni_od }} | Od: {{ filtr.reseni_od }} | ||||||
							
								
								
									
										20
									
								
								odevzdavatko/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								odevzdavatko/urls.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | ||||||
|  | from django.urls import path | ||||||
|  | 
 | ||||||
|  | from seminar.utils import org_required, resitel_required, viewMethodSwitch, \ | ||||||
|  | 	resitel_or_org_required | ||||||
|  | from . import views | ||||||
|  | 
 | ||||||
|  | urlpatterns = [ | ||||||
|  | 	path('org/add_solution', org_required(views.PosliReseniView.as_view()), name='seminar_vloz_reseni'), | ||||||
|  | 	path('resitel/nahraj_reseni', resitel_required(views.NahrajReseniView.as_view()), name='seminar_nahraj_reseni'), | ||||||
|  | 	path('resitel/odevzdana_reseni/', resitel_or_org_required(views.PrehledOdevzdanychReseni.as_view()), name='seminar_resitel_odevzdana_reseni'), | ||||||
|  | 
 | ||||||
|  | 	path('org/reseni/', org_required(views.TabulkaOdevzdanychReseniView.as_view()), name='odevzdavatko_tabulka'), | ||||||
|  | 	path('org/reseni/rocnik/<int:rocnik>/', org_required(views.TabulkaOdevzdanychReseniView.as_view()), name='odevzdavatko_tabulka'), | ||||||
|  | 	path('org/reseni/<int:problem>/<int:resitel>/', org_required(views.ReseniProblemuView.as_view()), name='odevzdavatko_reseni_resitele_k_problemu'), | ||||||
|  | 	path('org/reseni/<int:pk>', org_required(viewMethodSwitch(get=views.DetailReseniView.as_view(), post=views.hodnoceniReseniView)), name='odevzdavatko_detail_reseni'), | ||||||
|  | 	path('org/reseni/all', org_required(views.SeznamReseniView.as_view())), | ||||||
|  | 	path('org/reseni/akt', org_required(views.SeznamAktualnichReseniView.as_view())), | ||||||
|  | 
 | ||||||
|  | 	path('resitel/reseni/<int:pk>', resitel_or_org_required(views.ResitelReseniView.as_view()), name='odevzdavatko_resitel_reseni'), | ||||||
|  | ] | ||||||
|  | @ -1,9 +1,12 @@ | ||||||
| from django.core.exceptions import PermissionDenied | from django.core.exceptions import PermissionDenied | ||||||
| from django.views.generic import ListView, DetailView, FormView | from django.views.generic import ListView, DetailView, FormView | ||||||
|  | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
|  | from django.core.mail import send_mail | ||||||
|  | from django.utils import timezone | ||||||
|  | from django.views.generic import ListView, DetailView, FormView, CreateView | ||||||
| from django.views.generic.list import MultipleObjectTemplateResponseMixin,MultipleObjectMixin | from django.views.generic.list import MultipleObjectTemplateResponseMixin,MultipleObjectMixin | ||||||
| from django.views.generic.base import View | from django.views.generic.base import View | ||||||
| from django.views.generic.detail import SingleObjectMixin | from django.shortcuts import redirect, get_object_or_404, render | ||||||
| from django.shortcuts import redirect, get_object_or_404 |  | ||||||
| from django.urls import reverse | from django.urls import reverse | ||||||
| from django.db import transaction | from django.db import transaction | ||||||
| from django.db.models import Q | from django.db.models import Q | ||||||
|  | @ -14,9 +17,10 @@ from itertools import groupby | ||||||
| import logging | import logging | ||||||
| 
 | 
 | ||||||
| import seminar.models as m | import seminar.models as m | ||||||
| import seminar.forms as f | from . import forms as f | ||||||
| from seminar.forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm | from .forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm | ||||||
| from seminar.utils import aktivniResitele, resi_v_rocniku, deadline | from seminar.utils import resi_v_rocniku, deadline | ||||||
|  | from seminar.views import formularOKView | ||||||
| 
 | 
 | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
| 
 | 
 | ||||||
|  | @ -42,7 +46,7 @@ class SouhrnReseni: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TabulkaOdevzdanychReseniView(ListView): | class TabulkaOdevzdanychReseniView(ListView): | ||||||
| 	template_name = 'seminar/odevzdavatko/tabulka.html' | 	template_name = 'odevzdavatko/tabulka.html' | ||||||
| 	model = m.Hodnoceni | 	model = m.Hodnoceni | ||||||
| 
 | 
 | ||||||
| 	def inicializuj_osy_tabulky(self): | 	def inicializuj_osy_tabulky(self): | ||||||
|  | @ -166,7 +170,7 @@ class TabulkaOdevzdanychReseniView(ListView): | ||||||
| # Velmi silně inspirováno zdrojáky, FIXME: Nedá se to udělat smysluplněji? | # Velmi silně inspirováno zdrojáky, FIXME: Nedá se to udělat smysluplněji? | ||||||
| class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixin, View): | class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixin, View): | ||||||
| 	model = m.Reseni | 	model = m.Reseni | ||||||
| 	template_name = 'seminar/odevzdavatko/seznam.html' | 	template_name = 'odevzdavatko/seznam.html' | ||||||
| 	 | 	 | ||||||
| 	def get_queryset(self): | 	def get_queryset(self): | ||||||
| 		qs = super().get_queryset() | 		qs = super().get_queryset() | ||||||
|  | @ -208,7 +212,7 @@ class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixi | ||||||
| ## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex | ## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex | ||||||
| class DetailReseniView(DetailView): | class DetailReseniView(DetailView): | ||||||
| 	model = m.Reseni | 	model = m.Reseni | ||||||
| 	template_name = 'seminar/odevzdavatko/detail.html' | 	template_name = 'odevzdavatko/detail.html' | ||||||
| 	 | 	 | ||||||
| 	def aktualni_hodnoceni(self): | 	def aktualni_hodnoceni(self): | ||||||
| 		self.reseni = get_object_or_404(m.Reseni, id=self.kwargs['pk']) | 		self.reseni = get_object_or_404(m.Reseni, id=self.kwargs['pk']) | ||||||
|  | @ -232,7 +236,7 @@ class DetailReseniView(DetailView): | ||||||
| 
 | 
 | ||||||
| def hodnoceniReseniView(request, pk, *args, **kwargs): | def hodnoceniReseniView(request, pk, *args, **kwargs): | ||||||
| 	reseni = get_object_or_404(m.Reseni, pk=pk) | 	reseni = get_object_or_404(m.Reseni, pk=pk) | ||||||
| 	template_name = 'seminar/odevzdavatko/detail.html' | 	template_name = 'odevzdavatko/detail.html' | ||||||
| 	form_class = f.OhodnoceniReseniFormSet | 	form_class = f.OhodnoceniReseniFormSet | ||||||
| 	success_url = reverse('odevzdavatko_detail_reseni', kwargs={'pk': pk}) | 	success_url = reverse('odevzdavatko_detail_reseni', kwargs={'pk': pk}) | ||||||
| 
 | 
 | ||||||
|  | @ -271,7 +275,7 @@ def hodnoceniReseniView(request, pk, *args, **kwargs): | ||||||
| 
 | 
 | ||||||
| class ResitelReseniView(DetailView): | class ResitelReseniView(DetailView): | ||||||
| 	model = m.Reseni | 	model = m.Reseni | ||||||
| 	template_name = 'seminar/odevzdavatko/detail_resitele.html' | 	template_name = 'odevzdavatko/detail_resitele.html' | ||||||
| 
 | 
 | ||||||
| 	def aktualni_hodnoceni(self): | 	def aktualni_hodnoceni(self): | ||||||
| 		self.reseni = get_object_or_404(m.Reseni, id=self.kwargs['pk']) | 		self.reseni = get_object_or_404(m.Reseni, id=self.kwargs['pk']) | ||||||
|  | @ -299,7 +303,7 @@ class ResitelReseniView(DetailView): | ||||||
| 
 | 
 | ||||||
| class PrehledOdevzdanychReseni(ListView): | class PrehledOdevzdanychReseni(ListView): | ||||||
| 	model = m.Hodnoceni | 	model = m.Hodnoceni | ||||||
| 	template_name = 'seminar/odevzdavatko/resitel_prehled.html' | 	template_name = 'odevzdavatko/prehled_reseni.html' | ||||||
| 
 | 
 | ||||||
| 	def get_queryset(self): | 	def get_queryset(self): | ||||||
| 		if not self.request.user.is_authenticated: | 		if not self.request.user.is_authenticated: | ||||||
|  | @ -325,7 +329,7 @@ class PrehledOdevzdanychReseni(ListView): | ||||||
| 
 | 
 | ||||||
| class SeznamReseniView(ListView): | class SeznamReseniView(ListView): | ||||||
| 	model = m.Reseni | 	model = m.Reseni | ||||||
| 	template_name = 'seminar/odevzdavatko/seznam.html' | 	template_name = 'odevzdavatko/seznam.html' | ||||||
| 
 | 
 | ||||||
| class SeznamAktualnichReseniView(SeznamReseniView): | class SeznamAktualnichReseniView(SeznamReseniView): | ||||||
| 	def get_queryset(self): | 	def get_queryset(self): | ||||||
|  | @ -334,3 +338,94 @@ class SeznamAktualnichReseniView(SeznamReseniView): | ||||||
| 		resitele = resi_v_rocniku(akt_rocnik) | 		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 | 		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 | 		return qs | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class PosliReseniView(LoginRequiredMixin, FormView): | ||||||
|  | 	template_name = 'odevzdavatko/posli_reseni.html' | ||||||
|  | 	form_class = f.PosliReseniForm | ||||||
|  | 
 | ||||||
|  | 	def form_valid(self, form): | ||||||
|  | 		data = form.cleaned_data | ||||||
|  | 		nove_reseni = m.Reseni.objects.create( | ||||||
|  | 			cas_doruceni=data['cas_doruceni'], | ||||||
|  | 			forma=data['forma'], | ||||||
|  | 			poznamka=data['poznamka'], | ||||||
|  | 		) | ||||||
|  | 		nove_reseni.resitele.add(data['resitel']) | ||||||
|  | 		nove_reseni.problem.add(data['problem']) | ||||||
|  | 		nove_reseni.save() | ||||||
|  | 		# Chtěl jsem, aby bylo vidět, že se to uložilo, tak přesměrovávám na profil. | ||||||
|  | 		return redirect(reverse('profil')) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class NahrajReseniView(LoginRequiredMixin, CreateView): | ||||||
|  | 	model = m.Reseni | ||||||
|  | 	template_name = 'odevzdavatko/nahraj_reseni.html' | ||||||
|  | 	form_class = f.NahrajReseniForm | ||||||
|  | 
 | ||||||
|  | 	def get(self, request, *args, **kwargs): | ||||||
|  | 		# Zaříznutí starých řešitelů: | ||||||
|  | 		# FIXME: Je to tady dost naprasené, mělo by to asi být jinde… | ||||||
|  | 		osoba = m.Osoba.objects.get(user=self.request.user) | ||||||
|  | 		resitel = osoba.resitel | ||||||
|  | 		if resitel.rok_maturity <= m.Nastaveni.get_solo().aktualni_rocnik.prvni_rok: | ||||||
|  | 			return render(request, 'universal.html', { | ||||||
|  | 				'title': 'Nelze odevzdat', | ||||||
|  | 				'error': 'Zdá se, že jsi již odmaturoval/a, a tedy nemůžeš odevzdat do našeho semináře řešení.', | ||||||
|  | 				'text': 'Pokud se ti zdá, že to je chyba, napiš nám prosím e-mail. Díky.', | ||||||
|  | 			}) | ||||||
|  | 		return super().get(request, *args, **kwargs) | ||||||
|  | 
 | ||||||
|  | 	def get_context_data(self,**kwargs): | ||||||
|  | 		data = super().get_context_data(**kwargs) | ||||||
|  | 		if self.request.POST: | ||||||
|  | 			data['prilohy'] = f.ReseniSPrilohamiFormSet(self.request.POST,self.request.FILES) | ||||||
|  | 		else: | ||||||
|  | 			data['prilohy'] = f.ReseniSPrilohamiFormSet() | ||||||
|  | 		return data | ||||||
|  | 
 | ||||||
|  | 	# FIXME prepsat tak, aby form_valid se volalo jen tehdy, kdyz je form i formset validni | ||||||
|  | 	# Inspirace: https://stackoverflow.com/questions/41599809/using-a-django-filefield-in-an-inline-formset | ||||||
|  | 	def form_valid(self,form): | ||||||
|  | 		context = self.get_context_data() | ||||||
|  | 		prilohy = context['prilohy'] | ||||||
|  | 		if not prilohy.is_valid(): | ||||||
|  | 			return super().form_invalid(form) | ||||||
|  | 		with transaction.atomic(): | ||||||
|  | 			self.object = form.save() | ||||||
|  | 			self.object.resitele.add(m.Resitel.objects.get(osoba__user = self.request.user)) | ||||||
|  | 			self.object.cas_doruceni = timezone.now() | ||||||
|  | 			self.object.forma = m.Reseni.FORMA_UPLOAD | ||||||
|  | 			self.object.save() | ||||||
|  | 
 | ||||||
|  | 			prilohy.instance = self.object | ||||||
|  | 			prilohy.save() | ||||||
|  | 
 | ||||||
|  | 		# Pošleme mail opravovatelům a garantovi | ||||||
|  | 		# FIXME: Nechat spočítat databázi? Je to pár dotazů (pravděpodobně), takže to za to možná nestojí | ||||||
|  | 		prijemci = set() | ||||||
|  | 		problemy = [] | ||||||
|  | 		for prob in form.cleaned_data['problem']: | ||||||
|  | 			prijemci.update(prob.opravovatele.all()) | ||||||
|  | 			if prob.garant is not None: | ||||||
|  | 				prijemci.add(prob.garant) | ||||||
|  | 			problemy.append(prob) | ||||||
|  | 		# FIXME: Možná poslat mail i relevantním orgům nadproblémů? | ||||||
|  | 		if len(prijemci) < 1: | ||||||
|  | 			logger.warning(f"Pozor, neposílám e-mail nikomu. Problémy: {problemy}") | ||||||
|  | 		# FIXME: Víc informativní obsah mailů, možná vč. příloh? | ||||||
|  | 		prijemci = map(lambda it: it.osoba.email, prijemci) | ||||||
|  | 
 | ||||||
|  | 		resitel = m.Osoba.objects.get(user = self.request.user) | ||||||
|  | 
 | ||||||
|  | 		seznam = "problému " + str(problemy[0]) if len(problemy) == 1 else 'následujícím problémům:\n' + ', \n'.join(map(str, problemy)) | ||||||
|  | 		seznam_do_subjectu = "problému " + str(problemy[0]) + ("" if len(problemy) == 1 else f" (a dalším { len(problemy) - 1 })") | ||||||
|  | 
 | ||||||
|  | 		send_mail( | ||||||
|  | 			subject="Nové řešení k " + seznam_do_subjectu, | ||||||
|  | 			message=f"Řešitel{ '' if resitel.pohlavi_muz else 'ka' } { resitel } právě nahrál{'' if resitel.pohlavi_muz else 'a' } nové řešení k { seznam }.\n\nHurá do opravování: { self.object.absolute_url() }", | ||||||
|  | 			from_email="submitovatko@mam.mff.cuni.cz", # FIXME: Chceme to mít radši tady, nebo v nastavení? | ||||||
|  | 			recipient_list=list(prijemci), | ||||||
|  | 		) | ||||||
|  | 
 | ||||||
|  | 		return formularOKView(self.request, text='Řešení úspěšně odevzdáno') | ||||||
							
								
								
									
										4
									
								
								personalni/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								personalni/__init__.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | ||||||
|  | """ | ||||||
|  | Obsahuje vše okolo registrace a osobních údajů (ne přihlášení a změnu hesla). | ||||||
|  | Také obsahuje rozcestníky a Řešitele s Organizátorem. | ||||||
|  | """ | ||||||
							
								
								
									
										50
									
								
								personalni/admin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								personalni/admin.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,50 @@ | ||||||
|  | from django.contrib import admin | ||||||
|  | from django.contrib.auth.models import Group | ||||||
|  | from django_reverse_admin import ReverseModelAdmin | ||||||
|  | import seminar.models as m | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @admin.register(m.Osoba) | ||||||
|  | class OsobaAdmin(admin.ModelAdmin): | ||||||
|  | 	actions = ['synchronizuj_maily', 'udelej_orgem'] | ||||||
|  | 
 | ||||||
|  | 	def synchronizuj_maily(self, request, queryset): | ||||||
|  | 		for o in queryset: | ||||||
|  | 			if o.user is not None: | ||||||
|  | 				u = o.user | ||||||
|  | 				u.email = o.email | ||||||
|  | 				u.save() | ||||||
|  | 		self.message_user(request, "E-maily synchronizovány.") | ||||||
|  | 	synchronizuj_maily.short_description = "Synchronizuj vybraným osobám e-maily do uživatelů" | ||||||
|  | 
 | ||||||
|  | 	def udelej_orgem(self,request,queryset): | ||||||
|  | 		org_group = Group.objects.get(name='org') | ||||||
|  | 		print(queryset) | ||||||
|  | 		for o in queryset: | ||||||
|  | 			user = o.user | ||||||
|  | 			print(user) | ||||||
|  | 			user.groups.add(org_group) | ||||||
|  | 			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" | ||||||
|  | 
 | ||||||
|  | class OsobaInline(admin.TabularInline): | ||||||
|  | 	model = m.Osoba | ||||||
|  | 
 | ||||||
|  | @admin.register(m.Organizator) | ||||||
|  | class OrganizatorAdmin(ReverseModelAdmin): | ||||||
|  | 	search_fields = ['osoba__jmeno', 'osoba__prijmeni', 'osoba__prezdivka'] | ||||||
|  | 	inline_type = 'stacked' | ||||||
|  | 	inline_reverse = ['osoba'] | ||||||
|  | 
 | ||||||
|  | @admin.register(m.Resitel) | ||||||
|  | class ResitelAdmin(ReverseModelAdmin): | ||||||
|  | 	search_fields = ['osoba__jmeno', 'osoba__prijmeni', 'osoba__prezdivka'] | ||||||
|  | 	ordering = ('osoba__jmeno','osoba__prijmeni') | ||||||
|  | 	inline_type = 'stacked' | ||||||
|  | 	inline_reverse = ['osoba'] | ||||||
|  | 
 | ||||||
|  | admin.site.register(m.Skola) | ||||||
|  | admin.site.register(m.Prijemce) | ||||||
							
								
								
									
										5
									
								
								personalni/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								personalni/apps.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | from django.apps import AppConfig | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class PersonalniConfig(AppConfig): | ||||||
|  |     name = 'personalni' | ||||||
|  | @ -3,11 +3,8 @@ from dal import autocomplete | ||||||
| from django.contrib.auth.forms import PasswordResetForm | from django.contrib.auth.forms import PasswordResetForm | ||||||
| from django.core.exceptions import ObjectDoesNotExist | from django.core.exceptions import ObjectDoesNotExist | ||||||
| from django.contrib.auth.models import User | from django.contrib.auth.models import User | ||||||
| from django.forms import formset_factory |  | ||||||
| from django.forms.models import inlineformset_factory |  | ||||||
| 
 | 
 | ||||||
| from .models import Skola, Resitel, Osoba, Problem | from seminar.models import Skola, Resitel, Osoba | ||||||
| import seminar.models as m |  | ||||||
| 
 | 
 | ||||||
| from datetime import date | from datetime import date | ||||||
| import logging | import logging | ||||||
|  | @ -217,210 +214,8 @@ class ProfileEditForm(forms.Form): | ||||||
| 	#		elif data.get('skola_adresa')=='': | 	#		elif data.get('skola_adresa')=='': | ||||||
| 	#			self.add_error('skola_adresa',forms.ValidationError('Je nutné vyplnit adresu školy')) | 	#			self.add_error('skola_adresa',forms.ValidationError('Je nutné vyplnit adresu školy')) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class PoMaturiteProfileEditForm(ProfileEditForm): | class PoMaturiteProfileEditForm(ProfileEditForm): | ||||||
| 	rok_maturity = forms.IntegerField( | 	rok_maturity = forms.IntegerField( | ||||||
| 		label='Rok maturity', | 		label='Rok maturity', | ||||||
| 		required=True) | 		required=True) | ||||||
| 
 |  | ||||||
| class VlozReseniForm(forms.Form): |  | ||||||
| 	#FIXME jen podproblémy daného problému |  | ||||||
| 	problem = forms.ModelChoiceField(label='Problém',queryset=m.Problem.objects.all()) |  | ||||||
| 	# to_field_name |  | ||||||
| 	#problem = models.ManyToManyField(Problem, verbose_name='problém', help_text='Problém', |  | ||||||
| 	#	through='Hodnoceni') |  | ||||||
| 
 |  | ||||||
| 	# FIXME pridat vice resitelu |  | ||||||
| 	resitel = forms.ModelChoiceField(label="Řešitel", |  | ||||||
| 		queryset=Resitel.objects.all(), |  | ||||||
| 		widget=autocomplete.ModelSelect2( |  | ||||||
| 			url='autocomplete_resitel', |  | ||||||
| 			attrs = {'data-placeholder--id': '-1', |  | ||||||
| 				'data-placeholder--text' : '---', |  | ||||||
| 				'data-allow-clear': 'true'}) |  | ||||||
|     		) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 	#resitele = models.ManyToManyField(Resitel, verbose_name='autoři řešení', |  | ||||||
| 	#	help_text='Seznam autorů řešení', through='Reseni_Resitele') |  | ||||||
| 	 |  | ||||||
| 	cas_doruceni = forms.DateField(widget=DateInput(),label="Čas doručení") |  | ||||||
| 
 |  | ||||||
| 	#cas_doruceni = models.DateTimeField('čas_doručení', default=timezone.now, blank=True) |  | ||||||
| 
 |  | ||||||
| 	forma = forms.ChoiceField(label="Forma řešení",choices = m.Reseni.FORMA_CHOICES) |  | ||||||
| 	#forma = models.CharField('forma řešení', max_length=16, choices=FORMA_CHOICES, blank=False, |  | ||||||
| 	#	 default=FORMA_EMAIL) |  | ||||||
| 
 |  | ||||||
| 	poznamka = forms.CharField(label='Neveřejná poznámka', required=False) |  | ||||||
| 	#poznamka = models.TextField('neveřejná poznámka', blank=True, |  | ||||||
| 	#	help_text='Neveřejná poznámka k řešení (plain text)') |  | ||||||
| 
 |  | ||||||
| 	#TODO body do cisla |  | ||||||
| 	#TODO prilohy |  | ||||||
| 
 |  | ||||||
| 	##def __init__(self, *args, **kwargs): |  | ||||||
| 	##	super().__init__(*args, **kwargs) |  | ||||||
| 	##	#self.fields['favorite_color'] = forms.ChoiceField(choices=[(color.id, color.name) for color in Resitel.objects.all()]) |  | ||||||
| 
 |  | ||||||
| class NahrajReseniForm(forms.ModelForm): |  | ||||||
| 	class Meta: |  | ||||||
| 		model = m.Reseni |  | ||||||
| 		fields = ('problem',) |  | ||||||
| 		help_texts = {'problem':''} # Nezobrazovat help text ve formuláři |  | ||||||
| 		 |  | ||||||
| 		widgets = {'problem': |  | ||||||
| 				autocomplete.ModelSelect2Multiple( |  | ||||||
| 					url='autocomplete_problem_odevzdatelny', |  | ||||||
| 					attrs = {'data-placeholder--id': '-1', |  | ||||||
| 						'data-placeholder--text' : '---', |  | ||||||
| 						'data-allow-clear': 'true'}, |  | ||||||
| 				) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| ReseniSPrilohamiFormSet = inlineformset_factory(m.Reseni,m.PrilohaReseni,  |  | ||||||
| 		form = NahrajReseniForm, |  | ||||||
| 		fields = ('soubor','res_poznamka'), |  | ||||||
| 		widgets = {'res_poznamka':forms.TextInput()}, |  | ||||||
| 		extra = 1, |  | ||||||
| 		can_delete = False, |  | ||||||
| 
 |  | ||||||
| 		) |  | ||||||
| 
 |  | ||||||
| class NahrajObrazekKTreeNoduForm(forms.ModelForm): |  | ||||||
| 	class Meta: |  | ||||||
| 		model = m.Obrazek |  | ||||||
| 		fields = ('na_web',) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class JednoHodnoceniForm(forms.ModelForm): |  | ||||||
| 	class Meta: |  | ||||||
| 		model = m.Hodnoceni |  | ||||||
| 		fields = ('problem', 'body', 'cislo_body') |  | ||||||
| 		widgets = { |  | ||||||
| 			'problem': autocomplete.ModelSelect2( |  | ||||||
| 				url='autocomplete_problem_odevzdatelny',   # FIXME: Dovolit i starší? |  | ||||||
| 				) |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| OhodnoceniReseniFormSet = formset_factory(JednoHodnoceniForm, |  | ||||||
| 		extra = 0, |  | ||||||
| 		) |  | ||||||
| 
 |  | ||||||
| class PoznamkaReseniForm(forms.ModelForm): |  | ||||||
| 	class Meta: |  | ||||||
| 		model = m.Reseni |  | ||||||
| 		fields = ('poznamka',) |  | ||||||
| 
 |  | ||||||
| # FIXME: Ideálně by mělo být součástí třídy níž, ale neumím to udělat |  | ||||||
| DATE_FORMAT = '%Y-%m-%d' |  | ||||||
| 
 |  | ||||||
| class OdevzdavatkoTabulkaFiltrForm(forms.Form): |  | ||||||
| 	"""Form pro filtrování přehledové odevzdávátkové tabulky |  | ||||||
| 
 |  | ||||||
| 	Inspirováno https://kam.mff.cuni.cz/mffzoom/""" |  | ||||||
| 
 |  | ||||||
| 	# Věci definované níž se importují i ve views pro odevzdávátko (Inspirováno https://docs.djangoproject.com/en/3.1/ref/models/fields/#field-choices) |  | ||||||
| 
 |  | ||||||
| 	RESITELE_RELEVANTNI = 'relevantni' |  | ||||||
| 	RESITELE_NEODMATUROVAVSI = 'neodmaturovavsi' |  | ||||||
| 	RESITELE_CHOICES = [ |  | ||||||
| 		(RESITELE_RELEVANTNI, 'Relevantní řešitelé'), # I.e. nezobrazovat prázdné řádky tabulky |  | ||||||
| 		(RESITELE_NEODMATUROVAVSI, 'Všichni bez maturity'), |  | ||||||
| 		# Možná: všechny vč. historických? |  | ||||||
| 		] |  | ||||||
| 
 |  | ||||||
| 	PROBLEMY_MOJE = 'moje' |  | ||||||
| 	PROBLEMY_LETOSNI = 'letosni' |  | ||||||
| 	PROBLEMY_CHOICES = [ |  | ||||||
| 		(PROBLEMY_MOJE, 'Moje problémy'), # Letošní problémy, které mají v sobě nebo v nadproblémech přiřazeného daného orga |  | ||||||
| 		(PROBLEMY_LETOSNI, 'Všechny letošní'), |  | ||||||
| 		# TODO: *hlavní problémy, možná všechny... |  | ||||||
| 		# XXX: Chtělo by to i "aktuálně zadané... |  | ||||||
| 		] |  | ||||||
| 
 |  | ||||||
| 	# TODO: Typy problémů (problémy, úlohy, ostatní, všechny)? Jen některá řešení (obodovaná/neobodovaná, víc řešitelů, ...)? |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 	@classmethod |  | ||||||
| 	def gen_terminy(cls, rocnik=None): |  | ||||||
| 		import datetime |  | ||||||
| 		from time import strftime |  | ||||||
| 		 |  | ||||||
| 		from django.db.utils import OperationalError |  | ||||||
| 		try: |  | ||||||
| 			aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik |  | ||||||
| 			aktualni_cislo = m.Nastaveni.get_solo().aktualni_cislo |  | ||||||
| 		except OperationalError: |  | ||||||
| 			# django.db.utils.OperationalError: no such table: seminar_nastaveni |  | ||||||
| 			# Nemáme databázi, takže to selhalo. Pro jistotu vrátíme aspoň dvě možnosti, ať to nepadá dál |  | ||||||
| 			logger = logging.getLogger(__name__) |  | ||||||
| 			logger.error("Rozbitá databáze (před počátečními migracemi?)") |  | ||||||
| 			return [('broken', 'Je to rozbitý'), ('fubar', 'Nefunguje to')] |  | ||||||
| 
 |  | ||||||
| 		# FIXME: Tohle je hnusný monkey patch, mělo by to být nějak zahrnuto výš. |  | ||||||
| 		if rocnik is not None: |  | ||||||
| 			aktualni_rocnik = rocnik |  | ||||||
| 			aktualni_cislo = m.Cislo.objects.filter(rocnik=rocnik).order_by('poradi').last() |  | ||||||
| 
 |  | ||||||
| 		result = [] |  | ||||||
| 
 |  | ||||||
| 		for cislo in m.Cislo.objects.filter( |  | ||||||
| 				rocnik=aktualni_rocnik, |  | ||||||
| 				poradi__lte=aktualni_cislo.poradi, |  | ||||||
| 				).reverse():	# Standardně se řadí od nejnovějšího čísla |  | ||||||
| 			# Předem je mi líto kohokoliv, kdo tyhle řádky bude číst... |  | ||||||
| 			if cislo.datum_vydani is not None and cislo.datum_vydani <= datetime.date.today(): |  | ||||||
| 				result.append(( |  | ||||||
| 					strftime(DATE_FORMAT, cislo.datum_vydani.timetuple()), |  | ||||||
| 					f"Vydání {cislo.poradi}. čísla")) |  | ||||||
| 			if cislo.datum_preddeadline is not None and cislo.datum_preddeadline <= datetime.date.today(): |  | ||||||
| 				result.append(( |  | ||||||
| 					strftime(DATE_FORMAT, cislo.datum_preddeadline.timetuple()), |  | ||||||
| 					f"Předdeadline {cislo.poradi}. čísla")) |  | ||||||
| 			if cislo.datum_deadline_soustredeni is not None and cislo.datum_deadline_soustredeni <= datetime.date.today(): |  | ||||||
| 				result.append(( |  | ||||||
| 					strftime(DATE_FORMAT, cislo.datum_deadline_soustredeni.timetuple()), |  | ||||||
| 					f"Sous. deadline {cislo.poradi}. čísla")) |  | ||||||
| 			if cislo.datum_deadline is not None and cislo.datum_deadline <= datetime.date.today(): |  | ||||||
| 				result.append(( |  | ||||||
| 					strftime(DATE_FORMAT, cislo.datum_deadline.timetuple()), |  | ||||||
| 					f"Finální deadline {cislo.poradi}. čísla")) |  | ||||||
| 		result.append(( |  | ||||||
| 			strftime(DATE_FORMAT, datetime.date.today().timetuple()), f"Dnes")) |  | ||||||
| 
 |  | ||||||
| 		return result |  | ||||||
| 
 |  | ||||||
| 	@classmethod |  | ||||||
| 	def gen_initial(cls, rocnik=None): |  | ||||||
| 		terminy = cls.gen_terminy(rocnik) |  | ||||||
| 		initial = { |  | ||||||
| 			'resitele': cls.RESITELE_RELEVANTNI, |  | ||||||
| 			'problemy': cls.PROBLEMY_MOJE, |  | ||||||
| 			# Pokud chceme neaktuální ročník, tak nás nejspíš zajímají všechna řešení… |  | ||||||
| 			'reseni_od': terminy[-2] if rocnik is None else terminy[0], |  | ||||||
| 			'reseni_do': terminy[-1], |  | ||||||
| 			'neobodovane': False, |  | ||||||
| 		} |  | ||||||
| 		return initial |  | ||||||
| 
 |  | ||||||
| 	def __init__(self, *args, rocnik=None, **kwargs): |  | ||||||
| 		if 'initial' not in kwargs: |  | ||||||
| 			super().__init__(initial=self.gen_initial(rocnik), *args, **kwargs) |  | ||||||
| 		else: |  | ||||||
| 			super().__init__(*args, **kwargs) |  | ||||||
| 		# choices jako parametr Select widgetu neumí brát callable, jen iterable, takže si pro jednoduchost můžu rovnou uložit výsledek sem... |  | ||||||
| 		# A "sem" znamená do libovolné metody, protože jinak se jedná o kód, který django spustí při inicializaci a protože potřebujeme databázi, tak by spadnul při vyrábění testdat... |  | ||||||
| 		self.terminy = self.gen_terminy(rocnik) |  | ||||||
| 		self.fields['reseni_od'].widget = forms.Select(choices=self.gen_terminy(rocnik)) |  | ||||||
| 		# Pokud chceme neaktuální ročník, tak nás nejspíš zajímají všechna řešení… |  | ||||||
| 		self.fields['reseni_od'].initial = self.terminy[-2] if rocnik is None else self.terminy[0] |  | ||||||
| 		self.fields['reseni_do'].widget = forms.Select(choices=self.gen_terminy(rocnik)) |  | ||||||
| 		self.fields['reseni_do'].initial = self.terminy[-1] |  | ||||||
| 
 |  | ||||||
| 	# NOTE: Initial definuji pro jednotlivé fieldy, aby to bylo tady a nebylo potřeba to řešit ve views... |  | ||||||
| 	resitele = forms.ChoiceField(choices=RESITELE_CHOICES) |  | ||||||
| 	problemy = forms.ChoiceField(choices=PROBLEMY_CHOICES) |  | ||||||
| 	 |  | ||||||
| 	reseni_od = forms.DateField(input_formats=[DATE_FORMAT]) |  | ||||||
| 	reseni_do = forms.DateField(input_formats=[DATE_FORMAT]) |  | ||||||
| 	neobodovane = forms.BooleanField(required=False) |  | ||||||
							
								
								
									
										0
									
								
								personalni/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								personalni/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										105
									
								
								personalni/templates/personalni/udaje/edit.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								personalni/templates/personalni/udaje/edit.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,105 @@ | ||||||
|  | {% extends "base.html" %} | ||||||
|  | {% load staticfiles %} | ||||||
|  | 
 | ||||||
|  | {% block script %} | ||||||
|  |     <script src="{% static 'personalni/prihlaska.js' %}"></script> | ||||||
|  | {% endblock %} | ||||||
|  | 
 | ||||||
|  | <!-- | ||||||
|  | 
 | ||||||
|  | # pro přidání políčka do formuláře je potřeba | ||||||
|  | # - mít v modelu tu položku, kterou chci upravovat | ||||||
|  | # - přidat do views (prihlaskaView, resitelEditView) | ||||||
|  | # - přidat do forms | ||||||
|  | # - includovat do html | ||||||
|  | 
 | ||||||
|  | --> | ||||||
|  | 
 | ||||||
|  | {% block content %} | ||||||
|  | <h1> | ||||||
|  |   {% block nadpis1a %}{% block nadpis1b %} | ||||||
|  |    Změna osobních údajů | ||||||
|  |   {% endblock %}{% endblock %} | ||||||
|  | </h1> | ||||||
|  | <form action="{% url 'seminar_resitel_edit' %}" method="post"> | ||||||
|  |  {% csrf_token %} | ||||||
|  |  {{form.non_field_errors}} | ||||||
|  | 
 | ||||||
|  | <hr> | ||||||
|  | 
 | ||||||
|  |      <h4> | ||||||
|  |       Přihlašovací údaje | ||||||
|  |      </h4> | ||||||
|  |      <table class="form"> | ||||||
|  |        {% include "personalni/udaje/prihlaska_field.html" with field=form.username %} | ||||||
|  |      </table> | ||||||
|  | 
 | ||||||
|  | <hr> | ||||||
|  | 
 | ||||||
|  |     <h4> | ||||||
|  |      Osobní údaje | ||||||
|  |     </h4> | ||||||
|  |       <table class="form"> | ||||||
|  |        {% include "personalni/udaje/prihlaska_field.html" with field=form.jmeno %} | ||||||
|  |        {% include "personalni/udaje/prihlaska_field.html" with field=form.prijmeni %} | ||||||
|  |        {% include "personalni/udaje/prihlaska_field.html" with field=form.pohlavi_muz%} | ||||||
|  |        {% include "personalni/udaje/prihlaska_field.html" with field=form.email %} | ||||||
|  |        {% include "personalni/udaje/prihlaska_field.html" with field=form.telefon %} | ||||||
|  |        {% include "personalni/udaje/prihlaska_field.html" with field=form.datum_narozeni %} | ||||||
|  |      </table> | ||||||
|  | 
 | ||||||
|  |   <hr> | ||||||
|  | 
 | ||||||
|  |     <h4> | ||||||
|  |       Bydliště | ||||||
|  |     </h4> | ||||||
|  |       <table class="form"> | ||||||
|  |        {% include "personalni/udaje/prihlaska_field.html" with field=form.ulice %} | ||||||
|  |        {% include "personalni/udaje/prihlaska_field.html" with field=form.mesto %} | ||||||
|  |        {% include "personalni/udaje/prihlaska_field.html" with field=form.psc %} | ||||||
|  |        {% include "personalni/udaje/prihlaska_field.html" with field=form.stat %} | ||||||
|  |        {% include "personalni/udaje/prihlaska_field.html" with field=form.stat_text id="id_li_stat_text"%} | ||||||
|  |      </table> | ||||||
|  | 
 | ||||||
|  |  <hr> | ||||||
|  | 
 | ||||||
|  |     <h4> | ||||||
|  |      Škola | ||||||
|  |     </h4> | ||||||
|  |      <table class="form"> | ||||||
|  |        {% include "personalni/udaje/prihlaska_field.html" with field=form.skola %} | ||||||
|  |        <tr><td colspan="2" ><button id="id_skola_text_button" type="button">Škola není v seznamu</button></td></tr> | ||||||
|  |        <tr><td id="id_li_skola_vypln" colspan="2">Vyplň prosím celý název a adresu školy.</td></tr> | ||||||
|  |        {% include "personalni/udaje/prihlaska_field.html" with field=form.skola_nazev id="id_li_skola_nazev" %} | ||||||
|  |        {% include "personalni/udaje/prihlaska_field.html" with field=form.skola_adresa id="id_li_skola_adresa" %} | ||||||
|  |        {% include "personalni/udaje/prihlaska_field.html" with field=form.rok_maturity %} | ||||||
|  |      </table> | ||||||
|  | 
 | ||||||
|  |  <hr> | ||||||
|  | 
 | ||||||
|  |     <h4> | ||||||
|  |      Pošta | ||||||
|  |     </h4> | ||||||
|  |      <table class="form"> | ||||||
|  |        {% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat %} | ||||||
|  |        {% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat_cislo_emailem %} | ||||||
|  |      </table> | ||||||
|  | 
 | ||||||
|  |  <hr> | ||||||
|  | 
 | ||||||
|  |     <h4> | ||||||
|  |      Zasílání propagačních materiálů | ||||||
|  |     </h4> | ||||||
|  |      <table class="form"> | ||||||
|  |        {% include "personalni/udaje/prihlaska_field.html" with field=form.spam %} | ||||||
|  |      </table> | ||||||
|  | 
 | ||||||
|  |  <hr> | ||||||
|  | 
 | ||||||
|  |     <input type="submit" value="Změnit"> | ||||||
|  | </form> | ||||||
|  | <script> | ||||||
|  | $("#id_stat").on("change",addrCountryChanged); | ||||||
|  | $("#id_skola_text_button").on("click",schoolNotInList); | ||||||
|  | </script> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										123
									
								
								personalni/templates/personalni/udaje/prihlaska.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								personalni/templates/personalni/udaje/prihlaska.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,123 @@ | ||||||
|  | {% extends "base.html" %} | ||||||
|  | {% load staticfiles %} | ||||||
|  | 
 | ||||||
|  | {% block script %} | ||||||
|  |     <script src="{% static 'personalni/prihlaska.js' %}"></script> | ||||||
|  | {% endblock %} | ||||||
|  | 
 | ||||||
|  | <!-- | ||||||
|  | 
 | ||||||
|  | # pro přidání políčka do formuláře je potřeba | ||||||
|  | # - mít v modelu tu položku, kterou chci upravovat | ||||||
|  | # - přidat do views (prihlaskaView, resitelEditView) | ||||||
|  | # - přidat do forms | ||||||
|  | # - includovat do html | ||||||
|  | 
 | ||||||
|  | --> | ||||||
|  | 
 | ||||||
|  | {% block content %} | ||||||
|  | <h1> | ||||||
|  |   {% block nadpis1a %}{% block nadpis1b %} | ||||||
|  |     Přihláška do semináře | ||||||
|  |   {% endblock %}{% endblock %} | ||||||
|  | </h1> | ||||||
|  | 
 | ||||||
|  | <p><b>Tučně</b> popsaná pole jsou povinná.</p> | ||||||
|  | 
 | ||||||
|  | <form action="{% url 'seminar_prihlaska' %}" method="post"> | ||||||
|  |   {% csrf_token %} | ||||||
|  |   {{form.non_field_errors}} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | <hr> | ||||||
|  |          <h4> | ||||||
|  |           Přihlašovací údaje | ||||||
|  |          </h4> | ||||||
|  |          <table class="form"> | ||||||
|  |            {% include "personalni/udaje/prihlaska_field.html" with field=form.username %} | ||||||
|  | {#           {% include "personalni/udaje/prihlaska_field.html" with field=form.password %}#} | ||||||
|  | {#           {% include "personalni/udaje/prihlaska_field.html" with field=form.password_check %}#} | ||||||
|  |          </table> | ||||||
|  | 
 | ||||||
|  | <hr> | ||||||
|  | 
 | ||||||
|  |         <h4> | ||||||
|  |          Osobní údaje | ||||||
|  |         </h4> | ||||||
|  |           <table class="form"> | ||||||
|  |             {% include "personalni/udaje/prihlaska_field.html" with field=form.jmeno %} | ||||||
|  |             {% include "personalni/udaje/prihlaska_field.html" with field=form.prijmeni %} | ||||||
|  |             {% include "personalni/udaje/prihlaska_field.html" with field=form.pohlavi_muz%} | ||||||
|  |             {% include "personalni/udaje/prihlaska_field.html" with field=form.email %} | ||||||
|  |             {% include "personalni/udaje/prihlaska_field.html" with field=form.telefon %} | ||||||
|  |             {% include "personalni/udaje/prihlaska_field.html" with field=form.datum_narozeni %} | ||||||
|  |          </table> | ||||||
|  | 
 | ||||||
|  | <hr> | ||||||
|  | 
 | ||||||
|  |         <h4> | ||||||
|  |           Bydliště | ||||||
|  |         </h4> | ||||||
|  |           <table class="form"> | ||||||
|  |            {% include "personalni/udaje/prihlaska_field.html" with field=form.ulice %} | ||||||
|  |            {% include "personalni/udaje/prihlaska_field.html" with field=form.mesto %} | ||||||
|  |            {% include "personalni/udaje/prihlaska_field.html" with field=form.psc %} | ||||||
|  |            {% include "personalni/udaje/prihlaska_field.html" with field=form.stat %} | ||||||
|  |            {% include "personalni/udaje/prihlaska_field.html" with field=form.stat_text id="id_li_stat_text"%} | ||||||
|  |          </table> | ||||||
|  | 
 | ||||||
|  | <hr> | ||||||
|  | 
 | ||||||
|  |         <h4> | ||||||
|  |          Škola | ||||||
|  |         </h4> | ||||||
|  |          <table class="form"> | ||||||
|  |            {% include "personalni/udaje/prihlaska_field.html" with field=form.skola %} | ||||||
|  |            <tr><td colspan="2" ><button id="id_skola_text_button" type="button">Škola není v seznamu</button></td></tr> | ||||||
|  |            <tr><td id="id_li_skola_vypln" colspan="2">Vyplň prosím celý název a adresu školy.</td></tr> | ||||||
|  |            {% include "personalni/udaje/prihlaska_field.html" with field=form.skola_nazev id="id_li_skola_nazev" %} | ||||||
|  |            {% include "personalni/udaje/prihlaska_field.html" with field=form.skola_adresa id="id_li_skola_adresa" %} | ||||||
|  |            {% include "personalni/udaje/prihlaska_field.html" with field=form.rok_maturity %} | ||||||
|  |          </table> | ||||||
|  | 
 | ||||||
|  | <hr> | ||||||
|  | 
 | ||||||
|  |         <h4> | ||||||
|  |          Pošta | ||||||
|  |         </h4> | ||||||
|  |          <table class="form"> | ||||||
|  |            {% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat %} | ||||||
|  |            {% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat_cislo_emailem %} | ||||||
|  |          </table> | ||||||
|  |  <hr> | ||||||
|  | 
 | ||||||
|  |          <h4> | ||||||
|  |           GDPR | ||||||
|  |          </h4> | ||||||
|  |           {% include "personalni/udaje/gdpr.html" %} | ||||||
|  |           <table class="form"> | ||||||
|  |             {% include "personalni/udaje/prihlaska_field.html" with field=form.gdpr %} | ||||||
|  |           </table> | ||||||
|  | 
 | ||||||
|  | <hr> | ||||||
|  | 
 | ||||||
|  |         <h4> | ||||||
|  |          Zasílání propagačních materiálů | ||||||
|  |         </h4> | ||||||
|  |          <table class="form"> | ||||||
|  |            {% include "personalni/udaje/prihlaska_field.html" with field=form.spam %} | ||||||
|  |          </table> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | <hr> | ||||||
|  | 
 | ||||||
|  |     <input type="submit" value="Odeslat"> | ||||||
|  | </form> | ||||||
|  | <script> | ||||||
|  | $("#id_stat").on("change",addrCountryChanged); | ||||||
|  | $("#id_skola_text_button").on("click",schoolNotInList); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										24
									
								
								personalni/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								personalni/urls.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,24 @@ | ||||||
|  | from django.urls import path | ||||||
|  | from django.contrib.auth.decorators import login_required | ||||||
|  | from . import views | ||||||
|  | from seminar.utils import org_required | ||||||
|  | 
 | ||||||
|  | urlpatterns = [ | ||||||
|  | 	path( | ||||||
|  | 		'org/rozcestnik/', | ||||||
|  | 		org_required(views.OrgoRozcestnikView.as_view()), | ||||||
|  | 		name='seminar_org_rozcestnik' | ||||||
|  | 	), | ||||||
|  | 
 | ||||||
|  | 	path('prihlaska/', views.prihlaskaView, name='seminar_prihlaska'), | ||||||
|  | 
 | ||||||
|  | 	path( | ||||||
|  | 		'resitel/osobni-udaje/', | ||||||
|  | 		login_required(views.resitelEditView), | ||||||
|  | 		name='seminar_resitel_edit' | ||||||
|  | 	), | ||||||
|  | 
 | ||||||
|  | 	# Obecný view na profil -- orgům dá rozcestník, řešitelům jejich stránku | ||||||
|  | 	path('profil/', views.profilView, name='profil'), | ||||||
|  | 
 | ||||||
|  | ] | ||||||
							
								
								
									
										306
									
								
								personalni/views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										306
									
								
								personalni/views.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,306 @@ | ||||||
|  | from django.shortcuts import render | ||||||
|  | from django.urls import reverse | ||||||
|  | from django.views import generic | ||||||
|  | from django.db.models import Q | ||||||
|  | from django.views.decorators.debug import sensitive_post_parameters | ||||||
|  | from django.views.generic.base import TemplateView | ||||||
|  | from django.contrib.auth.models import User, Permission, Group | ||||||
|  | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
|  | from django.db import transaction | ||||||
|  | 
 | ||||||
|  | import seminar.models as s | ||||||
|  | import seminar.models as m | ||||||
|  | from .forms import PrihlaskaForm, ProfileEditForm, PoMaturiteProfileEditForm | ||||||
|  | 
 | ||||||
|  | from datetime import date | ||||||
|  | import logging | ||||||
|  | 
 | ||||||
|  | from seminar.views import formularOKView | ||||||
|  | from various.autentizace.views import LoginView | ||||||
|  | from various.autentizace.utils import posli_reset_hesla | ||||||
|  | 
 | ||||||
|  | from django.forms.models import model_to_dict | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class OrgoRozcestnikView(TemplateView): | ||||||
|  | 	""" Zobrazí organizátorský rozcestník.""" | ||||||
|  | 
 | ||||||
|  | 	template_name = 'personalni/profil/orgorozcestnik.html' | ||||||
|  | 
 | ||||||
|  | 	def get_context_data(self, **kwargs): | ||||||
|  | 		context = super().get_context_data(**kwargs) | ||||||
|  | 		context['posledni_soustredeni'] = s.Soustredeni.objects.order_by('-datum_konce').first() | ||||||
|  | 		nastaveni = s.Nastaveni.objects.first() | ||||||
|  | 		aktualni_rocnik = nastaveni.aktualni_rocnik | ||||||
|  | 		context['posledni_cislo_url'] = nastaveni.aktualni_cislo.verejne_url() | ||||||
|  | 		# TODO možná chceme odkazovat na právě rozpracované číslo, a ne to poslední vydané | ||||||
|  | 		# pokud nechceme haluzit kód (= poradi) dalšího čísla, bude asi potřeba jít | ||||||
|  | 		# přes treenody (a dát si přitom pozor na MezicisloNode) | ||||||
|  | 
 | ||||||
|  | 		neobodovana_reseni = s.Hodnoceni.objects.filter(body__isnull=True) | ||||||
|  | 		reseni_mimo_cislo = s.Hodnoceni.objects.filter(cislo_body__isnull=True) | ||||||
|  | 		context['pocet_neobodovanych_reseni'] = neobodovana_reseni.count() | ||||||
|  | 		context['pocet_reseni_mimo_cislo'] = reseni_mimo_cislo.count() | ||||||
|  | 
 | ||||||
|  | 		u = self.request.user | ||||||
|  | 		os = s.Osoba.objects.get(user=u) | ||||||
|  | 		organizator = s.Organizator.objects.get(osoba=os) | ||||||
|  | 
 | ||||||
|  | 		context['muj_pocet_neobodovanych_reseni'] = neobodovana_reseni.filter(Q(problem__garant=organizator) | Q(problem__autor=organizator) | Q(problem__opravovatele__in=[organizator])).distinct().count() | ||||||
|  | 		context['muj_pocet_reseni_mimo_cislo'] = reseni_mimo_cislo.filter(Q(problem__garant=organizator) | Q(problem__autor=organizator) | Q(problem__opravovatele__in=[organizator])).count() | ||||||
|  | 
 | ||||||
|  | 		#FIXME: přidat stav='STAV_ZADANY' | ||||||
|  | 		temata = s.Tema.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]), | ||||||
|  | 			rocnik=aktualni_rocnik).distinct() | ||||||
|  | 		ulohy = s.Uloha.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]), | ||||||
|  | 			cislo_zadani__rocnik=aktualni_rocnik).distinct() | ||||||
|  | 		clanky = s.Clanek.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]), | ||||||
|  | 			cislo__rocnik=aktualni_rocnik).distinct() | ||||||
|  | 
 | ||||||
|  | 		context['temata'] = temata | ||||||
|  | 		context['ulohy'] = ulohy | ||||||
|  | 		context['clanky'] = clanky | ||||||
|  | 		context['organizator'] = organizator | ||||||
|  | 		return context | ||||||
|  | 
 | ||||||
|  | 		#content_type = 'text/plain; charset=UTF8' | ||||||
|  | 	#XXX | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ResitelView(LoginRequiredMixin,generic.DetailView): | ||||||
|  | 	model = s.Resitel | ||||||
|  | 	template_name = 'personalni/profil/resitel.html' | ||||||
|  | 
 | ||||||
|  | 	def get_object(self, queryset=None): | ||||||
|  | 		print(self.request.user) | ||||||
|  | 		return s.Resitel.objects.get(osoba__user=self.request.user) | ||||||
|  | 
 | ||||||
|  | ### 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 | ||||||
|  | 
 | ||||||
|  | def prihlaska_log_gdpr_safe(logger, gdpr_logger, msg, form_data): | ||||||
|  | 	msg = "{}, form_hash:{}".format(msg,hash(frozenset(form_data.items))) | ||||||
|  | 	logger.warn(msg) | ||||||
|  | 	gdpr_logger.warn(msg+", form:{}".format(form_data)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @sensitive_post_parameters('jmeno', 'prijmeni', 'email', 'telefon', 'datum_narozeni', 'ulice', 'mesto', 'psc', 'skola') | ||||||
|  | def resitelEditView(request): | ||||||
|  | 	err_logger = logging.getLogger('seminar.prihlaska.problem') | ||||||
|  | 	## Načtení objektů Osoba a Resitel patřících k aktuálně přihlášenému uživateli | ||||||
|  | 	u = request.user | ||||||
|  | 	osoba_edit = s.Osoba.objects.get(user=u) | ||||||
|  | 	if hasattr(osoba_edit,'resitel'): | ||||||
|  | 		resitel_edit = osoba_edit.resitel | ||||||
|  | 	else: | ||||||
|  | 		resitel_edit = None | ||||||
|  | 	user_edit = osoba_edit.user | ||||||
|  | 	## Vytvoření slovníku, kterým předvyplním formulář  | ||||||
|  | 	prefill_1=model_to_dict(user_edit) | ||||||
|  | 	if resitel_edit: | ||||||
|  | 		prefill_2=model_to_dict(resitel_edit) | ||||||
|  | 		prefill_1.update(prefill_2) | ||||||
|  | 	prefill_3=model_to_dict(osoba_edit) | ||||||
|  | 	prefill_1.update(prefill_3) | ||||||
|  | 	if 'datum_narozeni' in prefill_1: | ||||||
|  | 		prefill_1['datum_narozeni'] = str(prefill_1['datum_narozeni']) | ||||||
|  | 	if 'rok_maturity' not in prefill_1 or prefill_1['rok_maturity'] < date.today().year: | ||||||
|  | 		form = PoMaturiteProfileEditForm(initial=prefill_1) | ||||||
|  | 	else: | ||||||
|  | 		form = ProfileEditForm(initial=prefill_1) | ||||||
|  | 	## Změna údajů a jejich uložení | ||||||
|  | 	if request.method == 'POST': | ||||||
|  | 		POST = request.POST.copy() | ||||||
|  | 		POST["username"] = osoba_edit.user.username | ||||||
|  | 
 | ||||||
|  | 		if 'rok_maturity' not in prefill_1 or prefill_1['rok_maturity'] < date.today().year: | ||||||
|  | 			form = PoMaturiteProfileEditForm(POST) | ||||||
|  | 		else: | ||||||
|  | 			form = ProfileEditForm(POST) | ||||||
|  | 		form.username = user_edit.username | ||||||
|  | 		if form.is_valid(): | ||||||
|  | 			## Změny v osobě | ||||||
|  | 			fcd = form.cleaned_data | ||||||
|  | 			form_hash = hash(frozenset(fcd.items())) | ||||||
|  | 			form_logger = logging.getLogger('seminar.prihlaska.form') | ||||||
|  | 			form_logger.info("EDIT:" + str(fcd) + str(form_hash))  # TODO možná logovat jinak | ||||||
|  | 			osoba_edit.jmeno = fcd['jmeno'] | ||||||
|  | 			osoba_edit.prijmeni = fcd['prijmeni'] | ||||||
|  | 			osoba_edit.pohlavi_muz = fcd['pohlavi_muz'] | ||||||
|  | 			osoba_edit.email = fcd['email'] | ||||||
|  | 			osoba_edit.telefon = fcd['telefon'] | ||||||
|  | 			osoba_edit.ulice = fcd['ulice'] | ||||||
|  | 			osoba_edit.mesto = fcd['mesto'] | ||||||
|  | 			osoba_edit.psc = fcd['psc'] | ||||||
|  | 			osoba_edit.datum_narozeni = fcd['datum_narozeni'] | ||||||
|  | 			## Změny v osobě s podmínkami | ||||||
|  | 			if fcd.get('spam',False): | ||||||
|  | 				osoba_edit.datum_souhlasu_zasilani = date.today() | ||||||
|  | 			if fcd.get('stat','') in ('CZ','SK'): | ||||||
|  | 				osoba_edit.stat = fcd['stat'] | ||||||
|  | 			else: | ||||||
|  | 				## Neznámá země | ||||||
|  | 				msg = "Unknown country {}".format(fcd['stat_text']) | ||||||
|  | 
 | ||||||
|  | 			if resitel_edit: | ||||||
|  | 				## Změny v řešiteli | ||||||
|  | 				resitel_edit.skola = fcd['skola'] | ||||||
|  | 				resitel_edit.rok_maturity = fcd['rok_maturity'] | ||||||
|  | 				resitel_edit.zasilat = fcd['zasilat'] | ||||||
|  | 				resitel_edit.zasilat_cislo_emailem = fcd['zasilat_cislo_emailem'] | ||||||
|  | 				if fcd.get('skola'): | ||||||
|  | 					resitel_edit.skola = fcd['skola'] | ||||||
|  | 				else: | ||||||
|  | 					# Unknown school - log it | ||||||
|  | 					msg = "Unknown school {}, {}".format(fcd['skola_nazev'],fcd['skola_adresa']) | ||||||
|  | 				resitel_edit.save() | ||||||
|  | 			osoba_edit.save() | ||||||
|  | 			return formularOKView(request, text=f'Údaje byly úspěšně uloženy. <a href="{reverse("profil")}">Vrátit se zpět na profil.</a>') | ||||||
|  | 
 | ||||||
|  | 	return render(request, 'personalni/udaje/edit.html', {'form': form}) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @sensitive_post_parameters('jmeno', 'prijmeni', 'email', 'telefon', 'datum_narozeni', 'ulice', 'mesto', 'psc', 'skola') | ||||||
|  | def prihlaskaView(request): | ||||||
|  | 	generic_logger = logging.getLogger('seminar.prihlaska') | ||||||
|  | 	err_logger = logging.getLogger('seminar.prihlaska.problem') | ||||||
|  | 	form_logger = logging.getLogger('seminar.prihlaska.form') | ||||||
|  | 	if request.method == 'POST': | ||||||
|  | 		form = PrihlaskaForm(request.POST) | ||||||
|  | 		# TODO vyresit, co se bude v jakych situacich zobrazovat | ||||||
|  | 		if form.is_valid(): | ||||||
|  | 			generic_logger.info("Form valid") | ||||||
|  | 			fcd = form.cleaned_data | ||||||
|  | 			form_hash = hash(frozenset(fcd.items())) | ||||||
|  | 			form_logger.info(str(fcd) + str(form_hash))  # TODO možná logovat jinak | ||||||
|  | 
 | ||||||
|  | 			with transaction.atomic(): | ||||||
|  | 				u = User.objects.create_user( | ||||||
|  | 					username=fcd['username'], | ||||||
|  | 					email = fcd['email']) | ||||||
|  | 				u.save() | ||||||
|  | 				resitel_perm = Permission.objects.filter(codename__exact='resitel').first() | ||||||
|  | 				u.user_permissions.add(resitel_perm) | ||||||
|  | 				resitel_grp = Group.objects.filter(name__exact='resitel').first() | ||||||
|  | 				u.groups.add(resitel_grp) | ||||||
|  | 
 | ||||||
|  | 				o = s.Osoba( | ||||||
|  | 					jmeno = fcd['jmeno'], | ||||||
|  | 					prijmeni = fcd['prijmeni'], | ||||||
|  | 					pohlavi_muz = fcd['pohlavi_muz'], | ||||||
|  | 					email = fcd['email'], | ||||||
|  | 					telefon = fcd.get('telefon',''), | ||||||
|  | 					datum_narozeni = fcd.get('datum_narozeni',None), | ||||||
|  | 					datum_souhlasu_udaje = date.today(), | ||||||
|  | 					datum_registrace = date.today(), | ||||||
|  | 					ulice = fcd.get('ulice',''), | ||||||
|  | 					mesto = fcd.get('mesto',''), | ||||||
|  | 					psc = fcd.get('psc',''), | ||||||
|  | 					poznamka = str(fcd) | ||||||
|  | 					) | ||||||
|  | 
 | ||||||
|  | 				if fcd.get('spam',False): | ||||||
|  | 					o.datum_souhlasu_zasilani = date.today() | ||||||
|  | 				if fcd.get('stat','') in ('CZ','SK'): | ||||||
|  | 					o.stat = fcd['stat'] | ||||||
|  | 				else: | ||||||
|  | 					# Unknown country - log it | ||||||
|  | 					msg = "Unknown country {}".format(fcd['stat_text']) | ||||||
|  | 					err_logger.warn(msg + str(form_hash)) | ||||||
|  | 
 | ||||||
|  | 				 | ||||||
|  | 				# Dovolujeme doregistraci uživatele pro existující mail, takže naopak chceme doplnit/aktualizovat údaje do stávajícího objektu | ||||||
|  | 				try: | ||||||
|  | 					orig_osoba = m.Osoba.objects.get(email=fcd['email']) | ||||||
|  | 					orig_osoba.poznamka += '\nDOREGISTRACE K EXISTUJÍCÍMU E-MAILU, diff níže.' | ||||||
|  | 				except m.Osoba.DoesNotExist: | ||||||
|  | 					# Trik: Budeme aktualizovat údaje nové osoby, takže se asi nic nezmění, ale fungovat to bude. | ||||||
|  | 					orig_osoba = o | ||||||
|  | 
 | ||||||
|  | 				# Porovnání údajů | ||||||
|  | 				assert orig_osoba.user is None, "Právě-registrující-se osoba už má Uživatele!" | ||||||
|  | 				osoba_attrs = ['jmeno', 'prijmeni', 'pohlavi_muz', 'email', 'telefon', 'datum_narozeni', 'ulice', 'mesto', 'psc', 'stat', 'datum_souhlasu_udaje', 'datum_souhlasu_zasilani', 'datum_registrace'] | ||||||
|  | 				diffattrs = [] | ||||||
|  | 				for attr in osoba_attrs: | ||||||
|  | 					new = getattr(o, attr) | ||||||
|  | 					old = getattr(orig_osoba, attr) | ||||||
|  | 					if new != old: | ||||||
|  | 						orig_osoba.poznamka += f'\nRozdíl v {attr}: Původní {old}, nový {new}' | ||||||
|  | 						diffattrs.append(f'Osoba.{attr}') | ||||||
|  | 						setattr(orig_osoba, attr, new) | ||||||
|  | 				# Datum registrace chceme původní / nižší: | ||||||
|  | 				orig_osoba.datum_registrace = min(orig_osoba.datum_registrace, o.datum_registrace) | ||||||
|  | 
 | ||||||
|  | 				# Od této chvíle dál je správná osoba ta "původní", novou podle formuláře si ale zachováme | ||||||
|  | 				o, o_form = orig_osoba, o | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 				o.save() | ||||||
|  | 				o.user = u | ||||||
|  | 				o.save() | ||||||
|  | 
 | ||||||
|  | 				# Jednoduchá kvazi-kontrola duplicitních Osob | ||||||
|  | 				kolize = m.Osoba.objects.filter(jmeno=o.jmeno, prijmeni=o.prijmeni) | ||||||
|  | 				if kolize.count() > 1:	# Jednu z nich jsme právě uložili | ||||||
|  | 					err_logger.warning(f'Zaregistrovala se osoba s kolizním jménem. ID osob: {[o.id for o in kolize]}') | ||||||
|  | 
 | ||||||
|  | 				r = s.Resitel( | ||||||
|  | 					rok_maturity = fcd['rok_maturity'], | ||||||
|  | 					zasilat = fcd['zasilat'], | ||||||
|  | 					zasilat_cislo_emailem = fcd['zasilat_cislo_emailem'] | ||||||
|  | 					) | ||||||
|  | 
 | ||||||
|  | 				if fcd.get('skola'): | ||||||
|  | 					r.skola = fcd['skola'] | ||||||
|  | 				else: | ||||||
|  | 					# Unknown school - log it | ||||||
|  | 					msg = "Unknown school {}, {}".format(fcd['skola_nazev'],fcd['skola_adresa']) | ||||||
|  | 					err_logger.warn(msg + str(form_hash)) | ||||||
|  | 
 | ||||||
|  | 				# Porovnání údajů u řešitele | ||||||
|  | 				try: | ||||||
|  | 					orig_resitel = o.resitel | ||||||
|  | 					orig_resitel.poznamka += '\nDOREGISTRACE ŘEŠITELE, diff:' | ||||||
|  | 				except m.Resitel.DoesNotExist: | ||||||
|  | 					# Stejný trik: | ||||||
|  | 					orig_resitel = r | ||||||
|  | 				resitel_attrs = ['skola', 'rok_maturity', 'zasilat', 'zasilat_cislo_emailem'] | ||||||
|  | 				for attr in resitel_attrs: | ||||||
|  | 					new = getattr(r, attr) | ||||||
|  | 					old = getattr(orig_resitel, attr) | ||||||
|  | 					if new != old: | ||||||
|  | 						orig_resitel.poznamka += f'\nRozdíl v {attr}: Původní {old}, nový {new}' | ||||||
|  | 						diffattrs.append(f'Resitel.{attr}') | ||||||
|  | 						setattr(orig_resitel, attr, new) | ||||||
|  | 				r, r_form = orig_resitel, r | ||||||
|  | 
 | ||||||
|  | 				r.osoba = o	# Tohle by mělo být bezpečné… | ||||||
|  | 				r.save() | ||||||
|  | 
 | ||||||
|  | 				if diffattrs: err_logger.warning(f'Different fields when matching Řešitel id {r.id} or Osoba id {o.id}: {diffattrs}') | ||||||
|  | 
 | ||||||
|  | 			posli_reset_hesla(u, request) | ||||||
|  | 			return formularOKView(request, text='Na tvůj e-mail jsme právě poslali odkaz pro nastavení hesla.') | ||||||
|  | 
 | ||||||
|  | 	# if a GET (or any other method) we'll create a blank form | ||||||
|  | 	else: | ||||||
|  | 		form = PrihlaskaForm() | ||||||
|  | 
 | ||||||
|  | 	return render(request, 'personalni/udaje/prihlaska.html', {'form': form}) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Jen hloupé rozhazovátko | ||||||
|  | def profilView(request): | ||||||
|  | 	user = request.user | ||||||
|  | 	if user.has_perm('auth.org'): | ||||||
|  | 		return OrgoRozcestnikView.as_view()(request) | ||||||
|  | 	if user.has_perm('auth.resitel'): | ||||||
|  | 		return ResitelView.as_view()(request) | ||||||
|  | 	else: | ||||||
|  | 		return LoginView.as_view()(request) | ||||||
							
								
								
									
										190
									
								
								seminar/admin.py
									
									
									
									
									
								
							
							
						
						
									
										190
									
								
								seminar/admin.py
									
									
									
									
									
								
							|  | @ -1,12 +1,9 @@ | ||||||
| from django.contrib import admin | from django.contrib import admin | ||||||
| from django.contrib.auth.models import Group |  | ||||||
| from django.db import models | from django.db import models | ||||||
| from django.forms import widgets, ModelForm | from django.forms import widgets, ModelForm | ||||||
| from django.core.exceptions import ValidationError | from django.core.exceptions import ValidationError | ||||||
| 
 | 
 | ||||||
| from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter | from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter | ||||||
| from reversion.admin import VersionAdmin |  | ||||||
| from django_reverse_admin import ReverseModelAdmin |  | ||||||
| from solo.admin import SingletonModelAdmin | from solo.admin import SingletonModelAdmin | ||||||
| from django.utils.safestring import mark_safe | from django.utils.safestring import mark_safe | ||||||
| 
 | 
 | ||||||
|  | @ -15,10 +12,7 @@ from seminar.utils import hlavni_problem | ||||||
| # Todo: reversion | # Todo: reversion | ||||||
| 
 | 
 | ||||||
| import seminar.models as m | import seminar.models as m | ||||||
| import seminar.treelib as tl |  | ||||||
| 
 | 
 | ||||||
| admin.site.register(m.Skola) |  | ||||||
| admin.site.register(m.Prijemce) |  | ||||||
| admin.site.register(m.Rocnik) | admin.site.register(m.Rocnik) | ||||||
| 
 | 
 | ||||||
| class CisloForm(ModelForm): | class CisloForm(ModelForm): | ||||||
|  | @ -58,6 +52,10 @@ class CisloForm(ModelForm): | ||||||
| 		if errors: | 		if errors: | ||||||
| 			errors.append(ValidationError(mark_safe( | 			errors.append(ValidationError(mark_safe( | ||||||
| 				'<b>Pokud chceš učinit všechny problémy, co nejsou zadané ani vyřešené, zadanými a číslo zveřejnit, můžeš to udělat pomocí akce v <a href="/admin/seminar/cislo">seznamu čísel</a></b>'))) | 				'<b>Pokud chceš učinit všechny problémy, co nejsou zadané ani vyřešené, zadanými a číslo zveřejnit, můžeš to udělat pomocí akce v <a href="/admin/seminar/cislo">seznamu čísel</a></b>'))) | ||||||
|  | 		if self.cleaned_data.get('datum_vydani') == None: | ||||||
|  | 			self.add_error('datum_vydani','Číslo určené ke zveřejnění nemá nastavené datum vydání') | ||||||
|  | 
 | ||||||
|  | 		if errors: | ||||||
| 			raise ValidationError(errors) | 			raise ValidationError(errors) | ||||||
| 
 | 
 | ||||||
| 		return self.cleaned_data | 		return self.cleaned_data | ||||||
|  | @ -105,47 +103,6 @@ class CisloAdmin(admin.ModelAdmin): | ||||||
| 
 | 
 | ||||||
| 	force_publish.short_description = 'Zveřejnit vybraná čísla a všechny návrhy úloh v nich učinit zadanými' | 	force_publish.short_description = 'Zveřejnit vybraná čísla a všechny návrhy úloh v nich učinit zadanými' | ||||||
| 
 | 
 | ||||||
| @admin.register(m.Osoba) |  | ||||||
| class OsobaAdmin(admin.ModelAdmin): |  | ||||||
| 	actions = ['synchronizuj_maily', 'udelej_orgem'] |  | ||||||
| 
 |  | ||||||
| 	def synchronizuj_maily(self, request, queryset): |  | ||||||
| 		for o in queryset: |  | ||||||
| 			if o.user is not None: |  | ||||||
| 				u = o.user |  | ||||||
| 				u.email = o.email |  | ||||||
| 				u.save() |  | ||||||
| 		self.message_user(request, "E-maily synchronizovány.") |  | ||||||
| 	synchronizuj_maily.short_description = "Synchronizuj vybraným osobám e-maily do uživatelů" |  | ||||||
| 
 |  | ||||||
| 	def udelej_orgem(self,request,queryset): |  | ||||||
| 		org_group = Group.objects.get(name='org') |  | ||||||
| 		print(queryset) |  | ||||||
| 		for o in queryset: |  | ||||||
| 			user = o.user |  | ||||||
| 			print(user) |  | ||||||
| 			user.groups.add(org_group) |  | ||||||
| 			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" |  | ||||||
| 
 |  | ||||||
| class OsobaInline(admin.TabularInline): |  | ||||||
| 	model = m.Osoba |  | ||||||
| 
 |  | ||||||
| @admin.register(m.Organizator) |  | ||||||
| class OrganizatorAdmin(ReverseModelAdmin): |  | ||||||
| 	search_fields = ['osoba__jmeno', 'osoba__prijmeni', 'osoba__prezdivka'] |  | ||||||
| 	inline_type = 'stacked' |  | ||||||
| 	inline_reverse = ['osoba'] |  | ||||||
| 
 |  | ||||||
| @admin.register(m.Resitel) |  | ||||||
| class ResitelAdmin(ReverseModelAdmin): |  | ||||||
| 	search_fields = ['osoba__jmeno', 'osoba__prijmeni', 'osoba__prezdivka'] |  | ||||||
| 	ordering = ('osoba__jmeno','osoba__prijmeni') |  | ||||||
| 	inline_type = 'stacked' |  | ||||||
| 	inline_reverse = ['osoba'] |  | ||||||
| 
 | 
 | ||||||
| @admin.register(m.Problem) | @admin.register(m.Problem) | ||||||
| class ProblemAdmin(PolymorphicParentModelAdmin): | class ProblemAdmin(PolymorphicParentModelAdmin): | ||||||
|  | @ -196,147 +153,8 @@ class ResitelInline(admin.TabularInline): | ||||||
| 	model = m.Resitel | 	model = m.Resitel | ||||||
| 	extra = 1 | 	extra = 1 | ||||||
| 
 | 
 | ||||||
| class SoustredeniUcastniciInline(admin.TabularInline): |  | ||||||
| 	model = m.Soustredeni_Ucastnici |  | ||||||
| 	extra = 1 |  | ||||||
| 	fields = ['resitel','poznamka'] |  | ||||||
| 	autocomplete_fields = ['resitel'] |  | ||||||
| 	ordering = ['resitel__osoba__jmeno', 'resitel__osoba__prijmeni'] |  | ||||||
| 	formfield_overrides = { |  | ||||||
| 		models.TextField: {'widget': widgets.TextInput} |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	def get_queryset(self,request): |  | ||||||
| 		qs = super().get_queryset(request) |  | ||||||
| 		return qs.select_related('resitel','soustredeni') |  | ||||||
| 
 |  | ||||||
| class SoustredeniOrganizatoriInline(admin.TabularInline): |  | ||||||
| 	model = m.Soustredeni.organizatori.through |  | ||||||
| 	extra = 1 |  | ||||||
| 	fields = ['organizator','poznamka'] |  | ||||||
| 	autocomplete_fields = ['organizator'] |  | ||||||
| 	ordering = ['organizator__osoba__jmeno','organizator__prijmeni'] |  | ||||||
| 	formfield_overrides = { |  | ||||||
| 		models.TextField: {'widget': widgets.TextInput} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	def get_queryset(self,request): |  | ||||||
| 		qs = super().get_queryset(request) |  | ||||||
| 		return qs.select_related('organizator', 'soustredeni') |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @admin.register(m.Soustredeni) |  | ||||||
| class SoustredeniAdmin(admin.ModelAdmin): |  | ||||||
| 	model = m.Soustredeni |  | ||||||
| 	inline_type = 'tabular' |  | ||||||
| 	inlines = [SoustredeniUcastniciInline, SoustredeniOrganizatoriInline] |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class PrilohaReseniInline(admin.TabularInline): |  | ||||||
| 	model = m.PrilohaReseni |  | ||||||
| 	extra = 1 |  | ||||||
| admin.site.register(m.PrilohaReseni) |  | ||||||
| 
 |  | ||||||
| class Reseni_ResiteleInline(admin.TabularInline): |  | ||||||
| 	model = m.Reseni_Resitele |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @admin.register(m.Reseni) |  | ||||||
| class ReseniAdmin(ReverseModelAdmin): |  | ||||||
| 	base_model = m.Reseni |  | ||||||
| 	inline_type = 'tabular' |  | ||||||
| 	# inline_reverse = ['text_cely','resitele'] TODO vrátit zpět a zrychlit dotaz |  | ||||||
| 	inline_reverse = ['resitele'] |  | ||||||
| 	exclude = ['text_zkraceny', 'text_zkraceny_set'] |  | ||||||
| 	inlines = [PrilohaReseniInline] |  | ||||||
| # FAIL in template |  | ||||||
| #	inlines = [PrilohaReseniInline,Reseni_ResiteleInline] |  | ||||||
| 
 |  | ||||||
| admin.site.register(m.Hodnoceni) |  | ||||||
| admin.site.register(m.Pohadka) | admin.site.register(m.Pohadka) | ||||||
| admin.site.register(m.Obrazek) | admin.site.register(m.Obrazek) | ||||||
| 
 |  | ||||||
| # Polymorfismus pro stromy |  | ||||||
| # TODO: Inlines podle https://django-polymorphic.readthedocs.io/en/stable/admin.html |  | ||||||
| 
 |  | ||||||
| @admin.register(m.TreeNode) |  | ||||||
| class TreeNodeAdmin(PolymorphicParentModelAdmin): |  | ||||||
| 	base_model = m.TreeNode |  | ||||||
| 	child_models = [ |  | ||||||
| 		m.RocnikNode, |  | ||||||
| 		m.CisloNode, |  | ||||||
| 		m.MezicisloNode, |  | ||||||
| 		m.TemaVCisleNode, |  | ||||||
| 		m.UlohaZadaniNode, |  | ||||||
| 		m.PohadkaNode, |  | ||||||
| 		m.UlohaVzorakNode, |  | ||||||
| 		m.TextNode, |  | ||||||
| 		m.CastNode, |  | ||||||
| 		m.OrgTextNode, |  | ||||||
| 		] |  | ||||||
| 
 |  | ||||||
| 	actions = ['aktualizuj_nazvy'] |  | ||||||
| 
 |  | ||||||
| 	# XXX: nejspíš je to totální DB HOG, nechcete to použít moc často. |  | ||||||
| 	def aktualizuj_nazvy(self, request, queryset): |  | ||||||
| 		newqs = queryset.get_real_instances() |  | ||||||
| 		for tn in newqs: |  | ||||||
| 			tn.aktualizuj_nazev() |  | ||||||
| 			tn.save() |  | ||||||
| 		self.message_user(request, "Názvy aktualizovány.") |  | ||||||
| 	aktualizuj_nazvy.short_description = "Aktualizuj vybraným TreeNodům názvy" |  | ||||||
| 
 |  | ||||||
| @admin.register(m.RocnikNode) |  | ||||||
| class RocnikNodeAdmin(PolymorphicChildModelAdmin): |  | ||||||
| 	base_model = m.RocnikNode |  | ||||||
| 	show_in_index = True |  | ||||||
| 
 |  | ||||||
| @admin.register(m.CisloNode) |  | ||||||
| class CisloNodeAdmin(PolymorphicChildModelAdmin): |  | ||||||
| 	base_model = m.CisloNode |  | ||||||
| 	show_in_index = True |  | ||||||
| 
 |  | ||||||
| @admin.register(m.MezicisloNode) |  | ||||||
| class MezicisloNodeAdmin(PolymorphicChildModelAdmin): |  | ||||||
| 	base_model = m.MezicisloNode |  | ||||||
| 	show_in_index = True |  | ||||||
| 
 |  | ||||||
| @admin.register(m.TemaVCisleNode) |  | ||||||
| class TemaVCisleNodeAdmin(PolymorphicChildModelAdmin): |  | ||||||
| 	base_model = m.TemaVCisleNode |  | ||||||
| 	show_in_index = True |  | ||||||
| 
 |  | ||||||
| @admin.register(m.UlohaZadaniNode) |  | ||||||
| class UlohaZadaniNodeAdmin(PolymorphicChildModelAdmin): |  | ||||||
| 	base_model = m.UlohaZadaniNode |  | ||||||
| 	show_in_index = True |  | ||||||
| 
 |  | ||||||
| @admin.register(m.PohadkaNode) |  | ||||||
| class PohadkaNodeAdmin(PolymorphicChildModelAdmin): |  | ||||||
| 	base_model = m.PohadkaNode |  | ||||||
| 	show_in_index = True |  | ||||||
| 
 |  | ||||||
| @admin.register(m.UlohaVzorakNode) |  | ||||||
| class UlohaVzorakNodeAdmin(PolymorphicChildModelAdmin): |  | ||||||
| 	base_model = m.UlohaVzorakNode |  | ||||||
| 	show_in_index = True |  | ||||||
| 
 |  | ||||||
| @admin.register(m.TextNode) |  | ||||||
| class TextNodeAdmin(PolymorphicChildModelAdmin): |  | ||||||
| 	base_model = m.TextNode |  | ||||||
| 	show_in_index = True |  | ||||||
| 
 |  | ||||||
| @admin.register(m.CastNode) |  | ||||||
| class TextNodeAdmin(PolymorphicChildModelAdmin): |  | ||||||
| 	base_model = m.CastNode |  | ||||||
| 	show_in_index = True |  | ||||||
| 	fields = ('nadpis',) |  | ||||||
| 
 |  | ||||||
| @admin.register(m.OrgTextNode) |  | ||||||
| class TextNodeAdmin(PolymorphicChildModelAdmin): |  | ||||||
| 	base_model = m.OrgTextNode |  | ||||||
| 	show_in_index = True |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| admin.site.register(m.Nastaveni, SingletonModelAdmin) | admin.site.register(m.Nastaveni, SingletonModelAdmin) | ||||||
| admin.site.register(m.Novinky) | admin.site.register(m.Novinky) | ||||||
|  |  | ||||||
|  | @ -1,139 +0,0 @@ | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
| 
 |  | ||||||
| from autocomplete_light import shortcuts as autocomplete_light |  | ||||||
| 
 |  | ||||||
| from .models import Skola, Resitel, Problem, Organizator |  | ||||||
| from taggit.models import Tag |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| autocomplete_light.register(Tag) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class SkolaAutocomplete(autocomplete_light.AutocompleteModelBase): |  | ||||||
| 
 |  | ||||||
|     model = Skola |  | ||||||
| 
 |  | ||||||
|     search_fields = ['nazev', 'mesto', 'ulice'] |  | ||||||
| 
 |  | ||||||
|     split_words = True |  | ||||||
| 
 |  | ||||||
|     limit_choices = 15 |  | ||||||
| 
 |  | ||||||
|     attrs = { |  | ||||||
|         # This will set the input placeholder attribute: |  | ||||||
|         'placeholder': 'Škola', |  | ||||||
|         # This will set the yourlabs.Autocomplete.minimumCharacters |  | ||||||
|         # options, the naming conversion is handled by jQuery |  | ||||||
|         'data-autocomplete-minimum-characters': 1, |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     widget_attrs = { |  | ||||||
|         'data-widget-maximum-values': 15, |  | ||||||
|         'class': 'modern-style', |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| autocomplete_light.register(SkolaAutocomplete) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class ResitelAutocomplete(autocomplete_light.AutocompleteModelBase): |  | ||||||
| 
 |  | ||||||
|     model = Resitel |  | ||||||
|      |  | ||||||
|     search_fields = ['jmeno', 'prijmeni'] |  | ||||||
| 
 |  | ||||||
|     split_words = False |  | ||||||
| 
 |  | ||||||
|     limit_choices = 15 |  | ||||||
| 
 |  | ||||||
|     def choice_label(self, resitel): |  | ||||||
|         return "%s, %s (%s)" % (resitel.plne_jmeno(), resitel.mesto, resitel.rok_maturity) |  | ||||||
| 
 |  | ||||||
|     attrs= { |  | ||||||
|         # This will set the input placeholder attribute: |  | ||||||
|         'placeholder': 'Řešitel', |  | ||||||
|         # This will set the yourlabs.Autocomplete.minimumCharacters |  | ||||||
|         # options, the naming conversion is handled by jQuery |  | ||||||
|         'data-autocomplete-minimum-characters': 1, |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     widget_attrs = { |  | ||||||
|         'data-widget-maximum-values': 15, |  | ||||||
|         # Enable modern-style widget ! |  | ||||||
|         'class': 'modern-style', |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| autocomplete_light.register(ResitelAutocomplete) |  | ||||||
| 
 |  | ||||||
| class OrganizatorAutocomplete(autocomplete_light.AutocompleteModelBase): |  | ||||||
| 
 |  | ||||||
|     model = Organizator |  | ||||||
|      |  | ||||||
|     search_fields = ['user__first_name', 'user__last_name', 'prezdivka'] |  | ||||||
| 
 |  | ||||||
|     split_words = False |  | ||||||
| 
 |  | ||||||
|     limit_choices = 15 |  | ||||||
| 
 |  | ||||||
|     def choice_label(self, organizator): |  | ||||||
|         return "%s '%s' %s" % (organizator.user.first_name, |  | ||||||
|                                 organizator.prezdivka, |  | ||||||
|                                 organizator.user.last_name) |  | ||||||
| 
 |  | ||||||
|     attrs = { |  | ||||||
|         # This will set the input placeholder attribute: |  | ||||||
|         'placeholder': 'Organizátor', |  | ||||||
|         # This will set the yourlabs.Autocomplete.minimumCharacters |  | ||||||
|         # options, the naming conversion is handled by jQuery |  | ||||||
|         'data-autocomplete-minimum-characters': 1, |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     widget_attrs = { |  | ||||||
|         'data-widget-maximum-values': 15, |  | ||||||
|         # Enable modern-style widget ! |  | ||||||
|         'class': 'modern-style', |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| autocomplete_light.register(OrganizatorAutocomplete) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class ProblemAutocomplete(autocomplete_light.AutocompleteModelBase): |  | ||||||
| 
 |  | ||||||
|     model = Problem |  | ||||||
|      |  | ||||||
|     search_fields = ['nazev'] |  | ||||||
| 
 |  | ||||||
|     split_words = False |  | ||||||
| 
 |  | ||||||
|     limit_choices = 10 |  | ||||||
| 
 |  | ||||||
|     def choice_label(self, p): |  | ||||||
|         if p.stav == Problem.STAV_ZADANY: |  | ||||||
|             popisek = "" |  | ||||||
|             try: |  | ||||||
|                 popisek = "%s (%s, %s.%s)".format(p.nazev, p.typ, p.cislo_zadani.rocnik.rocnik, p.kod_v_rocniku()) |  | ||||||
|             except: |  | ||||||
|                 #popisek = "%s (%s, %s.%s)".format(p.nazev, p.typ, p.stav) |  | ||||||
|                 popisek = "CHYBA" |  | ||||||
|             return popisek |  | ||||||
|         else: |  | ||||||
|             return "%s (%s, %s)".format(p.nazev, p.typ, p.stav) |  | ||||||
| 
 |  | ||||||
|     attrs = { |  | ||||||
|         # This will set the input placeholder attribute: |  | ||||||
|         'placeholder': 'Problém', |  | ||||||
|         # This will set the yourlabs.Autocomplete.minimumCharacters |  | ||||||
|         # options, the naming conversion is handled by jQuery |  | ||||||
|         'data-autocomplete-minimum-characters': 1, |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     widget_attrs = { |  | ||||||
|         'data-widget-maximum-values': 10, |  | ||||||
|         # Enable modern-style widget ! |  | ||||||
|         'class': 'modern-style', |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| #FIXME Nefunguje, nevime proc |  | ||||||
| #autocomplete_light.register(ProblemAutocomplete) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|  | @ -12,7 +12,7 @@ import taggit.managers | ||||||
| 
 | 
 | ||||||
| from datetime import date | from datetime import date | ||||||
| from django.db.models import Q | from django.db.models import Q | ||||||
| from seminar.treelib import get_parent | from treenode.treelib import get_parent | ||||||
| import datetime as dt | import datetime as dt | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ | ||||||
| 
 | 
 | ||||||
| from django.db import migrations, models | from django.db import migrations, models | ||||||
| import django.db.models.deletion | import django.db.models.deletion | ||||||
| from seminar.treelib import get_parent | from treenode.treelib import get_parent | ||||||
| 
 | 
 | ||||||
| import logging | import logging | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
|  |  | ||||||
							
								
								
									
										1786
									
								
								seminar/models.py
									
									
									
									
									
								
							
							
						
						
									
										1786
									
								
								seminar/models.py
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										8
									
								
								seminar/models/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								seminar/models/__init__.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | ||||||
|  | from .tvorba import * | ||||||
|  | from .odevzdavatko import * | ||||||
|  | from .base import * | ||||||
|  | from .personalni import * | ||||||
|  | from .soustredeni import * | ||||||
|  | from .pomocne import * | ||||||
|  | from .treenode import * | ||||||
|  | from .novinky import * | ||||||
							
								
								
									
										22
									
								
								seminar/models/base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								seminar/models/base.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | ||||||
|  | from django.urls import reverse | ||||||
|  | from django.db import models | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class SeminarModelBase(models.Model): | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         abstract = True | ||||||
|  | 
 | ||||||
|  |     def verejne(self): | ||||||
|  |         return False | ||||||
|  | 
 | ||||||
|  |     # def get_absolute_url(self): | ||||||
|  |     # 	return "https://" + str(get_current_site(None)) + self.verejne_url() | ||||||
|  | 
 | ||||||
|  |     def admin_url(self): | ||||||
|  |         model_name = self.__class__.__name__.lower() | ||||||
|  |         return reverse('admin:seminar_{}_change'.format(model_name), args=(self.id, )) | ||||||
|  | 
 | ||||||
|  | # def verejne_url(self): | ||||||
|  | # 	return None | ||||||
|  | 
 | ||||||
							
								
								
									
										38
									
								
								seminar/models/novinky.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								seminar/models/novinky.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,38 @@ | ||||||
|  | from django.db import models | ||||||
|  | from imagekit.models import ImageSpecField | ||||||
|  | from imagekit.processors import ResizeToFit | ||||||
|  | 
 | ||||||
|  | from reversion import revisions as reversion | ||||||
|  | 
 | ||||||
|  | from . import personalni as pm | ||||||
|  | 
 | ||||||
|  | @reversion.register(ignore_duplicates=True) | ||||||
|  | class Novinky(models.Model): | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         verbose_name = 'Novinka' | ||||||
|  |         verbose_name_plural = 'Novinky' | ||||||
|  |         ordering = ['-datum'] | ||||||
|  | 
 | ||||||
|  |     datum = models.DateField(auto_now_add=True) | ||||||
|  | 
 | ||||||
|  |     text = models.TextField('Text novinky', blank=True, null=True) | ||||||
|  |     obrazek = models.ImageField('Obrázek', upload_to='image_novinky/%Y/%m/%d/', | ||||||
|  |                                 null=True, blank=True) | ||||||
|  | 
 | ||||||
|  |     obrazek_maly = ImageSpecField(source='obrazek', | ||||||
|  |                                   processors=[ | ||||||
|  |                                       ResizeToFit(350, 200, upscale=False) | ||||||
|  |                                   ], | ||||||
|  |                                   options={'quality': 95}) | ||||||
|  | 
 | ||||||
|  |     autor = models.ForeignKey(pm.Organizator, verbose_name='Autor novinky', null=True, | ||||||
|  |                               on_delete=models.SET_NULL) | ||||||
|  | 
 | ||||||
|  |     zverejneno = models.BooleanField('Zveřejněno', default=False) | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         if self.text: | ||||||
|  |             return '[' + str(self.datum) + '] ' + self.text[0:50] | ||||||
|  |         else: | ||||||
|  |             return '[' + str(self.datum) + '] ' | ||||||
							
								
								
									
										191
									
								
								seminar/models/odevzdavatko.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								seminar/models/odevzdavatko.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,191 @@ | ||||||
|  | import os | ||||||
|  | 
 | ||||||
|  | import reversion | ||||||
|  | 
 | ||||||
|  | from django.contrib.sites.shortcuts import get_current_site | ||||||
|  | from django.db import models | ||||||
|  | from django.db.models import Sum | ||||||
|  | from django.urls import reverse_lazy | ||||||
|  | from django.utils import timezone | ||||||
|  | from django.conf import settings | ||||||
|  | 
 | ||||||
|  | from seminar.models import tvorba as am | ||||||
|  | from seminar.models import personalni as pm | ||||||
|  | from seminar.models import treenode as tm | ||||||
|  | from seminar.models import base as bm | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @reversion.register(ignore_duplicates=True) | ||||||
|  | class Reseni(bm.SeminarModelBase): | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         db_table = 'seminar_reseni' | ||||||
|  |         verbose_name = 'Řešení' | ||||||
|  |         verbose_name_plural = 'Řešení' | ||||||
|  |         #ordering = ['-problem', 'resitele']	# FIXME: Takhle to chceme, ale nefunguje to. | ||||||
|  |         ordering = ['-cas_doruceni'] | ||||||
|  | 
 | ||||||
|  |     # Interní ID | ||||||
|  |     id = models.AutoField(primary_key = True) | ||||||
|  | 
 | ||||||
|  |     # Ke každé dvojici řešní a problém existuje nanejvýš jedno hodnocení, doplnění vazby. | ||||||
|  |     problem = models.ManyToManyField(am.Problem, verbose_name='problém', help_text='Problém', | ||||||
|  |                                      through='Hodnoceni') | ||||||
|  | 
 | ||||||
|  |     resitele = models.ManyToManyField(pm.Resitel, verbose_name='autoři řešení', | ||||||
|  |                                       help_text='Seznam autorů řešení', through='Reseni_Resitele') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     cas_doruceni = models.DateTimeField('čas_doručení', default=timezone.now, blank=True) | ||||||
|  | 
 | ||||||
|  |     FORMA_PAPIR = 'papir' | ||||||
|  |     FORMA_EMAIL = 'email' | ||||||
|  |     FORMA_UPLOAD = 'upload' | ||||||
|  |     FORMA_CHOICES = [ | ||||||
|  |         (FORMA_PAPIR, 'Papírové řešení'), | ||||||
|  |         (FORMA_EMAIL, 'Emailem'), | ||||||
|  |         (FORMA_UPLOAD, 'Upload přes web'), | ||||||
|  |     ] | ||||||
|  |     forma = models.CharField('forma řešení', max_length=16, choices=FORMA_CHOICES, blank=False, | ||||||
|  |                              default=FORMA_EMAIL) | ||||||
|  | 
 | ||||||
|  |     text_cely = models.OneToOneField('ReseniNode', verbose_name='Plná verze textu řešení', | ||||||
|  |                                      blank=True, null=True, related_name="reseni_cely_set", | ||||||
|  |                                      on_delete=models.PROTECT) | ||||||
|  | 
 | ||||||
|  |     poznamka = models.TextField('neveřejná poznámka', blank=True, | ||||||
|  |                                 help_text='Neveřejná poznámka k řešení (plain text)') | ||||||
|  | 
 | ||||||
|  |     zverejneno = models.BooleanField('řešení zveřejněno', default=False, | ||||||
|  |                                      help_text='Udává, zda je řešení zveřejněno') | ||||||
|  | 
 | ||||||
|  |     def verejne_url(self): | ||||||
|  |         return str(reverse_lazy('odevzdavatko_detail_reseni', args=[self.id])) | ||||||
|  | 
 | ||||||
|  |     def absolute_url(self): | ||||||
|  |         return "https://" + str(get_current_site(None)) + self.verejne_url() | ||||||
|  | 
 | ||||||
|  |     # má OneToOneField s: | ||||||
|  |     # Konfera | ||||||
|  | 
 | ||||||
|  |     # má ForeignKey s: | ||||||
|  |     # Hodnoceni | ||||||
|  | 
 | ||||||
|  |     def sum_body(self): | ||||||
|  |         return self.hodnoceni_set.all().aggregate(Sum('body'))["body__sum"] | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         return "{}({}): {}({})".format(self.resitele.first(),len(self.resitele.all()), self.problem.first() ,len(self.problem.all())) | ||||||
|  |     # NOTE: Potenciální DB HOG (bez select_related) | ||||||
|  | 
 | ||||||
|  | ## Pravdepodobne uz nebude potreba: | ||||||
|  | #	def save(self, *args, **kwargs): | ||||||
|  | #		if ((self.cislo_body is None) and (self.problem.cislo_reseni) and | ||||||
|  | #				(self.problem.typ == Problem.TYP_ULOHA)): | ||||||
|  | #			self.cislo_body = self.problem.cislo_reseni | ||||||
|  | #		super(Reseni, self).save(*args, **kwargs) | ||||||
|  | 
 | ||||||
|  | class Hodnoceni(bm.SeminarModelBase): | ||||||
|  |     class Meta: | ||||||
|  |         db_table = 'seminar_hodnoceni' | ||||||
|  |         verbose_name = 'Hodnocení' | ||||||
|  |         verbose_name_plural = 'Hodnocení' | ||||||
|  | 
 | ||||||
|  |     # Interní ID | ||||||
|  |     id = models.AutoField(primary_key = True) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     body = models.DecimalField(max_digits=8, decimal_places=1, verbose_name='body', | ||||||
|  |                                blank=True, null=True) | ||||||
|  | 
 | ||||||
|  |     cislo_body = models.ForeignKey(am.Cislo, verbose_name='číslo pro body', | ||||||
|  |                                    related_name='hodnoceni', blank=True, null=True, on_delete=models.PROTECT) | ||||||
|  | 
 | ||||||
|  |     reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE) | ||||||
|  | 
 | ||||||
|  |     problem = models.ForeignKey(am.Problem, verbose_name='problém', | ||||||
|  |                                 related_name='hodnoceni', on_delete=models.PROTECT) | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         return "{}, {}, {}".format(self.problem, self.reseni, self.body) | ||||||
|  | 
 | ||||||
|  | def generate_filename(self, filename): | ||||||
|  |     return os.path.join( | ||||||
|  |         settings.SEMINAR_RESENI_DIR, | ||||||
|  |         am.aux_generate_filename(self, filename) | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @reversion.register(ignore_duplicates=True) | ||||||
|  | class PrilohaReseni(bm.SeminarModelBase): | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         db_table = 'seminar_priloha_reseni' | ||||||
|  |         verbose_name = 'Příloha řešení' | ||||||
|  |         verbose_name_plural = 'Přílohy řešení' | ||||||
|  |         ordering = ['reseni', 'vytvoreno'] | ||||||
|  | 
 | ||||||
|  |     # Interní ID | ||||||
|  |     id = models.AutoField(primary_key = True) | ||||||
|  | 
 | ||||||
|  |     reseni = models.ForeignKey(Reseni, verbose_name='řešení', related_name='prilohy', | ||||||
|  |                                on_delete=models.CASCADE) | ||||||
|  | 
 | ||||||
|  |     vytvoreno = models.DateTimeField('vytvořeno', default=timezone.now, blank=True, editable=False) | ||||||
|  | 
 | ||||||
|  |     soubor = models.FileField('soubor', upload_to = generate_filename) | ||||||
|  | 
 | ||||||
|  |     poznamka = models.TextField('neveřejná poznámka', blank=True, | ||||||
|  |                                 help_text='Neveřejná poznámka k příloze řešení (plain text), např. o původu') | ||||||
|  | 
 | ||||||
|  |     res_poznamka = models.TextField('poznámka řešitele', blank=True, | ||||||
|  |                                     help_text='Poznámka k příloze řešení, např. co daný soubor obsahuje') | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         return str(self.soubor) | ||||||
|  | 
 | ||||||
|  |     def split(self): | ||||||
|  |         "Vrátí cestu rozsekanou po složkách. To se hodí v templatech" | ||||||
|  |         # Věřím, že tohle funguje, případně použít os.path nebo pathlib. | ||||||
|  |         return self.soubor.url.split('/') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Vazebna tabulka. Mozna se generuje automaticky. | ||||||
|  | @reversion.register(ignore_duplicates=True) | ||||||
|  | class Reseni_Resitele(models.Model): | ||||||
|  | 
 | ||||||
|  |     class Meta: | ||||||
|  |         db_table = 'seminar_reseni_resitele' | ||||||
|  |         verbose_name = 'Řešení řešitelů' | ||||||
|  |         verbose_name_plural = 'Řešení řešitelů' | ||||||
|  |         ordering = ['reseni', 'resitele'] | ||||||
|  | 
 | ||||||
|  |     # Interní ID | ||||||
|  |     id = models.AutoField(primary_key = True) | ||||||
|  | 
 | ||||||
|  |     resitele = models.ForeignKey(pm.Resitel, verbose_name='řešitel', on_delete=models.PROTECT) | ||||||
|  | 
 | ||||||
|  |     reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE) | ||||||
|  | 
 | ||||||
|  |     # podil - jakou merou se ktery resitel podilel na danem reseni | ||||||
|  |     #	- pouziti v budoucnu, pokud by resitele nemeli dostat vsichni stejne bodu za spolecne reseni | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         return '{} od {}'.format(self.reseni, self.resitel) | ||||||
|  |     # NOTE: Poteciální DB HOG bez select_related | ||||||
|  | 
 | ||||||
|  | class ReseniNode(tm.TreeNode): | ||||||
|  |     class Meta: | ||||||
|  |         db_table = 'seminar_nodes_otistene_reseni' | ||||||
|  |         verbose_name = 'Otištěné řešení (Node)' | ||||||
|  |         verbose_name_plural = 'Otištěná řešení (Node)' | ||||||
|  |     reseni = models.ForeignKey(Reseni, | ||||||
|  |                                on_delete=models.PROTECT, | ||||||
|  |                                verbose_name = 'reseni') | ||||||
|  | 
 | ||||||
|  |     def aktualizuj_nazev(self): | ||||||
|  |         self.nazev = "ReseniNode: "+str(self.reseni) | ||||||
|  | 
 | ||||||
|  |     def getOdkazStr(self): | ||||||
|  |         return str(self.reseni) | ||||||
|  | 
 | ||||||
							
								
								
									
										438
									
								
								seminar/models/personalni.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										438
									
								
								seminar/models/personalni.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,438 @@ | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | import logging | ||||||
|  | 
 | ||||||
|  | from django.db import models | ||||||
|  | from django.utils import timezone | ||||||
|  | from django.conf import settings | ||||||
|  | from django.core.exceptions import ValidationError | ||||||
|  | from imagekit.models import ImageSpecField, ProcessedImageField | ||||||
|  | from imagekit.processors import ResizeToFit, Transpose | ||||||
|  | 
 | ||||||
|  | from django_countries.fields import CountryField | ||||||
|  | 
 | ||||||
|  | from reversion import revisions as reversion | ||||||
|  | 
 | ||||||
|  | from .base import SeminarModelBase | ||||||
|  | 
 | ||||||
|  | logger = logging.getLogger(__name__) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @reversion.register(ignore_duplicates=True) | ||||||
|  | class Osoba(SeminarModelBase): | ||||||
|  | 	 | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_osoby' | ||||||
|  | 		verbose_name = 'Osoba' | ||||||
|  | 		verbose_name_plural = 'Osoby' | ||||||
|  | 		ordering = ['prijmeni','jmeno'] | ||||||
|  | 	 | ||||||
|  | 	id = models.AutoField(primary_key = True) | ||||||
|  | 
 | ||||||
|  | 	jmeno = models.CharField('jméno', max_length=256) | ||||||
|  | 
 | ||||||
|  | 	prijmeni = models.CharField('příjmení', max_length=256) | ||||||
|  | 
 | ||||||
|  | 	prezdivka = models.CharField('přezdívka', blank=True, null=True, max_length=256) | ||||||
|  | 
 | ||||||
|  | 	# User, pokud má na webu účet | ||||||
|  | 	user = models.OneToOneField(settings.AUTH_USER_MODEL, blank=True, null=True,  | ||||||
|  | 				verbose_name='uživatel', on_delete=models.DO_NOTHING) | ||||||
|  | 
 | ||||||
|  | 	# Pohlaví. Že ho neznáme se snad nestane (a ušetří to práci při programování) | ||||||
|  | 	pohlavi_muz = models.BooleanField('pohlaví (muž)', default=False) | ||||||
|  | 
 | ||||||
|  | 	email = models.EmailField('e-mail', max_length=256, blank=True, default='') | ||||||
|  | 
 | ||||||
|  | 	telefon = models.CharField('telefon', max_length=256, blank=True, default='') | ||||||
|  | 
 | ||||||
|  | 	datum_narozeni = models.DateField('datum narození', blank=True, null=True) | ||||||
|  | 
 | ||||||
|  | 	# NULL dokud nedali souhlas | ||||||
|  | 	datum_souhlasu_udaje = models.DateField('datum souhlasu (údaje)', blank=True, null=True, | ||||||
|  | 		help_text='Datum souhlasu se zpracováním osobních údajů') | ||||||
|  | 
 | ||||||
|  | 	# NULL dokud nedali souhlas | ||||||
|  | 	datum_souhlasu_zasilani = models.DateField('datum souhlasu (spam)', blank=True, null=True, | ||||||
|  | 		help_text='Datum souhlasu se zasíláním MFF materiálů') | ||||||
|  | 
 | ||||||
|  | 	# Alespoň odhad (rok či i měsíc) | ||||||
|  | 	datum_registrace = models.DateField('datum registrace do semináře', default=timezone.now) | ||||||
|  | 
 | ||||||
|  | 	# Ulice může být i jen číslo | ||||||
|  | 	ulice = models.CharField('ulice', max_length=256, blank=True, default='') | ||||||
|  | 
 | ||||||
|  | 	mesto = models.CharField('město', max_length=256, blank=True, default='') | ||||||
|  | 
 | ||||||
|  | 	psc = models.CharField('PSČ', max_length=32, blank=True, default='') | ||||||
|  | 
 | ||||||
|  | 	# ISO 3166-1 dvojznakovy kod zeme velkym pismem (CZ, SK) | ||||||
|  | 	# Ekvivalentní s CharField(max_length=2, default='CZ', ...) | ||||||
|  | 	stat = CountryField('stát', default='CZ', | ||||||
|  | 		help_text='ISO 3166-1 kód země velkými písmeny (CZ, SK, ...)') | ||||||
|  | 
 | ||||||
|  | 	poznamka = models.TextField('neveřejná poznámka', blank=True, | ||||||
|  | 		help_text='Neveřejná poznámka k osobě (plain text)') | ||||||
|  | 
 | ||||||
|  | 	foto = ProcessedImageField(verbose_name='Fotografie osoby', | ||||||
|  | 			upload_to='image_osoby/velke/%Y/', null = True, blank = True, | ||||||
|  | 			help_text = 'Vlož fotografii osoby o libovolné velikosti', | ||||||
|  | 			processors=[ | ||||||
|  | 				Transpose(Transpose.AUTO), | ||||||
|  | 				ResizeToFit(500, 500, upscale=False) | ||||||
|  | 			], | ||||||
|  | 			options={'quality': 95}) | ||||||
|  | 	foto_male = ImageSpecField(source='foto', | ||||||
|  | 			processors=[ | ||||||
|  | 				ResizeToFit(200, 200, upscale=False) | ||||||
|  | 			], | ||||||
|  | 			options={'quality': 95}) | ||||||
|  | 
 | ||||||
|  | 	# má OneToOneField nejvýše s: | ||||||
|  | 	# Resitel | ||||||
|  | 	# Prijemce | ||||||
|  | 	# Organizator | ||||||
|  | 
 | ||||||
|  | 	def plne_jmeno(self): | ||||||
|  | 		return '{} {}'.format(self.jmeno, self.prijmeni) | ||||||
|  | 
 | ||||||
|  | 	def inicial_krestni(self): | ||||||
|  | 		jmena = self.jmeno.split() | ||||||
|  | 		return " ".join(['{}.'.format(jmeno[0]) for jmeno in jmena]) | ||||||
|  | 
 | ||||||
|  | 	def __str__(self): | ||||||
|  | 		return self.plne_jmeno() | ||||||
|  | 
 | ||||||
|  | 	# Overridujeme save Osoby, aby když si změní e-mail, aby se projevil i v | ||||||
|  | 	# Userovi (a tak se dal poslat mail s resetem hesla) | ||||||
|  | 	def save(self, *args, **kwargs): | ||||||
|  | 		if self.user is not None: | ||||||
|  | 			u = self.user | ||||||
|  | 			# U svatého tučňáka, prosím ať tohle funguje. | ||||||
|  | 			# (Takhle se kódit asi nemá...) | ||||||
|  | 			u.email = self.email | ||||||
|  | 			u.save() | ||||||
|  | 		super().save() | ||||||
|  | 
 | ||||||
|  | # | ||||||
|  | # Mělo by být částečně vytaženo z Aesopa | ||||||
|  | # viz https://ovvp.mff.cuni.cz/wiki/aesop/export-skol. | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | @reversion.register(ignore_duplicates=True) | ||||||
|  | class Skola(SeminarModelBase): | ||||||
|  | 
 | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_skoly' | ||||||
|  | 		verbose_name = 'Škola' | ||||||
|  | 		verbose_name_plural = 'Školy' | ||||||
|  | 		ordering = ['mesto', 'nazev'] | ||||||
|  | 
 | ||||||
|  | 	# Interní ID | ||||||
|  | 	id = models.AutoField(primary_key = True) | ||||||
|  | 
 | ||||||
|  | 	# Aesopi ID "izo:..." nebo "aesop:..." | ||||||
|  | 	# NULL znamená v exportu do aesopa "ufo" | ||||||
|  | 	aesop_id = models.CharField('Aesop ID', max_length=32, blank=True, default='', | ||||||
|  | 		help_text='Aesopi ID typu "izo:..." nebo "aesop:..."') | ||||||
|  | 
 | ||||||
|  | 	# IZO školy (jen české školy) | ||||||
|  | 	izo = models.CharField('IZO', max_length=32, blank=True, | ||||||
|  | 		help_text='IZO školy (jen české školy)') | ||||||
|  | 
 | ||||||
|  | 	# Celý název školy | ||||||
|  | 	nazev = models.CharField('název', max_length=256, | ||||||
|  | 		help_text='Celý název školy') | ||||||
|  | 
 | ||||||
|  | 	# Zkraceny nazev pro zobrazení ve výsledkovce, volitelné. | ||||||
|  | 	# Není v Aesopovi, musíme vytvářet sami. | ||||||
|  | 	kratky_nazev = models.CharField('zkrácený název', max_length=256, blank=True, | ||||||
|  | 		help_text="Zkrácený název pro zobrazení ve výsledkovce") | ||||||
|  | 
 | ||||||
|  | 	# Ulice může být jen číslo | ||||||
|  | 	ulice = models.CharField('ulice', max_length=256) | ||||||
|  | 
 | ||||||
|  | 	mesto = models.CharField('město', max_length=256) | ||||||
|  | 
 | ||||||
|  | 	psc = models.CharField('PSČ', max_length=32) | ||||||
|  | 
 | ||||||
|  | 	# ISO 3166-1 dvojznakovy kod zeme velkym pismem (CZ, SK) | ||||||
|  | 	# Ekvivalentní s CharField(max_length=2, default='CZ', ...) | ||||||
|  | 	stat = CountryField('stát', default='CZ', | ||||||
|  | 		help_text='ISO 3166-1 kód země velkými písmeny (CZ, SK, ...)') | ||||||
|  | 
 | ||||||
|  | 	# Jaké vzdělání škpla poskytuje? | ||||||
|  | 	je_zs = models.BooleanField('základní stupeň', default=True) | ||||||
|  | 	je_ss = models.BooleanField('střední stupeň', default=True) | ||||||
|  | 
 | ||||||
|  | 	poznamka = models.TextField('neveřejná poznámka', blank=True, | ||||||
|  | 		help_text='Neveřejná poznámka ke škole (plain text)') | ||||||
|  | 	 | ||||||
|  | 	kontaktni_osoba = models.ForeignKey(Osoba, verbose_name='Kontaktní osoba',  | ||||||
|  | 			blank=True, null=True, on_delete=models.SET_NULL) | ||||||
|  | 
 | ||||||
|  | 	def __str__(self): | ||||||
|  | 		return '{}, {}, {}'.format(self.nazev, self.ulice, self.mesto) | ||||||
|  | 
 | ||||||
|  | class Prijemce(SeminarModelBase): | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_prijemce' | ||||||
|  | 		verbose_name = 'příjemce' | ||||||
|  | 		verbose_name_plural = 'příjemce' | ||||||
|  | 	 | ||||||
|  | 
 | ||||||
|  | 	# Interní ID | ||||||
|  | 	id = models.AutoField(primary_key = True) | ||||||
|  | 
 | ||||||
|  | 	poznamka = models.TextField('neveřejná poznámka', blank=True, | ||||||
|  | 		help_text='Neveřejná poznámka k příemci čísel (plain text)') | ||||||
|  | 
 | ||||||
|  | 	osoba = models.OneToOneField(Osoba, verbose_name='komu', blank=False, null=False, | ||||||
|  | 		help_text='Které osobě či na jakou adresu se mají zasílat čísla', | ||||||
|  | 		on_delete=models.CASCADE) | ||||||
|  | 
 | ||||||
|  | 	# FIXME: možná chceme něco jako vazbu na osobu XOR školu a počet kusů k zaslání | ||||||
|  | 	# FIXME: a možná taky posílání na mail a možná taky přes něj chceme posílat i řešitelům | ||||||
|  | 
 | ||||||
|  | 	def __str__(self): | ||||||
|  | 		return self.osoba.plne_jmeno() | ||||||
|  | 	 | ||||||
|  | 
 | ||||||
|  | @reversion.register(ignore_duplicates=True) | ||||||
|  | class Resitel(SeminarModelBase): | ||||||
|  | 
 | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_resitele' | ||||||
|  | 		verbose_name = 'Řešitel' | ||||||
|  | 		verbose_name_plural = 'Řešitelé' | ||||||
|  | 		ordering = ['osoba'] | ||||||
|  | 
 | ||||||
|  | 	# Interní ID | ||||||
|  | 	id = models.AutoField(primary_key = True) | ||||||
|  | 
 | ||||||
|  | 	osoba = models.OneToOneField(Osoba, blank=False, null=False, verbose_name='osoba', | ||||||
|  | 		on_delete=models.PROTECT) | ||||||
|  | 	 | ||||||
|  | 
 | ||||||
|  | 	skola = models.ForeignKey(Skola, blank=True, null=True, verbose_name='škola', | ||||||
|  | 		on_delete=models.SET_NULL) | ||||||
|  | 
 | ||||||
|  | 	# Očekávaný rok maturity a vyřazení z aktivních řešitelů | ||||||
|  | 	rok_maturity = models.IntegerField('rok maturity', blank=True, null=True) | ||||||
|  | 
 | ||||||
|  | 	ZASILAT_DOMU = 'domu' | ||||||
|  | 	ZASILAT_DO_SKOLY = 'do_skoly' | ||||||
|  | 	ZASILAT_NIKAM = 'nikam' | ||||||
|  | 	ZASILAT_CHOICES = [ | ||||||
|  | 		(ZASILAT_DOMU, 'Domů'), | ||||||
|  | 		(ZASILAT_DO_SKOLY, 'Do školy'), | ||||||
|  | 		(ZASILAT_NIKAM, 'Nikam'), | ||||||
|  | 		] | ||||||
|  | 
 | ||||||
|  | 	zasilat = models.CharField('kam zasílat', max_length=32, choices=ZASILAT_CHOICES, blank=False, default=ZASILAT_DOMU) | ||||||
|  | 
 | ||||||
|  | 	zasilat_cislo_emailem = models.BooleanField('zasílat číslo emailem', help_text='True pokud chce řešitel dostávat číslo emailem', default=False) | ||||||
|  | 
 | ||||||
|  | 	poznamka = models.TextField('neveřejná poznámka', blank=True, | ||||||
|  | 		help_text='Neveřejná poznámka k řešiteli (plain text)') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 	def export_row(self): | ||||||
|  | 		"Slovnik pro pouziti v AESOP exportu" | ||||||
|  | 		return { | ||||||
|  | 			'id': self.id, | ||||||
|  | 			'name': self.osoba.jmeno, | ||||||
|  | 			'surname': self.osoba.prijmeni, | ||||||
|  | 			'gender': 'M' if self.osoba.pohlavi_muz else 'F', | ||||||
|  | 			'born': self.osoba.datum_narozeni.isoformat() if self.osoba.datum_narozeni else '', | ||||||
|  | 			'email': self.osoba.email, | ||||||
|  | 			'end-year': self.rok_maturity or '', | ||||||
|  | 
 | ||||||
|  | 			'street': self.osoba.ulice, | ||||||
|  | 			'town': self.osoba.mesto, | ||||||
|  | 			'postcode': self.osoba.psc, | ||||||
|  | 			'country': self.osoba.stat, | ||||||
|  | 
 | ||||||
|  | 			'spam-flag': 'Y' if self.osoba.datum_souhlasu_zasilani else '', | ||||||
|  | 			'spam-date': self.osoba.datum_souhlasu_zasilani.isoformat() if self.osoba.datum_souhlasu_zasilani else '', | ||||||
|  | 
 | ||||||
|  | 			'school': self.skola.aesop_id if self.skola else '', | ||||||
|  | 			'school-name': str(self.skola) if self.skola else 'Skola neni znama', | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 	def rocnik(self, rocnik): | ||||||
|  | 		"""Vrati skolni rocnik resitele pro zadany Rocnik. | ||||||
|  | 				Vraci '' pro neznamy rok maturity resitele, Z* pro ekvivalent ZŠ.""" | ||||||
|  | 		if self.rok_maturity is None: | ||||||
|  | 			return '' | ||||||
|  | 		rozdil = 5 - (self.rok_maturity - rocnik.prvni_rok) | ||||||
|  | 		if rozdil >= 1: | ||||||
|  | 			return str(rozdil) | ||||||
|  | 		else: | ||||||
|  | 			return 'Z' + str(rozdil + 9) | ||||||
|  | 
 | ||||||
|  | 	def vsechny_body(self): | ||||||
|  | 		"Spočítá body odjakživa." | ||||||
|  | 		vsechna_reseni = self.reseni_set.all() | ||||||
|  | 		from .odevzdavatko import Hodnoceni | ||||||
|  | 		vsechna_hodnoceni = Hodnoceni.objects.filter( | ||||||
|  | 			reseni__in=vsechna_reseni) | ||||||
|  | 		return sum(h.body for h in list(vsechna_hodnoceni) if h.body is not None) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 	def get_titul(self, body=None): | ||||||
|  | 		"Vrati titul jako řetězec." | ||||||
|  | 		 | ||||||
|  | 		# Nejprve si zadefinujeme titul | ||||||
|  | 		from enum import Enum | ||||||
|  | 		from functools import total_ordering | ||||||
|  | 		@total_ordering | ||||||
|  | 		class Titul(Enum): | ||||||
|  | 			""" Třída reprezentující možné tituly. Hodnoty jsou dvojice (dolní hranice, stringifikace). """ | ||||||
|  | 			nic =  (0, '') | ||||||
|  | 			bc =   (20, 'Bc.') | ||||||
|  | 			mgr =  (50, 'Mgr.') | ||||||
|  | 			dr =   (100, 'Dr.') | ||||||
|  | 			doc =  (200, 'Doc.') | ||||||
|  | 			prof = (500, 'Prof.') | ||||||
|  | 			akad = (1000, 'Akad.') | ||||||
|  | 
 | ||||||
|  | 			def __lt__(self, other): | ||||||
|  | 				return True if self.value[0] < other.value[0] else False | ||||||
|  | 			def __eq__(self, other): # Měla by být implicitní, ale klidně explicitně. | ||||||
|  | 				return True if self.value[0] == other.value[0] else False | ||||||
|  | 
 | ||||||
|  | 			def __str__(self): | ||||||
|  | 				return self.value[1] | ||||||
|  | 
 | ||||||
|  | 			@classmethod | ||||||
|  | 			def z_bodu(cls, body): | ||||||
|  | 				aktualni = cls.nic | ||||||
|  | 				# TODO: ověřit, že to funguje | ||||||
|  | 				for titul in cls: # Kdyžtak použít __members__.items() | ||||||
|  | 					if titul.value[0] <= body: | ||||||
|  | 						aktualni = titul | ||||||
|  | 					else: | ||||||
|  | 						break | ||||||
|  | 				return aktualni | ||||||
|  | 
 | ||||||
|  | 		# Hledáme body v databázi | ||||||
|  | 		# V listopadu 2020 jsme se na filosofické schůzce shodli o změně hranic titulů: | ||||||
|  | 		#  - body z 25. ročníku a dříve byly shledány dvakrát hodnotnějšími | ||||||
|  | 		#  - proto se započítávají dvojnásobně a byly posunuté hranice titulů | ||||||
|  | 		#  - staré tituly se ale nemají odebrat, pokud řešitel v t.č. minulém (26.) ročníku měl titul, má ho mít pořád. | ||||||
|  | 		from .odevzdavatko import Hodnoceni | ||||||
|  | 		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 if h.body is not None) | ||||||
|  | 
 | ||||||
|  | 		stare_body = body_z_hodnoceni(hodnoceni_do_25_rocniku) | ||||||
|  | 		if body is None: | ||||||
|  | 			nove_body = body_z_hodnoceni(novejsi_hodnoceni) | ||||||
|  | 		else: | ||||||
|  | 			# 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 | ||||||
|  | 
 | ||||||
|  | 		from .odevzdavatko import Hodnoceni | ||||||
|  | 		hodnoceni_do_26_rocniku = Hodnoceni.objects.filter(cislo_body__rocnik__rocnik__lte=26,reseni__in=self.reseni_set.all()) | ||||||
|  | 		novejsi_body = body_z_hodnoceni( | ||||||
|  | 			Hodnoceni.objects.filter(reseni__in=self.reseni_set.all()) | ||||||
|  | 			.difference(hodnoceni_do_26_rocniku) | ||||||
|  | 			) | ||||||
|  | 		starsi_body = body_z_hodnoceni(hodnoceni_do_26_rocniku) | ||||||
|  | 		if body is not None: | ||||||
|  | 			# Ještě z toho vybereme ty správně staré body | ||||||
|  | 			novejsi_body = max(0, body - starsi_body) | ||||||
|  | 			starsi_body = min(starsi_body, body) | ||||||
|  | 
 | ||||||
|  | 		# Titul pro 26. ročník | ||||||
|  | 		stary_titul = titul_do_26_rocniku(starsi_body) | ||||||
|  | 		# Titul podle aktuálních pravidel | ||||||
|  | 		novy_titul = Titul.z_bodu(logicke_body) | ||||||
|  | 
 | ||||||
|  | 		if novejsi_body == 0: | ||||||
|  | 			# Žádné nové body -- titul podle starých pravidel | ||||||
|  | 			return str(stary_titul) | ||||||
|  | 		return str(max(novy_titul, stary_titul)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 	def __str__(self): | ||||||
|  | 		return self.osoba.plne_jmeno() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @reversion.register(ignore_duplicates=True) | ||||||
|  | class Organizator(SeminarModelBase): | ||||||
|  | 	osoba = models.OneToOneField(Osoba, verbose_name='osoba', related_name='org', | ||||||
|  | 		help_text='osobní údaje organizátora', null=False, blank=False, | ||||||
|  | 		on_delete=models.PROTECT) | ||||||
|  | 
 | ||||||
|  | 	vytvoreno = models.DateTimeField( | ||||||
|  | 		'Vytvořeno', | ||||||
|  | 		default=timezone.now, | ||||||
|  | 		blank=True, | ||||||
|  | 		editable=False | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	organizuje_od = models.DateTimeField('Organizuje od', blank=True, null=True) | ||||||
|  | 	 | ||||||
|  | 	organizuje_do = models.DateTimeField('Organizuje do', blank=True, null=True) | ||||||
|  | 
 | ||||||
|  | 	studuje = models.CharField('Studium aj.', max_length = 256, | ||||||
|  | 			null = True, blank = True, | ||||||
|  | 			help_text="Např. 'Studuje Obecnou fyziku (Bc.), 3. ročník', " | ||||||
|  | 			"'Vystudovala Diskrétní modely a algoritmy (Mgr.)' nebo " | ||||||
|  | 			"'Přednáší na MFF'") | ||||||
|  | 
 | ||||||
|  | 	strucny_popis_organizatora = models.TextField('Stručný popis organizátora', | ||||||
|  | 			null = True, blank = True) | ||||||
|  | 
 | ||||||
|  | 	skola = models.CharField('Škola, kterou studuje', max_length = 256, null=True, blank=True, | ||||||
|  | 		help_text="Škola, např. MFF, VŠCHT, VUT, ... prostě aby se nemuselo psát do studuje" | ||||||
|  | 		"školu, ale jen obor, možnost zobrazit zvlášť") | ||||||
|  | 
 | ||||||
|  | 	def clean(self): | ||||||
|  | 		if self.organizuje_od and self.organizuje_do and (self.organizuje_od > self.organizuje_do): | ||||||
|  | 			raise ValidationError("Organizátor nemůže skončit s organizováním dříve než začal!") | ||||||
|  | 		super().clean() | ||||||
|  | 
 | ||||||
|  | 	def __str__(self): | ||||||
|  | 		if self.osoba.prezdivka: | ||||||
|  | 			return "{} '{}' {}".format(self.osoba.jmeno, | ||||||
|  | 				self.osoba.prezdivka, | ||||||
|  | 				self.osoba.prijmeni) | ||||||
|  | 		else: | ||||||
|  | 			return "{} {}".format(self.osoba.jmeno, self.osoba.prijmeni) | ||||||
|  | 
 | ||||||
|  | 	class Meta: | ||||||
|  | 		verbose_name = 'Organizátor' | ||||||
|  | 		verbose_name_plural = 'Organizátoři' | ||||||
|  | 		# Řadí aktivní orgy na začátek, pod tím v pořadí od nejstarších neaktivní orgy. | ||||||
|  | 		# TODO: Chtěl bych spíš mít nejstarší orgy dole. | ||||||
|  | 		# TODO: Zohledňovat přezdívky? | ||||||
|  | 		# TODO: Sjednotit s tím, jak se řadí organizátoři v seznau orgů na webu | ||||||
|  | 		ordering = ['-organizuje_do', 'osoba__jmeno', 'osoba__prijmeni'] | ||||||
							
								
								
									
										67
									
								
								seminar/models/pomocne.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								seminar/models/pomocne.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,67 @@ | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | import logging | ||||||
|  | from django.db import models | ||||||
|  | 
 | ||||||
|  | from .base import SeminarModelBase | ||||||
|  | 
 | ||||||
|  | logger = logging.getLogger(__name__) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Text(SeminarModelBase): | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_texty' | ||||||
|  | 		verbose_name = 'text' | ||||||
|  | 		verbose_name_plural = 'texty' | ||||||
|  | 
 | ||||||
|  | 	na_web = models.TextField( | ||||||
|  | 		'text na web', blank=True, | ||||||
|  | 		help_text='Text ke zveřejnění na webu') | ||||||
|  | 
 | ||||||
|  | 	do_cisla = models.TextField( | ||||||
|  | 		'text do čísla', blank=True, | ||||||
|  | 		help_text='Text ke zveřejnění v čísle') | ||||||
|  | 
 | ||||||
|  | 	# má OneToOneField s: | ||||||
|  | 	# Reseni (je u něj jako reseni_cele) | ||||||
|  | 
 | ||||||
|  | 	# obrázky mají návaznost opačným směrem (vazba z druhé strany) | ||||||
|  | 
 | ||||||
|  | 	def save(self, *args, **kwargs): | ||||||
|  | 		super().save(*args, **kwargs) | ||||||
|  | 		# *Node.save() aktualizuje název *Nodu. | ||||||
|  | 		for tn in self.textnode_set.all(): | ||||||
|  | 			tn.save() | ||||||
|  | 
 | ||||||
|  | 	def __str__(self): | ||||||
|  | 		return str(self.na_web)[:20] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Obrazek(SeminarModelBase): | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_obrazky' | ||||||
|  | 		verbose_name = 'obrázek' | ||||||
|  | 		verbose_name_plural = 'obrázky' | ||||||
|  | 
 | ||||||
|  | 	# Interní ID | ||||||
|  | 	id = models.AutoField(primary_key=True) | ||||||
|  | 
 | ||||||
|  | 	na_web = models.ImageField( | ||||||
|  | 		'obrázek na web', upload_to='obrazky/%Y/%m/%d/', | ||||||
|  | 		null=True, blank=True) | ||||||
|  | 
 | ||||||
|  | 	text = models.ForeignKey( | ||||||
|  | 		Text, verbose_name='text', | ||||||
|  | 		help_text='text, ve kterém se obrázek vyskytuje', | ||||||
|  | 		null=False, blank=False, on_delete=models.CASCADE) | ||||||
|  | 
 | ||||||
|  | 	do_cisla_barevny = models.FileField( | ||||||
|  | 		'barevný obrázek do čísla', | ||||||
|  | 		help_text='Barevná verze obrázku do čísla', | ||||||
|  | 		upload_to='obrazky/%Y/%m/%d/', blank=True, null=True) | ||||||
|  | 
 | ||||||
|  | 	do_cisla_cernobily = models.FileField( | ||||||
|  | 		'černobílý obrázek do čísla', | ||||||
|  | 		help_text='Černobílá verze obrázku do čísla', | ||||||
|  | 		upload_to='obrazky/%Y/%m/%d/', blank=True, null=True) | ||||||
|  | 
 | ||||||
|  | 	# TODO placement hint - chci ho tady / pred textem / za textem | ||||||
							
								
								
									
										214
									
								
								seminar/models/soustredeni.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								seminar/models/soustredeni.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,214 @@ | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | import logging | ||||||
|  | import os | ||||||
|  | 
 | ||||||
|  | from django.db import models | ||||||
|  | from django.urls import reverse | ||||||
|  | from reversion import revisions as reversion | ||||||
|  | 
 | ||||||
|  | from django.conf import settings | ||||||
|  | 
 | ||||||
|  | from . import personalni as pm | ||||||
|  | 
 | ||||||
|  | from .base import SeminarModelBase | ||||||
|  | from seminar.models import tvorba as am | ||||||
|  | 
 | ||||||
|  | logger = logging.getLogger(__name__) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @reversion.register(ignore_duplicates=True) | ||||||
|  | class Soustredeni(SeminarModelBase): | ||||||
|  | 
 | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_soustredeni' | ||||||
|  | 		verbose_name = 'Soustředění' | ||||||
|  | 		verbose_name_plural = 'Soustředění' | ||||||
|  | 		ordering = ['-rocnik__rocnik', '-datum_zacatku'] | ||||||
|  | 
 | ||||||
|  | 	# Interní ID | ||||||
|  | 	id = models.AutoField(primary_key = True) | ||||||
|  | 
 | ||||||
|  | 	rocnik = models.ForeignKey(am.Rocnik, verbose_name='ročník', related_name='soustredeni', | ||||||
|  | 		on_delete=models.PROTECT) | ||||||
|  | 
 | ||||||
|  | 	datum_zacatku = models.DateField('datum začátku', blank=True, null=True, | ||||||
|  | 		help_text='První den soustředění') | ||||||
|  | 
 | ||||||
|  | 	datum_konce = models.DateField('datum konce', blank=True, null=True, | ||||||
|  | 		help_text='Poslední den soustředění') | ||||||
|  | 
 | ||||||
|  | 	verejne_db = models.BooleanField('soustředění zveřejněno', db_column='verejne', default=False) | ||||||
|  | 
 | ||||||
|  | 	misto = models.CharField('místo soustředění', max_length=256, blank=True, default='', | ||||||
|  | 		help_text='Místo (název obce, volitelně též objektu') | ||||||
|  | 
 | ||||||
|  | 	ucastnici = models.ManyToManyField(pm.Resitel, verbose_name='účastníci soustředění', | ||||||
|  | 		help_text='Seznam účastníků soustředění', through='Soustredeni_Ucastnici') | ||||||
|  | 
 | ||||||
|  | 	organizatori = models.ManyToManyField(pm.Organizator, | ||||||
|  | 			verbose_name='Organizátoři soustředění', | ||||||
|  | 			help_text='Seznam organizátorů soustředění', | ||||||
|  | 			through='Soustredeni_Organizatori') | ||||||
|  | 
 | ||||||
|  | 	text = models.TextField('text k soustředění (HTML)', blank=True, default='') | ||||||
|  | 
 | ||||||
|  | 	TYP_JARNI = 'jarni' | ||||||
|  | 	TYP_PODZIMNI = 'podzimni' | ||||||
|  | 	TYP_VIKEND = 'vikend' | ||||||
|  | 	TYP_CHOICES = [ | ||||||
|  | 		(TYP_JARNI, 'Jarní soustředění'), | ||||||
|  | 		(TYP_PODZIMNI, 'Podzimní soustředění'), | ||||||
|  | 		(TYP_VIKEND, 'Víkendový sraz'), | ||||||
|  | 		] | ||||||
|  | 	typ = models.CharField('typ akce', max_length=16, choices=TYP_CHOICES, blank=False, default=TYP_PODZIMNI) | ||||||
|  | 
 | ||||||
|  | 	exportovat = models.BooleanField('export do AESOPa', db_column='exportovat', default=False, | ||||||
|  | 			help_text='Exportuje se jen podle tohoto flagu (ne veřejnosti)') | ||||||
|  | 
 | ||||||
|  | 	def __str__(self): | ||||||
|  | 		return '{} ({})'.format(self.misto, self.datum_zacatku) | ||||||
|  | 
 | ||||||
|  | 	def verejne(self): | ||||||
|  | 		return self.verejne_db | ||||||
|  | 	verejne.boolean = True | ||||||
|  | 
 | ||||||
|  | 	def verejne_url(self): | ||||||
|  | 		#return reverse('seminar_soustredeni', kwargs={'pk': self.id}) | ||||||
|  | 		return reverse('seminar_seznam_soustredeni') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @reversion.register(ignore_duplicates=True) | ||||||
|  | class Soustredeni_Ucastnici(SeminarModelBase): | ||||||
|  | # zmena dedicnosti z models.Model na SeminarModelBase, potencialni vznik bugu | ||||||
|  | 
 | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_soustredeni_ucastnici' | ||||||
|  | 		verbose_name = 'Účast na soustředění' | ||||||
|  | 		verbose_name_plural = 'Účasti na soustředění' | ||||||
|  | 		ordering = ['soustredeni', 'resitel'] | ||||||
|  | 
 | ||||||
|  | 	# Interní ID | ||||||
|  | 	id = models.AutoField(primary_key = True) | ||||||
|  | 
 | ||||||
|  | 	resitel = models.ForeignKey(pm.Resitel, verbose_name='řešitel', on_delete=models.PROTECT) | ||||||
|  | 
 | ||||||
|  | 	soustredeni = models.ForeignKey(Soustredeni, verbose_name='soustředění', | ||||||
|  | 		on_delete=models.PROTECT) | ||||||
|  | 
 | ||||||
|  | 	poznamka = models.TextField('neveřejná poznámka', blank=True, | ||||||
|  | 		help_text='Neveřejná poznámka k účasti (plain text)') | ||||||
|  | 
 | ||||||
|  | 	def __str__(self): | ||||||
|  | 		return '{} na {}'.format(self.resitel, self.soustredeni) | ||||||
|  | 		# NOTE: Poteciální DB HOG bez select_related | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @reversion.register(ignore_duplicates=True) | ||||||
|  | class Soustredeni_Organizatori(SeminarModelBase): | ||||||
|  | # zmena dedicnosti z models.Model na SeminarModelBase, potencialni vznik bugu | ||||||
|  | 
 | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_soustredeni_organizatori' | ||||||
|  | 		verbose_name = 'Účast organizátorů na soustředění' | ||||||
|  | 		verbose_name_plural = 'Účasti organizátorů na soustředění' | ||||||
|  | 		ordering = ['soustredeni', 'organizator'] | ||||||
|  | 
 | ||||||
|  | 	# Interní ID | ||||||
|  | 	id = models.AutoField(primary_key = True) | ||||||
|  | 
 | ||||||
|  | 	organizator = models.ForeignKey(pm.Organizator, verbose_name='organizátor', | ||||||
|  | 		on_delete=models.PROTECT) | ||||||
|  | 
 | ||||||
|  | 	soustredeni = models.ForeignKey(Soustredeni, verbose_name='soustředění', | ||||||
|  | 		on_delete=models.PROTECT) | ||||||
|  | 
 | ||||||
|  | 	poznamka = models.TextField('neveřejná poznámka', blank=True, | ||||||
|  | 		help_text='Neveřejná poznámka k účasti organizátora (plain text)') | ||||||
|  | 
 | ||||||
|  | 	def __str__(self): | ||||||
|  | 		return '{} na {}'.format(self.organizator, self.soustredeni) | ||||||
|  | 		# NOTE: Poteciální DB HOG bez select_related | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # FIXME cycle import | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Django neumí jednoduše serializovat partial nebo třídu s __call__ | ||||||
|  | # (https://docs.djangoproject.com/en/1.8/topics/migrations/), | ||||||
|  | # neprojdou pak migrace. Takže rozlišení funkcí generujících názvy souboru | ||||||
|  | # podle adresáře řešíme takto. | ||||||
|  | 
 | ||||||
|  | ## | ||||||
|  | def generate_filename_konfera(self, filename): | ||||||
|  | 	return os.path.join( | ||||||
|  | 		settings.SEMINAR_KONFERY_DIR, | ||||||
|  | 		am.aux_generate_filename(self, filename) | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | ## | ||||||
|  | 
 | ||||||
|  | @reversion.register(ignore_duplicates=True) | ||||||
|  | class Konfera(am.Problem): | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_konfera' | ||||||
|  | 		verbose_name = 'Konfera' | ||||||
|  | 		verbose_name_plural = 'Konfery' | ||||||
|  | 
 | ||||||
|  | 	anotace = models.TextField('anotace', blank=True, | ||||||
|  | 							   help_text='Popis, o čem bude konfera.') | ||||||
|  | 
 | ||||||
|  | 	abstrakt = models.TextField('abstrakt', blank=True, | ||||||
|  | 								help_text='Abstrakt konfery tak, jak byl uveden ve sborníku') | ||||||
|  | 
 | ||||||
|  | 	# FIXME: Umíme omezit jen na účastníky daného soustřeďka? | ||||||
|  | 	ucastnici = models.ManyToManyField(pm.Resitel, verbose_name='účastníci konfery', | ||||||
|  | 									   help_text='Seznam účastníků konfery', through='Konfery_Ucastnici') | ||||||
|  | 
 | ||||||
|  | 	soustredeni = models.ForeignKey(Soustredeni, verbose_name='soustředění', | ||||||
|  | 									related_name='konfery', on_delete = models.SET_NULL, null=True) | ||||||
|  | 
 | ||||||
|  | 	TYP_VELETRH = 'veletrh' | ||||||
|  | 	TYP_PREZENTACE = 'prezentace' | ||||||
|  | 	TYP_CHOICES = [ | ||||||
|  | 		(TYP_VELETRH, 'Veletrh (postery)'), | ||||||
|  | 		(TYP_PREZENTACE, 'Prezentace (přednáška)'), | ||||||
|  | 	] | ||||||
|  | 	typ_prezentace = models.CharField('typ prezentace', max_length=16, choices=TYP_CHOICES, | ||||||
|  | 									  blank=False, default=TYP_VELETRH) | ||||||
|  | 
 | ||||||
|  | 	prezentace = models.FileField('prezentace',help_text = 'Prezentace nebo fotka posteru', | ||||||
|  | 								  upload_to = generate_filename_konfera, blank=True) | ||||||
|  | 
 | ||||||
|  | 	materialy = models.FileField('materialy', | ||||||
|  | 								 help_text = 'Další materiály ke konfeře zabalené do jednoho souboru', | ||||||
|  | 								 upload_to = generate_filename_konfera, blank=True) | ||||||
|  | 
 | ||||||
|  | 	def __str__(self): | ||||||
|  | 		return "{}: ({})".format(self.nazev, self.soustredeni) | ||||||
|  | 
 | ||||||
|  | 	def cislo_node(self): | ||||||
|  | 		return None | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @reversion.register(ignore_duplicates=True) | ||||||
|  | class Konfery_Ucastnici(models.Model): | ||||||
|  | 
 | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_konfery_ucastnici' | ||||||
|  | 		verbose_name = 'Účast na konfeře' | ||||||
|  | 		verbose_name_plural = 'Účasti na konfeře' | ||||||
|  | 		ordering = ['konfera', 'resitel'] | ||||||
|  | 
 | ||||||
|  | 	# Interní ID | ||||||
|  | 	id = models.AutoField(primary_key = True) | ||||||
|  | 
 | ||||||
|  | 	resitel = models.ForeignKey(pm.Resitel, verbose_name='řešitel', on_delete=models.PROTECT) | ||||||
|  | 
 | ||||||
|  | 	konfera = models.ForeignKey(Konfera, verbose_name='konfera', on_delete=models.CASCADE) | ||||||
|  | 
 | ||||||
|  | 	poznamka = models.TextField('neveřejná poznámka', blank=True, | ||||||
|  | 		help_text='Neveřejná poznámka k účasti (plain text)') | ||||||
|  | 
 | ||||||
|  | 	def __str__(self): | ||||||
|  | 		return '{} na {}'.format(self.resitel, self.konfera) | ||||||
|  | 		# NOTE: Poteciální DB HOG bez select_related | ||||||
							
								
								
									
										266
									
								
								seminar/models/treenode.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										266
									
								
								seminar/models/treenode.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,266 @@ | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | import logging | ||||||
|  | 
 | ||||||
|  | from django.db import models | ||||||
|  | from django.urls import reverse | ||||||
|  | from django.contrib.contenttypes.models import ContentType | ||||||
|  | 
 | ||||||
|  | from unidecode import unidecode # Používám pro získání ID odkazu (ještě je to někde po někom zakomentované) | ||||||
|  | 
 | ||||||
|  | from polymorphic.models import PolymorphicModel | ||||||
|  | 
 | ||||||
|  | from . import personalni as pm | ||||||
|  | 
 | ||||||
|  | from .pomocne import Text | ||||||
|  | 
 | ||||||
|  | logger = logging.getLogger(__name__) | ||||||
|  | 
 | ||||||
|  | from seminar.models import tvorba as am | ||||||
|  | 
 | ||||||
|  | class TreeNode(PolymorphicModel): | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = "seminar_nodes_treenode" | ||||||
|  | 		verbose_name = "TreeNode" | ||||||
|  | 		verbose_name_plural = "TreeNody" | ||||||
|  | 
 | ||||||
|  | 	# TODO: Nechceme radši jako root vyžadovat přímo RocnikNode? | ||||||
|  | 	root = models.ForeignKey('TreeNode', | ||||||
|  | 		related_name="potomci_set", | ||||||
|  | 		null = True, | ||||||
|  | 		blank = False, | ||||||
|  | 		on_delete = models.SET_NULL, # Vrcholy s null kořenem jsou sirotci bez ročníku  | ||||||
|  | 		verbose_name="kořen stromu") | ||||||
|  | 	first_child = models.OneToOneField('TreeNode', | ||||||
|  | 		related_name='father_of_first', | ||||||
|  | 		null = True, | ||||||
|  | 		blank = True, | ||||||
|  | 		on_delete=models.SET_NULL, | ||||||
|  | 		verbose_name="první potomek") | ||||||
|  | 	succ = models.OneToOneField('TreeNode', | ||||||
|  | 		related_name="prev", | ||||||
|  | 		null = True, | ||||||
|  | 		blank = True, | ||||||
|  | 		on_delete=models.SET_NULL, | ||||||
|  | 		verbose_name="další element na stejné úrovni") | ||||||
|  | 	nazev = models.TextField("název tohoto node", | ||||||
|  | 		help_text = "Tento název se zobrazuje v nabídkách pro výběr vhodného TreeNode", | ||||||
|  | 		blank=False,  | ||||||
|  | 		null=True) # Nezveřejnitelný název na stránky - pouze do adminu | ||||||
|  | 	zajimave = models.BooleanField(default = False, | ||||||
|  | 		verbose_name = "Zajímavé", | ||||||
|  | 		help_text = "Zobrazí se daná věc na rozcestníku témátek") | ||||||
|  | 	srolovatelne = models.BooleanField(null = True, blank = True, | ||||||
|  | 		verbose_name = "Srolovatelné", | ||||||
|  | 		help_text = "Bude na stránce témátka možnost tuto položku skrýt") | ||||||
|  | 
 | ||||||
|  | 	def getOdkazStr(self): # String na rozcestník | ||||||
|  | 		return self.first_child.getOdkazStr() | ||||||
|  | 
 | ||||||
|  | 	def getOdkaz(self): # ID HTML tagu, na který se bude scrollovat #{{self.getOdkaz}} | ||||||
|  | 	# Jsem si vědom, že tu potenciálně vznikají kolize. | ||||||
|  | 	# Přijdou mi natolik nepravděpodobné, že je neřeším | ||||||
|  | 	# Chtěl jsem ale hezké odkazy | ||||||
|  | 		string = unidecode(self.getOdkazStr()) | ||||||
|  | 		returnVal = "" | ||||||
|  | 		i = 0	 | ||||||
|  | 		while len(returnVal) < 16: # Max 15 znaků | ||||||
|  | 			if i == len(string): | ||||||
|  | 				break | ||||||
|  | 			if string[i] == " ": | ||||||
|  | 				returnVal += "-" | ||||||
|  | 			if string[i].isalnum(): | ||||||
|  | 				returnVal += string[i].lower() | ||||||
|  | 			i += 1 | ||||||
|  | 		return returnVal | ||||||
|  | 
 | ||||||
|  | 	def __str__(self): | ||||||
|  | 		if self.nazev: | ||||||
|  | 			return self.nazev | ||||||
|  | 		else: | ||||||
|  | 			#TODO: logování | ||||||
|  | 			return "Nepojmenovaný Treenode" | ||||||
|  | 	 | ||||||
|  | 	def save(self, *args, **kwargs): | ||||||
|  | 		self.aktualizuj_nazev() | ||||||
|  | 		super().save(*args, **kwargs) | ||||||
|  | 
 | ||||||
|  | 	def aktualizuj_nazev(self): | ||||||
|  | 		raise NotImplementedError("Pokus o aktualizaci názvu obecného TreeNode místo konkrétní instance") | ||||||
|  | 
 | ||||||
|  | 	def get_admin_url(self): | ||||||
|  | 		content_type = ContentType.objects.get_for_model(self.__class__) | ||||||
|  | 		return reverse("admin:%s_%s_change" % (content_type.app_label, content_type.model), args=(self.id,)) | ||||||
|  | 
 | ||||||
|  | class RocnikNode(TreeNode): | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_nodes_rocnik' | ||||||
|  | 		verbose_name = 'Ročník (Node)' | ||||||
|  | 		verbose_name_plural = 'Ročníky (Node)' | ||||||
|  | 	rocnik = models.OneToOneField(am.Rocnik, | ||||||
|  | 		on_delete = models.PROTECT, # Pokud chci mazat ročník, musím si Node pořešit ručně | ||||||
|  | 		verbose_name = "ročník") | ||||||
|  | 
 | ||||||
|  | 	def aktualizuj_nazev(self): | ||||||
|  | 		self.nazev = "RocnikNode: "+str(self.rocnik) | ||||||
|  | 
 | ||||||
|  | class CisloNode(TreeNode): | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_nodes_cislo' | ||||||
|  | 		verbose_name = 'Číslo (Node)' | ||||||
|  | 		verbose_name_plural = 'Čísla (Node)' | ||||||
|  | 	cislo = models.OneToOneField(am.Cislo, | ||||||
|  | 		on_delete = models.PROTECT, # Pokud chci mazat číslo, musím si Node pořešit ručně | ||||||
|  | 		verbose_name = "číslo") | ||||||
|  | 
 | ||||||
|  | 	def aktualizuj_nazev(self): | ||||||
|  | 		self.nazev = "CisloNode: "+str(self.cislo) | ||||||
|  | 
 | ||||||
|  | 	def getOdkazStr(self): | ||||||
|  | 		return "Číslo " + str(self.cislo) | ||||||
|  | 
 | ||||||
|  | class MezicisloNode(TreeNode): | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_nodes_mezicislo' | ||||||
|  | 		verbose_name = 'Mezičíslo (Node)' | ||||||
|  | 		verbose_name_plural = 'Mezičísla (Node)' | ||||||
|  | 
 | ||||||
|  | 	# TODO: Využít TreeLib | ||||||
|  | 	def aktualizuj_nazev(self): | ||||||
|  | 		from treenode.treelib import safe_pred | ||||||
|  | 		if safe_pred(self) is not None: | ||||||
|  | 			if (self.prev.get_real_instance_class() != CisloNode and | ||||||
|  | 				self.prev.get_real_instance_class() != MezicisloNode): | ||||||
|  | 					raise ValueError("Předchůdce není číslo!") | ||||||
|  | 			posledni = self.prev.cislo | ||||||
|  | 			self.nazev = "MezicisloNode: Mezičíslo po čísle"+str(posledni) | ||||||
|  | 		elif self.root: | ||||||
|  | 			if self.root.get_real_instance_class() != RocnikNode: | ||||||
|  | 				raise ValueError("Kořen stromu není ročník!") | ||||||
|  | 			rocnik = self.root.rocnik | ||||||
|  | 			self.nazev = "MezicisloNode: První mezičíslo ročníku "+str(rocnik) | ||||||
|  | 		else: | ||||||
|  | 			print("!!!!! Nějaké neidentifikované mezičíslo !!!!!") | ||||||
|  | 			self.nazev = "MezicisloNode: Neidentifikovatelné mezičíslo!" | ||||||
|  | 	def getOdkazStr(self): | ||||||
|  | 		return "Obsah dostupný pouze na webu" | ||||||
|  | 
 | ||||||
|  | class TemaVCisleNode(TreeNode): | ||||||
|  | 	""" Obsahuje příspěvky k tématu v daném čísle """ | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_nodes_temavcisle' | ||||||
|  | 		verbose_name = 'Téma v čísle (Node)' | ||||||
|  | 		verbose_name_plural = 'Témata v čísle (Node)' | ||||||
|  | 	tema = models.ForeignKey(am.Tema, | ||||||
|  | 		on_delete=models.PROTECT, # Pokud chci mazat téma, musím si Node pořešit ručně | ||||||
|  | 		verbose_name = "téma v čísle") | ||||||
|  | 
 | ||||||
|  | 	def aktualizuj_nazev(self): | ||||||
|  | 		self.nazev = "TemaVCisleNode: "+str(self.tema) | ||||||
|  | 
 | ||||||
|  | 	def getOdkazStr(self): | ||||||
|  | 		return str(self.tema) | ||||||
|  | 
 | ||||||
|  | class OrgTextNode(TreeNode): | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_nodes_orgtextnode' | ||||||
|  | 		verbose_name = 'Organizátorský článek (Node)' | ||||||
|  | 		verbose_name_plural = 'Organizátorské články (Node)' | ||||||
|  | 	 | ||||||
|  | 	organizator = models.ForeignKey(pm.Organizator, | ||||||
|  | 		null=False, | ||||||
|  | 		blank=False, | ||||||
|  | 		on_delete=models.DO_NOTHING, | ||||||
|  | 		verbose_name="Organizátor", | ||||||
|  | 		) | ||||||
|  | 	org_verejny = models.BooleanField(default = True, | ||||||
|  | 		verbose_name = "Org je veřejný?", | ||||||
|  | 		help_text = "Pokud ano, bude org pod článkem podepsaný", | ||||||
|  | 		null=False, | ||||||
|  | 		) | ||||||
|  | 
 | ||||||
|  | 	def aktualizuj_nazev(self): | ||||||
|  | 		return f"OrgTextNode začínající následujícim: {self.first_child.nazev}" | ||||||
|  | 
 | ||||||
|  | 	# FIXME!!! | ||||||
|  | 	#def getOdkazStr(self): | ||||||
|  | 	#	return str(self.clanek) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class UlohaZadaniNode(TreeNode): | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_nodes_uloha_zadani' | ||||||
|  | 		verbose_name = 'Zadání úlohy (Node)' | ||||||
|  | 		verbose_name_plural = 'Zadání úloh (Node)' | ||||||
|  | 	uloha = models.OneToOneField(am.Uloha, | ||||||
|  | 		on_delete=models.PROTECT, # Pokud chci mazat téma, musím si Node pořešit ručně | ||||||
|  | 		verbose_name = "úloha", | ||||||
|  | 		null=True, | ||||||
|  | 		blank=False) | ||||||
|  | 
 | ||||||
|  | 	def aktualizuj_nazev(self): | ||||||
|  | 		self.nazev = "UlohaZadaniNode: "+str(self.uloha) | ||||||
|  | 
 | ||||||
|  | 	def getOdkazStr(self): | ||||||
|  | 		return str(self.uloha) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class PohadkaNode(TreeNode): | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_nodes_pohadka' | ||||||
|  | 		verbose_name = 'Pohádka (Node)' | ||||||
|  | 		verbose_name_plural = 'Pohádky (Node)' | ||||||
|  | 	pohadka = models.OneToOneField(am.Pohadka, | ||||||
|  | 		on_delete=models.PROTECT, # Pokud chci mazat pohádku, musím si Node pořešit ručně | ||||||
|  | 		verbose_name = "pohádka", | ||||||
|  | 		) | ||||||
|  | 
 | ||||||
|  | 	def aktualizuj_nazev(self): | ||||||
|  | 		self.nazev = "PohadkaNode: "+str(self.pohadka) | ||||||
|  | 
 | ||||||
|  | class UlohaVzorakNode(TreeNode): | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_nodes_uloha_vzorak' | ||||||
|  | 		verbose_name = 'Vzorák úlohy (Node)' | ||||||
|  | 		verbose_name_plural = 'Vzoráky úloh (Node)' | ||||||
|  | 	uloha = models.OneToOneField(am.Uloha, | ||||||
|  | 		on_delete=models.PROTECT, # Pokud chci mazat téma, musím si Node pořešit ručně | ||||||
|  | 		verbose_name = "úloha", | ||||||
|  | 		null=True, | ||||||
|  | 		blank=False) | ||||||
|  | 
 | ||||||
|  | 	def aktualizuj_nazev(self): | ||||||
|  | 		self.nazev = "UlohaVzorakNode: "+str(self.uloha) | ||||||
|  | 
 | ||||||
|  | 	def getOdkazStr(self): | ||||||
|  | 		return str(self.uloha) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class TextNode(TreeNode): | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_nodes_obsah' | ||||||
|  | 		verbose_name = 'Text (Node)' | ||||||
|  | 		verbose_name_plural = 'Text (Node)' | ||||||
|  | 	text = models.ForeignKey(Text, | ||||||
|  | 		on_delete=models.CASCADE, | ||||||
|  | 		verbose_name = 'text') | ||||||
|  | 	 | ||||||
|  | 	def aktualizuj_nazev(self): | ||||||
|  | 		self.nazev = "TextNode: "+str(self.text) | ||||||
|  | 
 | ||||||
|  | 	def getOdkazStr(self): | ||||||
|  | 		return str(self.text) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class CastNode(TreeNode): | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_nodes_cast' | ||||||
|  | 		verbose_name = 'Část (Node)' | ||||||
|  | 		verbose_name_plural = 'Části (Node)' | ||||||
|  | 	 | ||||||
|  | 	nadpis = models.CharField('Nadpis', max_length=100, help_text = 'Nadpis podvěšené části obsahu') | ||||||
|  | 
 | ||||||
|  | 	def aktualizuj_nazev(self): | ||||||
|  | 		self.nazev = "CastNode: "+str(self.nadpis) | ||||||
|  | 
 | ||||||
|  | 	def getOdkazStr(self): | ||||||
|  | 		return str(self.nadpis) | ||||||
							
								
								
									
										650
									
								
								seminar/models/tvorba.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										650
									
								
								seminar/models/tvorba.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,650 @@ | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | import os | ||||||
|  | import subprocess | ||||||
|  | import pathlib | ||||||
|  | import tempfile | ||||||
|  | import logging | ||||||
|  | 
 | ||||||
|  | from django.contrib.sites.shortcuts import get_current_site | ||||||
|  | from django.db import models | ||||||
|  | from django.utils import timezone | ||||||
|  | from django.conf import settings | ||||||
|  | from django.urls import reverse | ||||||
|  | from django.core.cache import cache | ||||||
|  | from django.core.exceptions import ObjectDoesNotExist, ValidationError | ||||||
|  | from django.utils.text import get_valid_filename | ||||||
|  | from django.utils.functional import cached_property | ||||||
|  | 
 | ||||||
|  | from solo.models import SingletonModel | ||||||
|  | from taggit.managers import TaggableManager | ||||||
|  | 
 | ||||||
|  | from reversion import revisions as reversion | ||||||
|  | 
 | ||||||
|  | from seminar.utils import roman | ||||||
|  | from seminar.utils import hlavni_problem | ||||||
|  | from treenode import treelib | ||||||
|  | 
 | ||||||
|  | from unidecode import unidecode # Používám pro získání ID odkazu (ještě je to někde po někom zakomentované) | ||||||
|  | 
 | ||||||
|  | from polymorphic.models import PolymorphicModel | ||||||
|  | 
 | ||||||
|  | from django.core.mail import EmailMessage | ||||||
|  | from seminar.utils import aktivniResitele | ||||||
|  | 
 | ||||||
|  | from . import personalni as pm | ||||||
|  | 
 | ||||||
|  | from .base import SeminarModelBase | ||||||
|  | 
 | ||||||
|  | logger = logging.getLogger(__name__) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @reversion.register(ignore_duplicates=True) | ||||||
|  | class Rocnik(SeminarModelBase): | ||||||
|  | 
 | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_rocniky' | ||||||
|  | 		verbose_name = 'Ročník' | ||||||
|  | 		verbose_name_plural = 'Ročníky' | ||||||
|  | 		ordering = ['-rocnik'] | ||||||
|  | 
 | ||||||
|  | 	# Interní ID | ||||||
|  | 	id = models.AutoField(primary_key = True) | ||||||
|  | 
 | ||||||
|  | 	prvni_rok = models.IntegerField('první rok', db_index=True, unique=True) | ||||||
|  | 
 | ||||||
|  | 	rocnik = models.IntegerField('číslo ročníku', db_index=True, unique=True) | ||||||
|  | 
 | ||||||
|  | 	exportovat = models.BooleanField('export do AESOPa', db_column='exportovat', default=False, | ||||||
|  | 			help_text='Exportuje se jen podle tohoto flagu (ne veřejnosti),' | ||||||
|  | 			' a to jen čísla s veřejnou výsledkovkou') | ||||||
|  | 
 | ||||||
|  | 	# má OneToOneField s: | ||||||
|  | 	# RocnikNode | ||||||
|  | 
 | ||||||
|  | 	def __str__(self): | ||||||
|  | 		return '{} ({}/{})'.format(self.rocnik, self.prvni_rok, self.prvni_rok+1) | ||||||
|  | 
 | ||||||
|  | 	# Ročník v římských číslech | ||||||
|  | 	def roman(self): | ||||||
|  | 		return roman(int(self.rocnik)) | ||||||
|  | 
 | ||||||
|  | 	def verejne(self): | ||||||
|  | 		return len(self.verejna_cisla()) > 0 | ||||||
|  | 	verejne.boolean = True | ||||||
|  | 	verejne.short_description = 'Veřejný (jen dle čísel)' | ||||||
|  | 
 | ||||||
|  | 	def neverejna_cisla(self): | ||||||
|  | 		vc = [c for c in self.cisla.all() if not c.verejne()] | ||||||
|  | 		vc.sort(key=lambda c: c.poradi) | ||||||
|  | 		return vc | ||||||
|  | 
 | ||||||
|  | 	def verejna_cisla(self): | ||||||
|  | 		vc = [c for c in self.cisla.all() if c.verejne()] | ||||||
|  | 		vc.sort(key=lambda c: c.poradi) | ||||||
|  | 		return vc | ||||||
|  | 
 | ||||||
|  | 	def posledni_verejne_cislo(self): | ||||||
|  | 		vc = self.verejna_cisla() | ||||||
|  | 		return vc[-1] if vc else None | ||||||
|  | 
 | ||||||
|  | 	def verejne_vysledkovky_cisla(self): | ||||||
|  | 		vc = list(self.cisla.filter(verejna_vysledkovka=True)) | ||||||
|  | 		vc.sort(key=lambda c: c.poradi) | ||||||
|  | 		return vc | ||||||
|  | 
 | ||||||
|  | 	def posledni_zverejnena_vysledkovka_cislo(self): | ||||||
|  | 		vc = self.verejne_vysledkovky_cisla() | ||||||
|  | 		return vc[-1] if vc else None | ||||||
|  | 
 | ||||||
|  | 	def druhy_rok(self): | ||||||
|  | 		return self.prvni_rok + 1 | ||||||
|  | 
 | ||||||
|  | 	def verejne_url(self): | ||||||
|  | 		return reverse('seminar_rocnik', kwargs={'rocnik': self.rocnik}) | ||||||
|  | 
 | ||||||
|  | 	@classmethod | ||||||
|  | 	def cached_rocnik(cls, r_id): | ||||||
|  | 		name = 'rocnik_%s' % (r_id, ) | ||||||
|  | 		c = cache.get(name) | ||||||
|  | 		if c is None: | ||||||
|  | 			c = cls.objects.get(id=r_id) | ||||||
|  | 			cache.set(name, c, 300) | ||||||
|  | 		return c | ||||||
|  | 		 | ||||||
|  | 	def save(self, *args, **kwargs): | ||||||
|  | 		super().save(*args, **kwargs) | ||||||
|  | 		# *Node.save() aktualizuje název *Nodu. | ||||||
|  | 		try: | ||||||
|  | 			self.rocniknode.save() | ||||||
|  | 		except ObjectDoesNotExist: | ||||||
|  | 			# Neexistující *Node nemá smysl aktualizovat. | ||||||
|  | 			pass | ||||||
|  | 
 | ||||||
|  | def cislo_pdf_filename(self, filename): | ||||||
|  | 	rocnik = str(self.rocnik.rocnik) | ||||||
|  | 	return pathlib.Path('cislo', 'pdf', rocnik, '{}-{}.pdf'.format(rocnik, self.poradi)) | ||||||
|  | 
 | ||||||
|  | def cislo_png_filename(self, filename): | ||||||
|  | 	rocnik = str(self.rocnik.rocnik) | ||||||
|  | 	return pathlib.Path('cislo', 'png', rocnik, '{}-{}.png'.format(rocnik, self.poradi)) | ||||||
|  | 
 | ||||||
|  | @reversion.register(ignore_duplicates=True) | ||||||
|  | class Cislo(SeminarModelBase): | ||||||
|  | 
 | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_cisla' | ||||||
|  | 		verbose_name = 'Číslo' | ||||||
|  | 		verbose_name_plural = 'Čísla' | ||||||
|  | 		ordering = ['-rocnik__rocnik', '-poradi'] | ||||||
|  | 
 | ||||||
|  | 	# Interní ID | ||||||
|  | 	id = models.AutoField(primary_key = True) | ||||||
|  | 
 | ||||||
|  | 	rocnik = models.ForeignKey(Rocnik, verbose_name='ročník', related_name='cisla', | ||||||
|  | 		db_index=True,on_delete=models.PROTECT) | ||||||
|  | 
 | ||||||
|  | 	poradi = models.CharField('název čísla', max_length=32, db_index=True, | ||||||
|  | 		help_text='Většinou jen "1", vyjímečně "7-8", lexikograficky určuje pořadí v ročníku!') | ||||||
|  | 
 | ||||||
|  | 	datum_vydani = models.DateField('datum vydání', blank=True, null=True, | ||||||
|  | 		help_text='Datum vydání finální verze') | ||||||
|  |   | ||||||
|  | 	datum_deadline_soustredeni = models.DateField( | ||||||
|  | 		'datum deadline soustředění', | ||||||
|  | 		blank=True, null=True, | ||||||
|  | 		help_text='Datum pro příjem řešení pro účast na soustředění') | ||||||
|  |   | ||||||
|  | 	datum_preddeadline = models.DateField('datum předdeadline', blank=True, null=True, | ||||||
|  | 		help_text='Datum pro příjem řešení, která se otisknou v dalším čísle') | ||||||
|  | 
 | ||||||
|  | 	datum_deadline = models.DateField('datum deadline', blank=True, null=True, | ||||||
|  | 		help_text='Datum pro příjem řešení úloh zadaných v tomto čísle') | ||||||
|  | 
 | ||||||
|  | 	verejne_db = models.BooleanField('číslo zveřejněno', | ||||||
|  | 		db_column='verejne', default=False) | ||||||
|  | 
 | ||||||
|  | 	verejna_vysledkovka = models.BooleanField( | ||||||
|  | 		'zveřejněna výsledkovka', | ||||||
|  | 		default=False, | ||||||
|  | 		help_text='Je-li false u veřejného čísla, ' | ||||||
|  | 				'není výsledkovka zatím veřejná.') | ||||||
|  | 
 | ||||||
|  | 	poznamka = models.TextField('neveřejná poznámka', blank=True, | ||||||
|  | 		help_text='Neveřejná poznámka k číslu (plain text)') | ||||||
|  | 
 | ||||||
|  | 	pdf = models.FileField('pdf', upload_to=cislo_pdf_filename, null=True, blank=True, | ||||||
|  | 		help_text='PDF čísla, které si mohou řešitelé stáhnout') | ||||||
|  | 
 | ||||||
|  | 	titulka_nahled = models.ImageField('Obrázek titulní strany', upload_to=cislo_png_filename, null=True, blank=True, | ||||||
|  | 		help_text='Obrázek titulní strany, generuje se automaticky') | ||||||
|  | 
 | ||||||
|  | 	# má OneToOneField s: | ||||||
|  | 	# CisloNode | ||||||
|  | 
 | ||||||
|  | 	def kod(self): | ||||||
|  | 		return '%s.%s' % (self.rocnik.rocnik, self.poradi) | ||||||
|  | 	kod.short_description = 'Kód čísla' | ||||||
|  | 
 | ||||||
|  | 	def __str__(self): | ||||||
|  | 		# Potenciální DB HOG, pokud by se ročník necachoval | ||||||
|  | 		r = Rocnik.cached_rocnik(self.rocnik_id) | ||||||
|  | 		return '{}.{}'.format(r.rocnik, self.poradi) | ||||||
|  | 
 | ||||||
|  | 	def verejne(self): | ||||||
|  | 		return self.verejne_db | ||||||
|  | 	verejne.boolean = True | ||||||
|  | 
 | ||||||
|  | 	def verejne_url(self): | ||||||
|  | 		return reverse('seminar_cislo', kwargs={'rocnik': self.rocnik.rocnik, 'cislo': self.poradi}) | ||||||
|  | 
 | ||||||
|  | 	def absolute_url(self): | ||||||
|  | 		return "https://" + str(get_current_site(None)) + self.verejne_url() | ||||||
|  | 
 | ||||||
|  | 	def nasledujici(self): | ||||||
|  | 		"Vrací None, pokud je toto poslední" | ||||||
|  | 		return self.relativni_v_rocniku(1) | ||||||
|  | 
 | ||||||
|  | 	def predchozi(self): | ||||||
|  | 		"Vrací None, pokud je toto první" | ||||||
|  | 		return self.relativni_v_rocniku(-1) | ||||||
|  | 
 | ||||||
|  | 	def relativni_v_rocniku(self, rel_index): | ||||||
|  | 		"Číslo o `index` dále v ročníku. None pokud neexistuje." | ||||||
|  | 		cs = self.rocnik.cisla.order_by('cislo').all() | ||||||
|  | 		i = list(cs).index(self) + rel_index | ||||||
|  | 		if (i < 0) or (i >= len(cs)): | ||||||
|  | 			return None | ||||||
|  | 		return cs[i] | ||||||
|  | 
 | ||||||
|  | 	def vygeneruj_nahled(self): | ||||||
|  | 		VYSKA = 594 | ||||||
|  | 		sirka = int(VYSKA*210/297) | ||||||
|  | 		if not self.pdf: | ||||||
|  | 			return | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 		# Pokud obrázek neexistuje nebo není aktuální, vytvoř jej | ||||||
|  | 		if not self.titulka_nahled or os.path.getmtime(self.titulka_nahled.path) < os.path.getmtime(self.pdf.path): | ||||||
|  | 			png_filename = pathlib.Path(tempfile.mkdtemp(), 'nahled.png') | ||||||
|  | 
 | ||||||
|  | 			subprocess.run([ | ||||||
|  | 				"gs", | ||||||
|  | 				"-sstdout=%stderr", | ||||||
|  | 				"-dSAFER", | ||||||
|  | 				"-dNOPAUSE", | ||||||
|  | 				"-dBATCH", | ||||||
|  | 				"-dNOPROMPT", | ||||||
|  | 				"-sDEVICE=png16m", | ||||||
|  | 				"-r300x300", | ||||||
|  | 				"-dFirstPage=1d", | ||||||
|  | 				"-dLastPage=1d", | ||||||
|  | 				"-sOutputFile=" + str(png_filename), | ||||||
|  | 				"-f%s" % self.pdf.path | ||||||
|  | 				], | ||||||
|  | 				check=True, | ||||||
|  | 				capture_output=True | ||||||
|  | 			) | ||||||
|  | 
 | ||||||
|  | 			with open(png_filename,'rb') as f: | ||||||
|  | 				self.titulka_nahled.save('',f,True) | ||||||
|  | 
 | ||||||
|  | 			png_filename.unlink() | ||||||
|  | 			png_filename.parent.rmdir() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 	@classmethod | ||||||
|  | 	def get(cls, rocnik, cislo): | ||||||
|  | 		try: | ||||||
|  | 			r = Rocnik.objects.get(rocnik=rocnik) | ||||||
|  | 			c = r.cisla.get(poradi=cislo) | ||||||
|  | 		except ObjectDoesNotExist: | ||||||
|  | 			return None | ||||||
|  | 		return c | ||||||
|  | 
 | ||||||
|  | 	def __init__(self, *args, **kwargs): | ||||||
|  | 		super().__init__(*args, **kwargs) | ||||||
|  | 		self.__original_verejne = self.verejne_db | ||||||
|  | 
 | ||||||
|  | 	def posli_cislo_mailem(self): | ||||||
|  | 		# parametry e-mailu | ||||||
|  | 		odkaz = self.absolute_url() | ||||||
|  | 
 | ||||||
|  | 		poslat_z_mailu = 'zadani@mam.mff.cuni.cz' | ||||||
|  | 		predmet = 'Vyšlo číslo {}'.format(self.kod()) | ||||||
|  | 		text_mailu = 'Ahoj,\n' \ | ||||||
|  | 			   'na adrese {} najdete nejnovější číslo.\n' \ | ||||||
|  | 			   'Vaše M&M\n'.format(odkaz) | ||||||
|  | 
 | ||||||
|  | 		# Prijemci e-mailu | ||||||
|  | 		emaily = map(lambda r: r.osoba.email, filter(lambda r: r.zasilat_cislo_emailem, aktivniResitele(self))) | ||||||
|  | 
 | ||||||
|  | 		if not settings.POSLI_MAILOVOU_NOTIFIKACI: | ||||||
|  | 			print("Poslal bych upozornění na tyto adresy: ", " ".join(emaily)) | ||||||
|  | 			return | ||||||
|  | 
 | ||||||
|  | 		email = EmailMessage( | ||||||
|  | 			subject=predmet, | ||||||
|  | 			body=text_mailu, | ||||||
|  | 			from_email=poslat_z_mailu, | ||||||
|  | 			bcc=list(emaily) | ||||||
|  | 			#bcc = příjemci skryté kopie | ||||||
|  | 		) | ||||||
|  | 
 | ||||||
|  | 		email.send() | ||||||
|  | 
 | ||||||
|  | 	def save(self, *args, **kwargs): | ||||||
|  | 		super().save(*args, **kwargs) | ||||||
|  | 		self.vygeneruj_nahled() | ||||||
|  | 		# Při zveřejnění pošle mail | ||||||
|  | 		if self.verejne_db and not self.__original_verejne: | ||||||
|  | 			self.posli_cislo_mailem() | ||||||
|  | 		# *Node.save() aktualizuje název *Nodu. | ||||||
|  | 		try: | ||||||
|  | 			self.cislonode.save() | ||||||
|  | 		except ObjectDoesNotExist: | ||||||
|  | 			# Neexistující *Node nemá smysl aktualizovat, ale je potřeba ho naopak vyrobit | ||||||
|  | 			logger.warning(f'Číslo {self} nemělo ČísloNode, vyrábím…') | ||||||
|  | 			from seminar.models.treenode import CisloNode | ||||||
|  | 			CisloNode.objects.create(cislo=self) | ||||||
|  | 
 | ||||||
|  | 	def clean(self): | ||||||
|  | 		# Finální deadline má být až poslední a je povinný, pokud nějaký deadline existuje. | ||||||
|  | 		# Existence: | ||||||
|  | 		if self.datum_deadline is None and (self.datum_preddeadline is not None or self.datum_deadline_soustredeni is not None): | ||||||
|  | 			raise ValidationError({'datum_deadline': "Číslo musí mít finální deadline, pokud má nějaké deadliny"}) | ||||||
|  | 		if self.datum_deadline is not None: | ||||||
|  | 			if self.datum_preddeadline is not None and self.datum_preddeadline > self.datum_deadline: | ||||||
|  | 				raise ValidationError({'datum_preddeadline': "Předdeadline musí předcházet finálnímu deadlinu"}) | ||||||
|  | 			if self.datum_deadline_soustredeni is not None and self.datum_deadline_soustredeni > self.datum_deadline: | ||||||
|  | 				raise ValidationError({'datum_deadline_soustredeni': "Soustřeďkový deadline musí předcházet finálnímu deadlinu"}) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @reversion.register(ignore_duplicates=True) | ||||||
|  | # Pozor na následující řádek. *Nekrmit, asi kouše!* | ||||||
|  | class Problem(SeminarModelBase,PolymorphicModel): | ||||||
|  | 
 | ||||||
|  | 	class Meta: | ||||||
|  | 		# Není abstraktní, protože se na něj jinak nedají dělat ForeignKeys. | ||||||
|  | 		# TODO: Udělat to polymorfní (pomocí django-polymorphic), abychom dostali  | ||||||
|  | 		# po těch vazbách přímo tu úlohu/témátko vč. fieldů, které nejsou součástí  | ||||||
|  | 		# modelu Problem? | ||||||
|  | 
 | ||||||
|  | 		#abstract = True | ||||||
|  | 		db_table = 'seminar_problemy' | ||||||
|  | 		verbose_name = 'Problém' | ||||||
|  | 		verbose_name_plural = 'Problémy' | ||||||
|  | 		ordering = ['nazev'] | ||||||
|  | 
 | ||||||
|  | 	# Interní ID | ||||||
|  | 	id = models.AutoField(primary_key = True) | ||||||
|  | 
 | ||||||
|  | 	# Název | ||||||
|  | 	nazev = models.CharField('název', max_length=256) # Zveřejnitelný na stránky | ||||||
|  | 
 | ||||||
|  | 	# Problém má podproblémy | ||||||
|  | 	nadproblem = models.ForeignKey('self', verbose_name='nadřazený problém', | ||||||
|  | 		related_name='podproblem', null=True, blank=True, | ||||||
|  | 		on_delete=models.SET_NULL) | ||||||
|  | 
 | ||||||
|  | 	STAV_NAVRH = 'navrh' | ||||||
|  | 	STAV_ZADANY = 'zadany' | ||||||
|  | 	STAV_VYRESENY = 'vyreseny' | ||||||
|  | 	STAV_SMAZANY = 'smazany' | ||||||
|  | 	STAV_CHOICES = [ | ||||||
|  | 		(STAV_NAVRH, 'Návrh'), | ||||||
|  | 		(STAV_ZADANY, 'Zadaný'), | ||||||
|  | 		(STAV_VYRESENY, 'Vyřešený'), | ||||||
|  | 		(STAV_SMAZANY, 'Smazaný'), | ||||||
|  | 		] | ||||||
|  | 	stav = models.CharField('stav problému', max_length=32, choices=STAV_CHOICES, blank=False, default=STAV_NAVRH) | ||||||
|  | 	# Téma je taky Problém, takže má stavy, "zadané" témátko je aktuálně otevřené a dá se k němu něco poslat (řešení nebo článek) | ||||||
|  | 
 | ||||||
|  | 	zamereni = TaggableManager(verbose_name='zaměření',  | ||||||
|  | 		help_text='Zaměření M/F/I/O problému, příp. další tagy', blank=True) | ||||||
|  | 
 | ||||||
|  | 	poznamka = models.TextField('org poznámky (HTML)', blank=True, | ||||||
|  | 		help_text='Neveřejný návrh úlohy, návrh řešení, text zadání, poznámky ...') | ||||||
|  | 
 | ||||||
|  | 	autor = models.ForeignKey(pm.Organizator, verbose_name='autor problému', | ||||||
|  | 		related_name='autor_problemu_%(class)s', null=True, blank=True, | ||||||
|  | 		on_delete=models.SET_NULL) | ||||||
|  | 
 | ||||||
|  | 	garant = models.ForeignKey(pm.Organizator, verbose_name='garant zadaného problému', | ||||||
|  | 		related_name='garant_problemu_%(class)s', null=True, blank=True, | ||||||
|  | 		on_delete=models.SET_NULL) | ||||||
|  | 
 | ||||||
|  | 	opravovatele = models.ManyToManyField(pm.Organizator, verbose_name='opravovatelé', | ||||||
|  | 		blank=True, related_name='opravovatele_%(class)s') | ||||||
|  | 
 | ||||||
|  | 	kod = models.CharField('lokální kód', max_length=32, blank=True, default='', | ||||||
|  | 		help_text='Číslo/kód úlohy v čísle nebo kód tématu/článku/seriálu v ročníku') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 	vytvoreno = models.DateTimeField('vytvořeno', default=timezone.now, blank=True, editable=False) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 	def __str__(self): | ||||||
|  | 		return self.nazev | ||||||
|  | 
 | ||||||
|  | 	# Implicitini implementace, jednotlivé dědící třídy si přepíšou | ||||||
|  | 	@cached_property | ||||||
|  | 	def kod_v_rocniku(self): | ||||||
|  | 		if self.stav == 'zadany': | ||||||
|  | 			if self.nadproblem: | ||||||
|  | 				return self.nadproblem.kod_v_rocniku+".{}".format(self.kod) | ||||||
|  | 			return str(self.kod) | ||||||
|  | 		return '<Není zadaný>' | ||||||
|  | 
 | ||||||
|  | #	def verejne(self): | ||||||
|  | #		# aktuálně podle stavu problému | ||||||
|  | #		# FIXME pro některé problémy možná chceme override | ||||||
|  | #		# FIXME vrací veřejnost čistě problému, nezávisle na čísle, ve kterém je.  | ||||||
|  | #		# Je to tak správně? Podle aktuální představy ano. | ||||||
|  | #		stav_verejny = False | ||||||
|  | #		if self.stav == 'zadany' or self.stav == 'vyreseny': | ||||||
|  | #			stav_verejny = True | ||||||
|  | #			print("stav_verejny: {}".format(stav_verejny))		 | ||||||
|  | # | ||||||
|  | #		cislo_verejne = False | ||||||
|  | #		cislonode = self.cislo_node() | ||||||
|  | #		if cislonode is None: | ||||||
|  | #			# problém nemá vlastní node, veřejnost posuzujeme jen podle stavu | ||||||
|  | #			print("empty node")		 | ||||||
|  | #			return stav_verejny | ||||||
|  | #		else:	 | ||||||
|  | #			cislo_zadani = cislonode.cislo | ||||||
|  | #			if (cislo_zadani and cislo_zadani.verejne()): | ||||||
|  | #				print("cislo: {}".format(cislo_zadani)) | ||||||
|  | #				cislo_verejne = True | ||||||
|  | #			print("stav_verejny: {}".format(stav_verejny))		 | ||||||
|  | #			print("cislo_verejne: {}".format(cislo_verejne))		 | ||||||
|  | #			return (stav_verejny and cislo_verejne) | ||||||
|  | #	verejne.boolean = True | ||||||
|  | 
 | ||||||
|  | 	def verejne_url(self): | ||||||
|  | 		return reverse('seminar_problem', kwargs={'pk': self.id}) | ||||||
|  | 
 | ||||||
|  | 	def admin_url(self): | ||||||
|  | 			return reverse('admin:seminar_problem_change', args=(self.id, )) | ||||||
|  | 
 | ||||||
|  | 	def hlavni_problem(self): | ||||||
|  | 		""" Pro daný problém vrátí jeho nejvyšší nadproblém.""" | ||||||
|  | 		return hlavni_problem(self) | ||||||
|  | 
 | ||||||
|  | # FIXME - k úloze | ||||||
|  | 	def body_v_zavorce(self): | ||||||
|  | 		"""Vrať string s body v závorce jsou-li u problému vyplněné, jinak '' | ||||||
|  | 
 | ||||||
|  | 		Je-li desetinná část nulová, nezobrazuj ji. | ||||||
|  | 		""" | ||||||
|  | 		pocet_bodu = None | ||||||
|  | 		if self.body: | ||||||
|  | 			b = self.body | ||||||
|  | 			pocet_bodu = int(b) if int(b) == b else b | ||||||
|  | 		return "({}\u2009b)".format(pocet_bodu) if self.body else "" | ||||||
|  | 
 | ||||||
|  | class Tema(Problem): | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_temata' | ||||||
|  | 		verbose_name = 'Téma' | ||||||
|  | 		verbose_name_plural = 'Témata' | ||||||
|  | 	 | ||||||
|  | 	TEMA_TEMA = 'tema' | ||||||
|  | 	TEMA_SERIAL = 'serial' | ||||||
|  | 	TEMA_CHOICES = [ | ||||||
|  | 		(TEMA_TEMA, 'Téma'), | ||||||
|  | 		(TEMA_SERIAL, 'Seriál'), | ||||||
|  | 		] | ||||||
|  | 	tema_typ = models.CharField('Typ tématu', max_length=16, choices=TEMA_CHOICES,  | ||||||
|  | 		blank=False, default=TEMA_TEMA) | ||||||
|  | 
 | ||||||
|  | 	rocnik = models.ForeignKey(Rocnik, verbose_name='ročník',related_name='temata',blank=True, null=True, | ||||||
|  | 		on_delete=models.PROTECT) | ||||||
|  | 
 | ||||||
|  | 	abstrakt = models.TextField('Abstrakt na rozcestník', blank=True) | ||||||
|  | 	obrazek = models.ImageField('Obrázek na rozcestník', null=True, blank=True) | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def kod_v_rocniku(self): | ||||||
|  | 		if self.stav == 'zadany': | ||||||
|  | 			if self.nadproblem: | ||||||
|  | 				return self.nadproblem.kod_v_rocniku+".t{}".format(self.kod) | ||||||
|  | 			return "t{}".format(self.kod) | ||||||
|  | 		return '<Není zadaný>' | ||||||
|  | 
 | ||||||
|  | 	def save(self, *args, **kwargs): | ||||||
|  | 		super().save(*args, **kwargs) | ||||||
|  | 		# *Node.save() aktualizuje název *Nodu. | ||||||
|  | 		for tvcn in self.temavcislenode_set.all(): | ||||||
|  | 			tvcn.save() | ||||||
|  | 
 | ||||||
|  | 	def cislo_node(self): | ||||||
|  | 		tema_node_set = self.temavcislenode_set.all() | ||||||
|  | 		tema_cisla_vyskyt = [] | ||||||
|  | 		from seminar.models.treenode import CisloNode | ||||||
|  | 		for tn in tema_node_set: | ||||||
|  | 			tema_cisla_vyskyt.append( | ||||||
|  | 				treelib.get_upper_node_of_type(tn, CisloNode).cislo) | ||||||
|  | 		tema_cisla_vyskyt.sort(key=lambda x:x.datum_vydani) | ||||||
|  | 		prvni_zadani = tema_cisla_vyskyt[0] | ||||||
|  | 		return prvni_zadani.cislonode | ||||||
|  | 
 | ||||||
|  | class Clanek(Problem): | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_clanky' | ||||||
|  | 		verbose_name = 'Článek' | ||||||
|  | 		verbose_name_plural = 'Články' | ||||||
|  | 	 | ||||||
|  | 	cislo = models.ForeignKey(Cislo, blank=True, null=True, on_delete=models.PROTECT, | ||||||
|  | 		verbose_name='číslo vydání', related_name='vydane_clanky') | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def kod_v_rocniku(self): | ||||||
|  | 		if self.stav == 'zadany': | ||||||
|  | # Nemělo by být potřeba | ||||||
|  | #			if self.nadproblem: | ||||||
|  | #				return self.nadproblem.kod_v_rocniku+".c{}".format(self.kod) | ||||||
|  | 			return "c{}".format(self.kod) | ||||||
|  | 		return '<Není zadaný>' | ||||||
|  | 	 | ||||||
|  | 	def node(self): | ||||||
|  | 		return None | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Uloha(Problem): | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_ulohy' | ||||||
|  | 		verbose_name = 'Úloha' | ||||||
|  | 		verbose_name_plural = 'Úlohy' | ||||||
|  | 	 | ||||||
|  | 	cislo_zadani = models.ForeignKey(Cislo, verbose_name='číslo zadání', blank=True,  | ||||||
|  | 		null=True, related_name='zadane_ulohy', on_delete=models.PROTECT) | ||||||
|  | 	 | ||||||
|  | 	cislo_deadline = models.ForeignKey(Cislo, verbose_name='číslo deadlinu', blank=True,  | ||||||
|  | 		null=True, related_name='deadlinove_ulohy', on_delete=models.PROTECT) | ||||||
|  | 
 | ||||||
|  | 	cislo_reseni = models.ForeignKey(Cislo, verbose_name='číslo řešení', blank=True,  | ||||||
|  | 		null=True, related_name='resene_ulohy', | ||||||
|  | 		help_text='Číslo s řešením úlohy, jen pro úlohy', | ||||||
|  | 		on_delete=models.PROTECT) | ||||||
|  | 
 | ||||||
|  | 	max_body = models.DecimalField(max_digits=8, decimal_places=1, verbose_name='maximum bodů',  | ||||||
|  | 		blank=True, null=True) | ||||||
|  | 
 | ||||||
|  | 	# má OneToOneField s: | ||||||
|  | 	# UlohaZadaniNode | ||||||
|  | 	# UlohaVzorakNode | ||||||
|  | 
 | ||||||
|  | 	@cached_property | ||||||
|  | 	def kod_v_rocniku(self): | ||||||
|  | 		if self.stav == 'zadany': | ||||||
|  | 			name="{}.u{}".format(self.cislo_zadani.poradi,self.kod) | ||||||
|  | 			if self.nadproblem: | ||||||
|  | 				return self.nadproblem.kod_v_rocniku+name | ||||||
|  | 			return name | ||||||
|  | 		return '<Není zadaný>' | ||||||
|  | 
 | ||||||
|  | 	def save(self, *args, **kwargs): | ||||||
|  | 		super().save(*args, **kwargs) | ||||||
|  | 		# *Node.save() aktualizuje název *Nodu. | ||||||
|  | 		try: | ||||||
|  | 			self.ulohazadaninode.save() | ||||||
|  | 		except ObjectDoesNotExist: | ||||||
|  | 			# Neexistující *Node nemá smysl aktualizovat. | ||||||
|  | 			pass | ||||||
|  | 		try: | ||||||
|  | 			self.ulohavzoraknode.save() | ||||||
|  | 		except ObjectDoesNotExist: | ||||||
|  | 			# Neexistující *Node nemá smysl aktualizovat. | ||||||
|  | 			pass | ||||||
|  | 
 | ||||||
|  | 	def cislo_node(self): | ||||||
|  | 		zadani_node = self.ulohazadaninode | ||||||
|  | 		from seminar.models.treenode import CisloNode | ||||||
|  | 		return treelib.get_upper_node_of_type(zadani_node, CisloNode) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def aux_generate_filename(self, filename): | ||||||
|  | 	"""Pomocná funkce generující ošetřený název souboru v adresáři s datem""" | ||||||
|  | 	clean = get_valid_filename( | ||||||
|  | 		unidecode(filename.replace('/', '-').replace('\0', '')) | ||||||
|  | 	) | ||||||
|  | 	datedir = timezone.now().strftime('%Y-%m') | ||||||
|  | 	fname = "{}/{}".format( | ||||||
|  | 		timezone.now().strftime('%Y-%m-%d-%H:%M'), | ||||||
|  | 		clean) | ||||||
|  | 	return os.path.join(datedir, fname) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Pohadka(SeminarModelBase): | ||||||
|  | 	"""Kus pohádky před/za úlohou v čísle""" | ||||||
|  | 
 | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_pohadky' | ||||||
|  | 		verbose_name = 'Pohádka' | ||||||
|  | 		verbose_name_plural = 'Pohádky' | ||||||
|  | 		ordering = ['vytvoreno'] | ||||||
|  | 
 | ||||||
|  | 	# Interní ID | ||||||
|  | 	id = models.AutoField(primary_key=True) | ||||||
|  | 
 | ||||||
|  | 	autor = models.ForeignKey( | ||||||
|  | 		pm.Organizator, | ||||||
|  | 		verbose_name="Autor pohádky", | ||||||
|  | 
 | ||||||
|  | 		# Při nahrávání z TeXu není vyplnění vyžadováno, v adminu je | ||||||
|  | 		null=True, | ||||||
|  | 		blank=False, | ||||||
|  | 		on_delete=models.SET_NULL | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	vytvoreno = models.DateTimeField( | ||||||
|  | 		'Vytvořeno', | ||||||
|  | 		default=timezone.now, | ||||||
|  | 		blank=True, | ||||||
|  | 		editable=False | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	# má OneToOneField s: | ||||||
|  | 	# PohadkaNode | ||||||
|  | 
 | ||||||
|  | 	def __str__(self): | ||||||
|  | 		uryvek = self.text if len(self.text) < 50 else self.text[:(50-3)]+"..." | ||||||
|  | 		return uryvek | ||||||
|  | 
 | ||||||
|  | 	def save(self, *args, **kwargs): | ||||||
|  | 		super().save(*args, **kwargs) | ||||||
|  | 		# *Node.save() aktualizuje název *Nodu. | ||||||
|  | 		try: | ||||||
|  | 			self.pohadkanode.save() | ||||||
|  | 		except ObjectDoesNotExist: | ||||||
|  | 			# Neexistující *Node nemá smysl aktualizovat. | ||||||
|  | 			pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @reversion.register(ignore_duplicates=True) | ||||||
|  | class Nastaveni(SingletonModel): | ||||||
|  | 
 | ||||||
|  | 	class Meta: | ||||||
|  | 		db_table = 'seminar_nastaveni' | ||||||
|  | 		verbose_name = 'Nastavení semináře' | ||||||
|  | 
 | ||||||
|  | #	aktualni_rocnik = models.ForeignKey(Rocnik, verbose_name='aktuální ročník', | ||||||
|  | #		null=False, on_delete=models.PROTECT) | ||||||
|  | 
 | ||||||
|  | 	aktualni_cislo = models.ForeignKey(Cislo, verbose_name='poslední vydané číslo',  | ||||||
|  | 		null=False, on_delete=models.PROTECT) | ||||||
|  | 
 | ||||||
|  | 	@property | ||||||
|  | 	def aktualni_rocnik(self): | ||||||
|  | 		return self.aktualni_cislo.rocnik | ||||||
|  | 
 | ||||||
|  | 	def __str__(self): | ||||||
|  | 		return 'Nastavení semináře' | ||||||
|  | 
 | ||||||
|  | 	def admin_url(self): | ||||||
|  | 		return reverse('admin:seminar_nastaveni_change', args=(self.id, )) | ||||||
|  | 	 | ||||||
|  | 	def verejne(self): | ||||||
|  | 		return False | ||||||
|  | @ -1,97 +0,0 @@ | ||||||
| {% extends "seminar/archiv/base_cisla.html" %} |  | ||||||
| 
 |  | ||||||
| {# {% block content %} |  | ||||||
|  <div> |  | ||||||
| 
 |  | ||||||
|   <h1> |  | ||||||
|     {% block nadpis1a %}{% block nadpis1b %} |  | ||||||
|       Číslo {{ cislo }} |  | ||||||
|     {% endblock %}{% endblock %} |  | ||||||
|   </h1> |  | ||||||
| 
 |  | ||||||
|   {% if cislo.pdf %} |  | ||||||
|     <p><a href='{{ cislo.pdf.url }}'>Číslo v pdf</a> |  | ||||||
|   {% endif %} |  | ||||||
|   <p><a href='{{ cislo.rocnik.verejne_url }}'>Ročník {{ cislo.rocnik }}</a> |  | ||||||
| 
 |  | ||||||
|   {% if v_cisle_zadane %} |  | ||||||
|     <h2>Zadané problémy</h2> |  | ||||||
|     <ul> |  | ||||||
|     {% for p in v_cisle_zadane %} |  | ||||||
|       <li{% if user.is_staff and not cislo.verejne %} class='mam-org-only'{% endif %}> |  | ||||||
|         {% if user.is_staff or cislo.verejne %} |  | ||||||
|           <a href='{{ p.verejne_url }}'>{% endif %}{{ p.kod_v_rocniku }} {{ p.nazev }} {{ p.body_v_zavorce }}{% if user.is_staff or cislo.verejne %}</a>{% endif %} |  | ||||||
|     {% endfor %} |  | ||||||
|     </ul> |  | ||||||
|   {% endif %} |  | ||||||
| 
 |  | ||||||
|   {% if resene_problemy %} |  | ||||||
|     <h2>Řešené problémy</h2> |  | ||||||
|     <ul> |  | ||||||
|     {% for p in resene_problemy %} |  | ||||||
|       <li{% if user.is_staff and not cislo.verejne %} class='mam-org-only'{% endif %}> |  | ||||||
|         {% if user.is_staff or cislo.verejne %} |  | ||||||
|           <a href='{{ p.verejne_url }}'>{% endif %}{{ p.kod_v_rocniku }} {{ p.nazev }} {{ p.body_v_zavorce }}{% if user.is_staff or cislo.verejne %}</a>{% endif %} |  | ||||||
|     {% endfor %} |  | ||||||
|     </ul> |  | ||||||
|   {% endif %} |  | ||||||
| 
 |  | ||||||
|   {% if user.is_staff %} |  | ||||||
|       <div class="mam-org-only"> |  | ||||||
|         <h2> Orgovské odkazy </h2> |  | ||||||
|         <ul> |  | ||||||
|           <li><a href="obalky.pdf">Obálky (PDF)</a></li> |  | ||||||
|           <li><a href="tituly.tex">Tituly (TeX)</a></li> |  | ||||||
|           <li><a href="vysledkovka.tex">Výsledkovka (TeX)</a></li> |  | ||||||
|           <li><a href="obalkovani">Obálkování</a></li> |  | ||||||
|         </ul> |  | ||||||
|       </div> |  | ||||||
|   {% endif %}  |  | ||||||
| 
 |  | ||||||
|   {% if cislo.verejna_vysledkovka %} |  | ||||||
|   <h2>Výsledkovka</h2> |  | ||||||
|   {% else %} |  | ||||||
|     {% if user.is_staff %} |  | ||||||
|       <div class='mam-org-only'> |  | ||||||
|       <h2>Výsledkovka (neveřejná)</h2> |  | ||||||
|     {% endif %} |  | ||||||
|   {% endif %} |  | ||||||
| 
 |  | ||||||
|   {% if cislo.verejna_vysledkovka or user.is_staff %} |  | ||||||
|     <table class='vysledkovka'> |  | ||||||
|       <tr class='border-b'> |  | ||||||
|         <th class='border-r'># |  | ||||||
|         <th class='border-r'>Jméno #} |  | ||||||
|         {# problémy by měly být veřejné, když je veřejná výsledkovka #} |  | ||||||
| {#        {% for p in problemy %} |  | ||||||
|         <th class='border-r'><a href="{{ p.verejne_url }}">{{ p.kod_v_rocniku }}</a> |  | ||||||
|         {% endfor %} |  | ||||||
|         <th class='border-r'>Za číslo</sup> |  | ||||||
|         <th class='border-r'>Za ročník |  | ||||||
|         <th class='border-r'>Odjakživa |  | ||||||
|     {% for rv in radky_vysledkovky %} |  | ||||||
|       <tr> |  | ||||||
|         <td class='border-r'>{% autoescape off %}{{ rv.poradi }}{% endautoescape %} |  | ||||||
|             <th class='border-r'> |  | ||||||
|             {% if rv.resitel.titul != "" %} |  | ||||||
|               {{ rv.resitel.titul }}<sup>MM</sup> |  | ||||||
|             {% endif %} |  | ||||||
|             {{ rv.resitel.osoba.plne_jmeno }} |  | ||||||
|         {% for b in rv.hlavni_problemy_body %} |  | ||||||
|         <td class='border-r'>{{ b }} |  | ||||||
|         {% endfor %} |  | ||||||
|         <td class='border-r'>{{ rv.body_cislo }} |  | ||||||
|         <td class='border-r'><b>{{ rv.body_rocnik }}</b> |  | ||||||
|         <td class='border-r'>{{ rv.body_celkem_odjakziva }} |  | ||||||
|       </tr> |  | ||||||
|     {% endfor %} |  | ||||||
|     </table> |  | ||||||
|   {% endif %} |  | ||||||
| 
 |  | ||||||
|   {% if not cislo.verejna_vysledkovka and user.is_staff %} |  | ||||||
|       </div> |  | ||||||
|   {% endif %} |  | ||||||
| 
 |  | ||||||
| </div>  |  | ||||||
| {% endblock content %} #} |  | ||||||
| 
 |  | ||||||
|  | @ -79,73 +79,7 @@ | ||||||
|   {% endif %} |   {% endif %} | ||||||
| 
 | 
 | ||||||
|   {% if cislo.verejna_vysledkovka or user.je_org %} |   {% if cislo.verejna_vysledkovka or user.je_org %} | ||||||
|     <table class='vysledkovka'> |       {% include "vysledkovky/vysledkovka_cisla.html" %} | ||||||
|       <tr class='border-b'> |  | ||||||
|         <th class='border-r'># |  | ||||||
|         <th class='border-r'>Jméno |  | ||||||
|         {% for p in problemy %} |  | ||||||
|         <th class='border-r' id="problem{{ forloop.counter }}">{# <a href="{{ p.verejne_url }}"> #}{{ p.kod_v_rocniku }}{# </a> #} |  | ||||||
| 
 |  | ||||||
|             {# TODELETE #} |  | ||||||
|                 {% for podproblemy in podproblemy_iter.next %} |  | ||||||
|                     <th class='border-r podproblem{{ forloop.parentloop.counter }} podproblem'>{# <a href="{{ podproblemy.verejne_url }}"> #}{{ podproblemy.kod_v_rocniku }}{# </a> #} |  | ||||||
|                 {% endfor %} |  | ||||||
|             {# TODELETE #} |  | ||||||
| 
 |  | ||||||
|         {% endfor %} |  | ||||||
|         {% if ostatni %}<th class='border-r'>Ostatní {% endif %} |  | ||||||
| 
 |  | ||||||
|             {# TODELETE #} |  | ||||||
|                 {% for podproblemy in podproblemy_iter.next %} |  | ||||||
|                     <th class='border-r podproblem{{ problemy.len }} podproblem'>{# <a href="{{ podproblemy.verejne_url }}"> #}{{ podproblemy.kod_v_rocniku }}{# </a> #} |  | ||||||
|                 {% endfor %} |  | ||||||
|             {# TODELETE #} |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         <th class='border-r'>Za číslo |  | ||||||
|         <th class='border-r'>Za ročník |  | ||||||
|         <th class='border-r'>Odjakživa |  | ||||||
|     {% for rv in radky_vysledkovky %} |  | ||||||
|       <tr> |  | ||||||
|         <td class='border-r'>{% autoescape off %}{{ rv.poradi }}{% endautoescape %} |  | ||||||
|             <th class='border-r'> |  | ||||||
|             {% if rv.titul %} |  | ||||||
|               {{ rv.titul }}<sup>MM</sup> |  | ||||||
|             {% endif %} |  | ||||||
|             {{ rv.resitel.osoba.plne_jmeno }} |  | ||||||
|         {% for b in rv.body_problemy_sezn %} |  | ||||||
|         <td class='border-r'>{{ b }} |  | ||||||
| 
 |  | ||||||
|             {# TODELETE #} |  | ||||||
|                 {% for body_podproblemu in rv.body_podproblemy_iter.next %} |  | ||||||
|                     <td class='border-r podproblem{{ forloop.parentloop.counter }} podproblem'>{{ body_podproblemu }} |  | ||||||
|                 {% endfor %} |  | ||||||
|             {# TODELETE #} |  | ||||||
| 
 |  | ||||||
|         {% endfor %} |  | ||||||
|         <td class='border-r'>{{ rv.body_cislo }} |  | ||||||
|         <td class='border-r'><b>{{ rv.body_rocnik }}</b> |  | ||||||
|         <td class='border-r'>{{ rv.body_celkem_odjakziva }} |  | ||||||
|       </tr> |  | ||||||
|     {% endfor %} |  | ||||||
|     </table> |  | ||||||
| 
 |  | ||||||
|       {# TODELETE #} |  | ||||||
|         <script> |  | ||||||
|           {% for p in problemy %} |  | ||||||
|             $(".podproblem{{ forloop.counter }}").css("display", "none") |  | ||||||
|             $("#problem{{ forloop.counter }}")[0].addEventListener('mouseover', podproblem{{ forloop.counter }}) |  | ||||||
|             $("#problem{{ forloop.counter }}")[0].addEventListener('mouseout', podproblem{{ forloop.counter }}end) |  | ||||||
|             function podproblem{{ forloop.counter }}(event) { |  | ||||||
|                 $(".podproblem{{ forloop.counter }}").css("display", "") |  | ||||||
|             } |  | ||||||
|             function podproblem{{ forloop.counter }}end(event) { |  | ||||||
|                 $(".podproblem{{ forloop.counter }}").css("display", "none") |  | ||||||
|             } |  | ||||||
|           {% endfor %} |  | ||||||
|         </script> |  | ||||||
|       {# TODELETE #} |  | ||||||
| 
 |  | ||||||
|   {% endif %} |   {% endif %} | ||||||
| 
 | 
 | ||||||
|   {% if not cislo.verejna_vysledkovka and user.je_org %} |   {% if not cislo.verejna_vysledkovka and user.je_org %} | ||||||
|  |  | ||||||
|  | @ -114,18 +114,14 @@ | ||||||
| 
 | 
 | ||||||
|   {% if vysledkovka %} |   {% if vysledkovka %} | ||||||
|     <h2>Výsledková listina</h2> |     <h2>Výsledková listina</h2> | ||||||
|     {% include "seminar/vysledkovka_rocnik.html" %} |       {% include "vysledkovky/vysledkovka_rocnik.html" %} | ||||||
|   {% endif %} |   {% endif %} | ||||||
| 
 | 
 | ||||||
|   {% if user.je_org %} |   {% if user.je_org %} | ||||||
|     <div class='mam-org-only'> |     <div class='mam-org-only'> | ||||||
|     <a href='vysledkovka.tex' download>Výsledkovka ročníku (LaTeX, včetně neveřejných)</a> |     <a href='vysledkovka.tex' download>Výsledkovka ročníku (LaTeX, včetně neveřejných)</a> | ||||||
|     <h2>Výsledková listina včetně neveřejných bodů</h2> |     <h2>Výsledková listina včetně neveřejných bodů</h2> | ||||||
|       {% with radky_vysledkovky_s_neverejnymi as radky_vysledkovky %} |         {% include "vysledkovky/vysledkovka_rocnik_neverejna.html" %} | ||||||
|       {% with cisla_s_neverejnymi as cisla %} |  | ||||||
|         {% include "seminar/vysledkovka_rocnik.html" %} |  | ||||||
|       {% endwith %} |  | ||||||
|       {% endwith %} |  | ||||||
|     </div> |     </div> | ||||||
|   {% endif %} |   {% endif %} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,105 +0,0 @@ | ||||||
| {% extends "base.html" %} |  | ||||||
| {% load staticfiles %} |  | ||||||
| 
 |  | ||||||
| {% block script %} |  | ||||||
|     <script src="{% static 'seminar/prihlaska.js' %}"></script> |  | ||||||
| {% endblock %} |  | ||||||
| 
 |  | ||||||
| <!-- |  | ||||||
| 
 |  | ||||||
| # pro přidání políčka do formuláře je potřeba |  | ||||||
| # - mít v modelu tu položku, kterou chci upravovat |  | ||||||
| # - přidat do views (prihlaskaView, resitelEditView) |  | ||||||
| # - přidat do forms |  | ||||||
| # - includovat do html |  | ||||||
| 
 |  | ||||||
| --> |  | ||||||
| 
 |  | ||||||
| {% block content %} |  | ||||||
| <h1> |  | ||||||
|   {% block nadpis1a %}{% block nadpis1b %} |  | ||||||
|    Změna osobních údajů |  | ||||||
|   {% endblock %}{% endblock %} |  | ||||||
| </h1> |  | ||||||
| <form action="{% url 'seminar_resitel_edit' %}" method="post"> |  | ||||||
|  {% csrf_token %} |  | ||||||
|  {{form.non_field_errors}} |  | ||||||
| 
 |  | ||||||
| <hr> |  | ||||||
| 
 |  | ||||||
|      <h4> |  | ||||||
|       Přihlašovací údaje |  | ||||||
|      </h4> |  | ||||||
|      <table class="form"> |  | ||||||
|        {% include "seminar/profil/prihlaska_field.html" with field=form.username %} |  | ||||||
|      </table> |  | ||||||
| 
 |  | ||||||
| <hr> |  | ||||||
| 
 |  | ||||||
|     <h4> |  | ||||||
|      Osobní údaje |  | ||||||
|     </h4> |  | ||||||
|       <table class="form"> |  | ||||||
|        {% include "seminar/profil/prihlaska_field.html" with field=form.jmeno %} |  | ||||||
|        {% include "seminar/profil/prihlaska_field.html" with field=form.prijmeni %} |  | ||||||
|        {% include "seminar/profil/prihlaska_field.html" with field=form.pohlavi_muz%} |  | ||||||
|        {% include "seminar/profil/prihlaska_field.html" with field=form.email %} |  | ||||||
|        {% include "seminar/profil/prihlaska_field.html" with field=form.telefon %} |  | ||||||
|        {% include "seminar/profil/prihlaska_field.html" with field=form.datum_narozeni %} |  | ||||||
|      </table> |  | ||||||
| 
 |  | ||||||
|   <hr> |  | ||||||
| 
 |  | ||||||
|     <h4> |  | ||||||
|       Bydliště |  | ||||||
|     </h4> |  | ||||||
|       <table class="form"> |  | ||||||
|        {% include "seminar/profil/prihlaska_field.html" with field=form.ulice %} |  | ||||||
|        {% include "seminar/profil/prihlaska_field.html" with field=form.mesto %} |  | ||||||
|        {% include "seminar/profil/prihlaska_field.html" with field=form.psc %} |  | ||||||
|        {% include "seminar/profil/prihlaska_field.html" with field=form.stat %} |  | ||||||
|        {% include "seminar/profil/prihlaska_field.html" with field=form.stat_text id="id_li_stat_text"%} |  | ||||||
|      </table> |  | ||||||
| 
 |  | ||||||
|  <hr> |  | ||||||
| 
 |  | ||||||
|     <h4> |  | ||||||
|      Škola |  | ||||||
|     </h4> |  | ||||||
|      <table class="form"> |  | ||||||
|        {% include "seminar/profil/prihlaska_field.html" with field=form.skola %} |  | ||||||
|        <tr><td colspan="2" ><button id="id_skola_text_button" type="button">Škola není v seznamu</button></td></tr> |  | ||||||
|        <tr><td id="id_li_skola_vypln" colspan="2">Vyplň prosím celý název a adresu školy.</td></tr> |  | ||||||
|        {% include "seminar/profil/prihlaska_field.html" with field=form.skola_nazev id="id_li_skola_nazev" %} |  | ||||||
|        {% include "seminar/profil/prihlaska_field.html" with field=form.skola_adresa id="id_li_skola_adresa" %} |  | ||||||
|        {% include "seminar/profil/prihlaska_field.html" with field=form.rok_maturity %} |  | ||||||
|      </table> |  | ||||||
| 
 |  | ||||||
|  <hr> |  | ||||||
| 
 |  | ||||||
|     <h4> |  | ||||||
|      Pošta |  | ||||||
|     </h4> |  | ||||||
|      <table class="form"> |  | ||||||
|        {% include "seminar/profil/prihlaska_field.html" with field=form.zasilat %} |  | ||||||
|        {% include "seminar/profil/prihlaska_field.html" with field=form.zasilat_cislo_emailem %} |  | ||||||
|      </table> |  | ||||||
| 
 |  | ||||||
|  <hr> |  | ||||||
| 
 |  | ||||||
|     <h4> |  | ||||||
|      Zasílání propagačních materiálů |  | ||||||
|     </h4> |  | ||||||
|      <table class="form"> |  | ||||||
|        {% include "seminar/profil/prihlaska_field.html" with field=form.spam %} |  | ||||||
|      </table> |  | ||||||
| 
 |  | ||||||
|  <hr> |  | ||||||
| 
 |  | ||||||
|     <input type="submit" value="Změnit"> |  | ||||||
| </form> |  | ||||||
| <script> |  | ||||||
| $("#id_stat").on("change",addrCountryChanged); |  | ||||||
| $("#id_skola_text_button").on("click",schoolNotInList); |  | ||||||
| </script> |  | ||||||
| {% endblock %} |  | ||||||
|  | @ -1,123 +0,0 @@ | ||||||
| {% extends "base.html" %} |  | ||||||
| {% load staticfiles %} |  | ||||||
| 
 |  | ||||||
| {% block script %} |  | ||||||
|     <script src="{% static 'seminar/prihlaska.js' %}"></script> |  | ||||||
| {% endblock %} |  | ||||||
| 
 |  | ||||||
| <!-- |  | ||||||
| 
 |  | ||||||
| # pro přidání políčka do formuláře je potřeba |  | ||||||
| # - mít v modelu tu položku, kterou chci upravovat |  | ||||||
| # - přidat do views (prihlaskaView, resitelEditView) |  | ||||||
| # - přidat do forms |  | ||||||
| # - includovat do html |  | ||||||
| 
 |  | ||||||
| --> |  | ||||||
| 
 |  | ||||||
| {% block content %} |  | ||||||
| <h1> |  | ||||||
|   {% block nadpis1a %}{% block nadpis1b %} |  | ||||||
|     Přihláška do semináře |  | ||||||
|   {% endblock %}{% endblock %} |  | ||||||
| </h1> |  | ||||||
| 
 |  | ||||||
| <p><b>Tučně</b> popsaná pole jsou povinná.</p> |  | ||||||
| 
 |  | ||||||
| <form action="{% url 'seminar_prihlaska' %}" method="post"> |  | ||||||
|   {% csrf_token %} |  | ||||||
|   {{form.non_field_errors}} |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| <hr> |  | ||||||
|          <h4> |  | ||||||
|           Přihlašovací údaje |  | ||||||
|          </h4> |  | ||||||
|          <table class="form"> |  | ||||||
|            {% include "seminar/profil/prihlaska_field.html" with field=form.username %} |  | ||||||
| {#           {% include "seminar/profil/prihlaska_field.html" with field=form.password %}#} |  | ||||||
| {#           {% include "seminar/profil/prihlaska_field.html" with field=form.password_check %}#} |  | ||||||
|          </table> |  | ||||||
| 
 |  | ||||||
| <hr> |  | ||||||
| 
 |  | ||||||
|         <h4> |  | ||||||
|          Osobní údaje |  | ||||||
|         </h4> |  | ||||||
|           <table class="form"> |  | ||||||
|             {% include "seminar/profil/prihlaska_field.html" with field=form.jmeno %} |  | ||||||
|             {% include "seminar/profil/prihlaska_field.html" with field=form.prijmeni %} |  | ||||||
|             {% include "seminar/profil/prihlaska_field.html" with field=form.pohlavi_muz%} |  | ||||||
|             {% include "seminar/profil/prihlaska_field.html" with field=form.email %} |  | ||||||
|             {% include "seminar/profil/prihlaska_field.html" with field=form.telefon %} |  | ||||||
|             {% include "seminar/profil/prihlaska_field.html" with field=form.datum_narozeni %} |  | ||||||
|          </table> |  | ||||||
| 
 |  | ||||||
| <hr> |  | ||||||
| 
 |  | ||||||
|         <h4> |  | ||||||
|           Bydliště |  | ||||||
|         </h4> |  | ||||||
|           <table class="form"> |  | ||||||
|            {% include "seminar/profil/prihlaska_field.html" with field=form.ulice %} |  | ||||||
|            {% include "seminar/profil/prihlaska_field.html" with field=form.mesto %} |  | ||||||
|            {% include "seminar/profil/prihlaska_field.html" with field=form.psc %} |  | ||||||
|            {% include "seminar/profil/prihlaska_field.html" with field=form.stat %} |  | ||||||
|            {% include "seminar/profil/prihlaska_field.html" with field=form.stat_text id="id_li_stat_text"%} |  | ||||||
|          </table> |  | ||||||
| 
 |  | ||||||
| <hr> |  | ||||||
| 
 |  | ||||||
|         <h4> |  | ||||||
|          Škola |  | ||||||
|         </h4> |  | ||||||
|          <table class="form"> |  | ||||||
|            {% include "seminar/profil/prihlaska_field.html" with field=form.skola %} |  | ||||||
|            <tr><td colspan="2" ><button id="id_skola_text_button" type="button">Škola není v seznamu</button></td></tr> |  | ||||||
|            <tr><td id="id_li_skola_vypln" colspan="2">Vyplň prosím celý název a adresu školy.</td></tr> |  | ||||||
|            {% include "seminar/profil/prihlaska_field.html" with field=form.skola_nazev id="id_li_skola_nazev" %} |  | ||||||
|            {% include "seminar/profil/prihlaska_field.html" with field=form.skola_adresa id="id_li_skola_adresa" %} |  | ||||||
|            {% include "seminar/profil/prihlaska_field.html" with field=form.rok_maturity %} |  | ||||||
|          </table> |  | ||||||
| 
 |  | ||||||
| <hr> |  | ||||||
| 
 |  | ||||||
|         <h4> |  | ||||||
|          Pošta |  | ||||||
|         </h4> |  | ||||||
|          <table class="form"> |  | ||||||
|            {% include "seminar/profil/prihlaska_field.html" with field=form.zasilat %} |  | ||||||
|            {% include "seminar/profil/prihlaska_field.html" with field=form.zasilat_cislo_emailem %} |  | ||||||
|          </table> |  | ||||||
|  <hr> |  | ||||||
| 
 |  | ||||||
|          <h4> |  | ||||||
|           GDPR |  | ||||||
|          </h4> |  | ||||||
|           {% include "seminar/profil/gdpr.html" %} |  | ||||||
|           <table class="form"> |  | ||||||
|             {% include "seminar/profil/prihlaska_field.html" with field=form.gdpr %} |  | ||||||
|           </table> |  | ||||||
| 
 |  | ||||||
| <hr> |  | ||||||
| 
 |  | ||||||
|         <h4> |  | ||||||
|          Zasílání propagačních materiálů |  | ||||||
|         </h4> |  | ||||||
|          <table class="form"> |  | ||||||
|            {% include "seminar/profil/prihlaska_field.html" with field=form.spam %} |  | ||||||
|          </table> |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| <hr> |  | ||||||
| 
 |  | ||||||
|     <input type="submit" value="Odeslat"> |  | ||||||
| </form> |  | ||||||
| <script> |  | ||||||
| $("#id_stat").on("change",addrCountryChanged); |  | ||||||
| $("#id_skola_text_button").on("click",schoolNotInList); |  | ||||||
| </script> |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| {% endblock %} |  | ||||||
|  | @ -9,7 +9,7 @@ | ||||||
|   </h1> |   </h1> | ||||||
| 
 | 
 | ||||||
|   {% if radky_vysledkovky %} |   {% if radky_vysledkovky %} | ||||||
|     {% include "seminar/vysledkovka_rocnik.html" %} |       {% include "vysledkovky/vysledkovka_rocnik.html" %} | ||||||
|   {% else %} |   {% else %} | ||||||
|     <p>V tomto ročníku zatím žádné výsledky nejsou.</p> |     <p>V tomto ročníku zatím žádné výsledky nejsou.</p> | ||||||
|   {% endif %} |   {% endif %} | ||||||
|  | @ -22,11 +22,7 @@ | ||||||
|   {% if user.je_org and vysledkovka_s_neverejnymi %} |   {% if user.je_org and vysledkovka_s_neverejnymi %} | ||||||
|     <div class='mam-org-only'> |     <div class='mam-org-only'> | ||||||
|     <h1>Výsledky včetně neveřejných</h1> |     <h1>Výsledky včetně neveřejných</h1> | ||||||
|     {% with vysledkovka_s_neverejnymi as radky_vysledkovky %} |         {% include "vysledkovky/vysledkovka_rocnik_neverejna.html" %} | ||||||
|         {% with cisla_s_neverejnymi as cisla %} |  | ||||||
|       {% include "seminar/vysledkovka_rocnik.html" %} |  | ||||||
|         {% endwith %} |  | ||||||
|     {% endwith %} |  | ||||||
|     </div> |     </div> | ||||||
|   {% endif %} |   {% endif %} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,15 +0,0 @@ | ||||||
| from django import template |  | ||||||
| 
 |  | ||||||
| from seminar.models import Rocnik |  | ||||||
| 
 |  | ||||||
| register = template.Library() |  | ||||||
| 
 |  | ||||||
| @register.inclusion_tag('results.html') |  | ||||||
| def seminar_rocniky(parser, token): |  | ||||||
|     return { |  | ||||||
|         'rocniky': Rocnik.objects.all() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| @register.simple_tag |  | ||||||
| def aktualni_rocniky(): |  | ||||||
|     return Rocnik.objects.all() |  | ||||||
|  | @ -17,7 +17,7 @@ import seminar.models as m | ||||||
| 
 | 
 | ||||||
| from django.contrib.flatpages.models import FlatPage | from django.contrib.flatpages.models import FlatPage | ||||||
| from django.contrib.sites.models import Site | from django.contrib.sites.models import Site | ||||||
| from seminar.treelib import all_children, insert_last_child, all_children_of_type, create_node_after | from treenode.treelib import all_children, insert_last_child, all_children_of_type, create_node_after | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| User = django.contrib.auth.get_user_model() | User = django.contrib.auth.get_user_model() | ||||||
|  | @ -753,7 +753,7 @@ def gen_clanek(rnd, organizatori, resitele): | ||||||
| 	reseni.text_cely = reseninode | 	reseni.text_cely = reseninode | ||||||
| 	reseni.save() | 	reseni.save() | ||||||
| 
 | 
 | ||||||
| 	from seminar.treelib import insert_last_child, create_child | 	from treenode.treelib import insert_last_child, create_child | ||||||
| 	insert_last_child(cislonode, reseninode) | 	insert_last_child(cislonode, reseninode) | ||||||
| 
 | 
 | ||||||
| 	# Vyrobíme nějaký obsah | 	# Vyrobíme nějaký obsah | ||||||
|  |  | ||||||
|  | @ -1,7 +1,6 @@ | ||||||
| from django.urls import path, include, re_path | from django.urls import path, include, re_path | ||||||
| from django.contrib.auth.decorators import login_required |  | ||||||
| from . import views | from . import views | ||||||
| from .utils import org_required, resitel_required, viewMethodSwitch, resitel_or_org_required | from .utils import org_required | ||||||
| 
 | 
 | ||||||
| urlpatterns = [ | urlpatterns = [ | ||||||
| #	path('aktualni/temata/', views.TemataRozcestnikView), | #	path('aktualni/temata/', views.TemataRozcestnikView), | ||||||
|  | @ -18,42 +17,6 @@ urlpatterns = [ | ||||||
| 	path('rocnik/<int:rocnik>/', views.RocnikView.as_view(), name='seminar_rocnik'), | 	path('rocnik/<int:rocnik>/', views.RocnikView.as_view(), name='seminar_rocnik'), | ||||||
| 	path('cislo/<int:rocnik>.<str:cislo>/', views.CisloView.as_view(), name='seminar_cislo'), | 	path('cislo/<int:rocnik>.<str:cislo>/', views.CisloView.as_view(), name='seminar_cislo'), | ||||||
| 	path('problem/<int:pk>/', views.problemView, name='seminar_problem'), | 	path('problem/<int:pk>/', views.problemView, name='seminar_problem'), | ||||||
| 	#path('treenode/<int:pk>/', views.TreeNodeView.as_view(), name='seminar_treenode'), |  | ||||||
| 	#path('treenode/<int:pk>/json/', views.TreeNodeJSONView.as_view(), name='seminar_treenode_json'), |  | ||||||
| 	#path('treenode/text/<int:pk>/', views.TextWebView.as_view(), name='seminar_textnode_web'), |  | ||||||
| 	#path('treenode/editor/pridat/<str:co>/<int:pk>/<str:kam>/', views.TreeNodePridatView.as_view(), name='treenode_pridat'), |  | ||||||
| 	#path('treenode/editor/smazat/<int:pk>/', views.TreeNodeSmazatView.as_view(), name='treenode_smazat'), |  | ||||||
| 	#path('treenode/editor/odvesitpryc/<int:pk>/', views.TreeNodeOdvesitPrycView.as_view(), name='treenode_odvesitpryc'), |  | ||||||
| 	#path('treenode/editor/podvesit/<int:pk>/<str:kam>/', views.TreeNodePodvesitView.as_view(), name='treenode_podvesit'), |  | ||||||
| 	#path('treenode/editor/prohodit/<int:pk>/', views.TreeNodeProhoditView.as_view(), name='treenode_prohodit'), |  | ||||||
| 	#path('treenode/sirotcinec/', views.SirotcinecView.as_view(), name='seminar_treenode_sirotcinec'), |  | ||||||
| 	#path('problem/(?P<pk>\d+)/(?P<prispevek>\d+)/', views.PrispevekView.as_view(), name='seminar_problem_prispevek'), |  | ||||||
| 
 |  | ||||||
| 	# Soustredeni |  | ||||||
| 	path( |  | ||||||
| 		'soustredeni/probehlo/', |  | ||||||
| 		views.SoustredeniListView.as_view(), |  | ||||||
| 		name='seminar_seznam_soustredeni' |  | ||||||
| 	), |  | ||||||
| 	path( |  | ||||||
| 		'soustredeni/<int:soustredeni>/seznam_ucastniku', |  | ||||||
| 		org_required(views.SoustredeniUcastniciView.as_view()), |  | ||||||
| 		name='soustredeni_ucastnici' |  | ||||||
| 	), |  | ||||||
| 	path( |  | ||||||
| 		'soustredeni/<int:soustredeni>/maily_ucastniku', |  | ||||||
| 		org_required(views.SoustredeniMailyUcastnikuView.as_view()), |  | ||||||
| 		name='maily_ucastniku' |  | ||||||
| 	), |  | ||||||
| 	path( |  | ||||||
| 		'soustredeni/<int:soustredeni>/export_ucastniku', |  | ||||||
| 		org_required(views.soustredeniUcastniciExportView), |  | ||||||
| 		name='soustredeni_ucastnici_export' |  | ||||||
| 	), |  | ||||||
| 	path( |  | ||||||
| 		'soustredeni/<int:soustredeni>/fotogalerie/', |  | ||||||
| 		include('galerie.urls') |  | ||||||
| 	), |  | ||||||
| 
 | 
 | ||||||
| 	# Zadani | 	# Zadani | ||||||
| #	path('aktualni/zadani/', views.AktualniZadaniView.as_view(), name='seminar_aktualni_zadani'), # Dočasně ad-hoc jednoduchá věc. | #	path('aktualni/zadani/', views.AktualniZadaniView.as_view(), name='seminar_aktualni_zadani'), # Dočasně ad-hoc jednoduchá věc. | ||||||
|  | @ -102,46 +65,7 @@ urlpatterns = [ | ||||||
| 		'cislo/<int:trocnik>.<str:tcislo>/odmeny/<int:frocnik>.<str:fcislo>/', | 		'cislo/<int:trocnik>.<str:tcislo>/odmeny/<int:frocnik>.<str:fcislo>/', | ||||||
| 		org_required(views.OdmenyView.as_view()), | 		org_required(views.OdmenyView.as_view()), | ||||||
| 		name="seminar_archiv_odmeny"), | 		name="seminar_archiv_odmeny"), | ||||||
| 	path( |  | ||||||
| 		'soustredeni/<int:soustredeni>/obalky.pdf', |  | ||||||
| 		org_required(views.soustredeniObalkyView), |  | ||||||
| 		name='seminar_soustredeni_obalky' |  | ||||||
| 	), |  | ||||||
| 	# příprava na nestatický orgorozcestník |  | ||||||
| 	path( |  | ||||||
| 		'org/rozcestnik/', |  | ||||||
| 		org_required(views.OrgoRozcestnikView.as_view()), |  | ||||||
| 		name='seminar_org_rozcestnik' |  | ||||||
| 	), |  | ||||||
| 
 |  | ||||||
| 	path('prihlaska/',views.prihlaskaView, name='seminar_prihlaska'), |  | ||||||
| 
 |  | ||||||
| 	path('resitel/odevzdana_reseni/', resitel_or_org_required(views.PrehledOdevzdanychReseni.as_view()), name='seminar_resitel_odevzdana_reseni'), |  | ||||||
| 
 |  | ||||||
| 	path( |  | ||||||
| 		'resitel/osobni-udaje/', |  | ||||||
| 		login_required(views.resitelEditView), |  | ||||||
| 		name='seminar_resitel_edit' |  | ||||||
| 	), |  | ||||||
| 
 |  | ||||||
| 	# Obecný view na profil -- orgům dá rozcestník, řešitelům jejich stránku |  | ||||||
| 	path('profil/', views.profilView, name='profil'), |  | ||||||
| 
 |  | ||||||
| 	path('org/add_solution', org_required(views.AddSolutionView.as_view()), name='seminar_vloz_reseni'), |  | ||||||
| 	path('resitel/nahraj_reseni', resitel_required(views.NahrajReseniView.as_view()), name='seminar_nahraj_reseni'), |  | ||||||
| 
 |  | ||||||
| 	re_path(r'^temp/vue/.*$',views.VueTestView.as_view(),name='vue_test_view'), |  | ||||||
| 	path('temp/image_upload/', views.NahrajObrazekKTreeNoduView.as_view()), |  | ||||||
| 
 | 
 | ||||||
| 	path('', views.TitulniStranaView.as_view(), name='titulni_strana'), | 	path('', views.TitulniStranaView.as_view(), name='titulni_strana'), | ||||||
| 	path('jak-resit/', views.JakResitView.as_view(), name='jak_resit'), | 	path('jak-resit/', views.JakResitView.as_view(), name='jak_resit'), | ||||||
| 
 |  | ||||||
| 	path('org/reseni/', org_required(views.TabulkaOdevzdanychReseniView.as_view()), name='odevzdavatko_tabulka'), |  | ||||||
| 	path('org/reseni/rocnik/<int:rocnik>/', org_required(views.TabulkaOdevzdanychReseniView.as_view()), name='odevzdavatko_tabulka'), |  | ||||||
| 	path('org/reseni/<int:problem>/<int:resitel>/', org_required(views.ReseniProblemuView.as_view()), name='odevzdavatko_reseni_resitele_k_problemu'), |  | ||||||
| 	path('org/reseni/<int:pk>', org_required(viewMethodSwitch(get=views.DetailReseniView.as_view(), post=views.hodnoceniReseniView)), name='odevzdavatko_detail_reseni'), |  | ||||||
| 	path('org/reseni/all', org_required(views.SeznamReseniView.as_view())), |  | ||||||
| 	path('org/reseni/akt', org_required(views.SeznamAktualnichReseniView.as_view())), |  | ||||||
| 
 |  | ||||||
| 	path('resitel/reseni/<int:pk>', resitel_or_org_required(views.ResitelReseniView.as_view()), name='odevzdavatko_resitel_reseni'), |  | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | @ -20,7 +20,7 @@ from enum import auto | ||||||
| import logging | import logging | ||||||
| 
 | 
 | ||||||
| import seminar.models as m | import seminar.models as m | ||||||
| import seminar.treelib as t | import treenode.treelib as t | ||||||
| 
 | 
 | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,4 @@ | ||||||
| from .views_all import * | from .views_all import * | ||||||
| from .views_rest import * |  | ||||||
| from .odevzdavatko import * |  | ||||||
| 
 | 
 | ||||||
| # Dočsasné views | # Dočsasné views | ||||||
| from .docasne import * | from .docasne import * | ||||||
|  |  | ||||||
|  | @ -1,35 +1,26 @@ | ||||||
| from django.shortcuts import get_object_or_404, render, redirect | from django.shortcuts import get_object_or_404, render | ||||||
| from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, JsonResponse | from django.http import HttpResponse | ||||||
| from django.urls import reverse,reverse_lazy | from django.urls import reverse | ||||||
| from django.core.exceptions import PermissionDenied, ObjectDoesNotExist | from django.core.exceptions import ObjectDoesNotExist | ||||||
| from django.core.mail import send_mail |  | ||||||
| from django.views import generic | from django.views import generic | ||||||
| from django.utils.translation import ugettext as _ | from django.utils.translation import ugettext as _ | ||||||
| from django.http import Http404,HttpResponseBadRequest,HttpResponseRedirect | from django.http import Http404 | ||||||
| from django.db.models import Q, Sum, Count | from django.db.models import Q, Sum, Count | ||||||
| from django.views.decorators.csrf import ensure_csrf_cookie | from django.views.generic.base import RedirectView | ||||||
| from django.views.decorators.debug import sensitive_post_parameters |  | ||||||
| from django.views.generic.edit import FormView, CreateView |  | ||||||
| from django.views.generic.base import TemplateView, RedirectView |  | ||||||
| from django.contrib.auth.models import User, Permission, Group |  | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin |  | ||||||
| from django.db import transaction |  | ||||||
| from django.core import serializers |  | ||||||
| from django.core.exceptions import PermissionDenied | from django.core.exceptions import PermissionDenied | ||||||
| from django.forms.models import model_to_dict |  | ||||||
| 
 | 
 | ||||||
| import seminar.models as s | import seminar.models as s | ||||||
| import seminar.models as m | import seminar.models as m | ||||||
| from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni, Organizator, Resitel, Novinky, Soustredeni_Ucastnici, Pohadka, Tema, Clanek, Osoba, Skola # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci | from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Organizator, Resitel, Novinky, Tema, Clanek # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci | ||||||
| #from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva | #from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva | ||||||
| from seminar import utils, treelib | from seminar import utils | ||||||
| from seminar.forms import PrihlaskaForm, ProfileEditForm, PoMaturiteProfileEditForm | from treenode import treelib | ||||||
| import seminar.forms as f | import treenode.templatetags as tnltt | ||||||
| import seminar.templatetags.treenodes as tnltt | import treenode.serializers as vr | ||||||
| import seminar.views.views_rest as vr | from vysledkovky.utils import body_resitelu | ||||||
| from seminar.views.vysledkovka import vysledkovka_rocniku, vysledkovka_cisla, body_resitelu | from vysledkovky.views import vysledkovka_rocniku, vysledkovka_cisla | ||||||
| 
 | 
 | ||||||
| from datetime import timedelta, date, datetime, MAXYEAR | from datetime import date, datetime | ||||||
| from django.utils import timezone | from django.utils import timezone | ||||||
| from itertools import groupby | from itertools import groupby | ||||||
| from collections import OrderedDict | from collections import OrderedDict | ||||||
|  | @ -40,16 +31,10 @@ import os | ||||||
| import os.path as op | import os.path as op | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| import unicodedata | import unicodedata | ||||||
| import json |  | ||||||
| import traceback |  | ||||||
| import sys |  | ||||||
| import csv |  | ||||||
| import logging | import logging | ||||||
| import time | import time | ||||||
| 
 | 
 | ||||||
| from seminar.utils import aktivniResitele, resi_v_rocniku, problemy_rocniku, cisla_rocniku, hlavni_problemy_f | from seminar.utils import aktivniResitele | ||||||
| from various.autentizace.views import LoginView |  | ||||||
| from various.autentizace.utils import posli_reset_hesla |  | ||||||
| 
 | 
 | ||||||
| # ze starého modelu | # ze starého modelu | ||||||
| #def verejna_temata(rocnik): | #def verejna_temata(rocnik): | ||||||
|  | @ -221,141 +206,6 @@ class TNLData(object): | ||||||
| 	def __repr__(self): | 	def __repr__(self): | ||||||
| 		return("TNL({})".format(self.node)) | 		return("TNL({})".format(self.node)) | ||||||
| 
 | 
 | ||||||
| class TreeNodeView(generic.DetailView): |  | ||||||
| 	model = s.TreeNode |  | ||||||
| 	template_name = 'seminar/treenode.html' |  | ||||||
| 
 |  | ||||||
| 	def get_context_data(self,**kwargs): |  | ||||||
| 		context = super().get_context_data(**kwargs) |  | ||||||
| 		context['tnldata'] = TNLData.from_treenode(self.object,self.request.user) |  | ||||||
| 		return context |  | ||||||
| 
 |  | ||||||
| class TreeNodeJSONView(generic.DetailView): |  | ||||||
| 	model = s.TreeNode |  | ||||||
| 
 |  | ||||||
| 	def get(self,request,*args, **kwargs): |  | ||||||
| 		self.object = self.get_object() |  | ||||||
| 		data = TNLData.from_treenode(self.object,self.request.user).to_json() |  | ||||||
| 		return JsonResponse(data) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class TreeNodePridatView(generic.View): |  | ||||||
| 	type_from_str = { |  | ||||||
| 		'rocnikNode': m.RocnikNode, |  | ||||||
| 		'cisloNode': m.CisloNode, |  | ||||||
| 		'castNode': m.CastNode, |  | ||||||
| 		'textNode': m.TextNode, |  | ||||||
| 		'temaVCisleNode': m.TemaVCisleNode, |  | ||||||
| 		'reseniNode': m.ReseniNode, |  | ||||||
| 		'ulohaZadaniNode': m.UlohaZadaniNode, |  | ||||||
| 		'ulohaVzorakNode': m.UlohaVzorakNode, |  | ||||||
| 		'pohadkaNode': m.PohadkaNode, |  | ||||||
| 		'orgText': m.OrgTextNode, |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 	def post(self, request, *args, **kwargs): |  | ||||||
| 		######## FIXME: ROZEPSANE, NEFUNGUJE, DOPSAT !!!!!! ########### |  | ||||||
| 		node = s.TreeNode.objects.get(pk=self.kwargs['pk']) |  | ||||||
| 		kam = self.kwargs['kam'] |  | ||||||
| 		co = self.kwargs['co'] |  | ||||||
| 		typ = self.type_from_str[co] |  | ||||||
| 
 |  | ||||||
| 		raise NotImplementedError('Neni to dopsane, dopis to!') |  | ||||||
| 
 |  | ||||||
| 		if kam not in ('pred','syn','za'): |  | ||||||
| 			raise ValidationError('Přidat lze pouze před nebo za node nebo jako syna') |  | ||||||
| 
 |  | ||||||
| 		if co == m.TextNode: |  | ||||||
| 			new_obj = m.Text() |  | ||||||
| 			new_obj.save() |  | ||||||
| 		elif co == m.CastNode: |  | ||||||
| 			new_obj = m.CastNode() |  | ||||||
| 			new_obj.nadpis = request.POST.get('pridat-castNode-{}-{}'.format(node.id,kam)) |  | ||||||
| 			new_obj.save() |  | ||||||
| 		elif co == m.ReseniNode: |  | ||||||
| 			new_obj = m |  | ||||||
| 			pass |  | ||||||
| 		elif co == m.UlohaZadaniNode: |  | ||||||
| 			pass |  | ||||||
| 		elif co == m.UlohaReseniNode: |  | ||||||
| 			pass |  | ||||||
| 		else: |  | ||||||
| 			new_obj = None |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 		if kam == 'pred': |  | ||||||
| 			pass |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 		if kam == 'syn': |  | ||||||
| 			if typ == m.TextNode: |  | ||||||
| 				text_obj = m.Text() |  | ||||||
| 				text_obj.save() |  | ||||||
| 				node = treelib.create_child(node,typ,text=text_obj) |  | ||||||
| 			else: |  | ||||||
| 				node = treelib.create_child(node,typ) |  | ||||||
| 		if kam == 'za': |  | ||||||
| 			if typ == m.TextNode: |  | ||||||
| 				text_obj = m.Text() |  | ||||||
| 				text_obj.save() |  | ||||||
| 				node = treelib.create_node_after(node,typ,text=text_obj) |  | ||||||
| 			else: |  | ||||||
| 				node = treelib.create_node_after(node,typ) |  | ||||||
| 
 |  | ||||||
| 		return redirect(node.get_admin_url()) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class TreeNodeSmazatView(generic.base.View): |  | ||||||
| 	def post(self, request, *args, **kwargs): |  | ||||||
| 		node = s.TreeNode.objects.get(pk=self.kwargs['pk']) |  | ||||||
| 		if node.first_child: |  | ||||||
| 			raise NotImplementedError('Mazání TreeNode se syny není zatím podporováno!') |  | ||||||
| 		treelib.disconnect_node(node) |  | ||||||
| 		node.delete() |  | ||||||
| 		return redirect(request.headers.get('referer')) |  | ||||||
| 
 |  | ||||||
| class TreeNodeOdvesitPrycView(generic.base.View): |  | ||||||
| 	def post(self, request, *args, **kwargs): |  | ||||||
| 		node = s.TreeNode.objects.get(pk=self.kwargs['pk']) |  | ||||||
| 		treelib.disconnect_node(node) |  | ||||||
| 		node.root = None |  | ||||||
| 		node.save() |  | ||||||
| 		return redirect(request.headers.get('referer')) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class TreeNodePodvesitView(generic.base.View): |  | ||||||
| 	def post(self, request, *args, **kwargs): |  | ||||||
| 		node = s.TreeNode.objects.get(pk=self.kwargs['pk']) |  | ||||||
| 		kam = self.kwargs['kam'] |  | ||||||
| 		if kam == 'pred': |  | ||||||
| 			treelib.lower_node(node) |  | ||||||
| 		elif kam == 'za': |  | ||||||
| 			raise NotImplementedError('Podvěsit za není zatím podporováno') |  | ||||||
| 		return redirect(request.headers.get('referer')) |  | ||||||
| 
 |  | ||||||
| class TreeNodeProhoditView(generic.base.View): |  | ||||||
| 	def post(self, request, *args, **kwargs): |  | ||||||
| 		node = s.TreeNode.objects.get(pk=self.kwargs['pk']) |  | ||||||
| 		treelib.swap_succ(node) |  | ||||||
| 		return redirect(request.headers.get('referer')) |  | ||||||
| 		#FIXME ve formulari predat puvodni url a vratit redirect na ni |  | ||||||
| 
 |  | ||||||
| class SirotcinecView(generic.ListView): |  | ||||||
| 	model = s.TreeNode |  | ||||||
| 	template_name = 'seminar/orphanage.html' |  | ||||||
| 
 |  | ||||||
| 	def get_queryset(self): |  | ||||||
| 		return s.TreeNode.objects.not_instance_of(s.RocnikNode).filter(root=None,prev=None,succ=None,father_of_first=None) |  | ||||||
| 
 |  | ||||||
| # FIXME pouzit Django REST Framework |  | ||||||
| class TextWebView(generic.DetailView): |  | ||||||
| 	model = s.Text |  | ||||||
| 
 |  | ||||||
| 	def get(self,request,*args, **kwargs): |  | ||||||
| 		self.object = self.get_object() |  | ||||||
| 		return JsonResponse(model_to_dict(self.object,exclude='do_cisla')) |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| # FIXME: Pozor, níž je ještě jeden ProblemView! | # FIXME: Pozor, níž je ještě jeden ProblemView! | ||||||
| #class ProblemView(generic.DetailView): | #class ProblemView(generic.DetailView): | ||||||
|  | @ -494,31 +344,34 @@ def ZadaniAktualniVysledkovkaView(request): | ||||||
| 	nastaveni = get_object_or_404(Nastaveni) | 	nastaveni = get_object_or_404(Nastaveni) | ||||||
| 	# Aktualni verejna vysledkovka | 	# Aktualni verejna vysledkovka | ||||||
| 	rocnik = nastaveni.aktualni_rocnik | 	rocnik = nastaveni.aktualni_rocnik | ||||||
| 	vysledkovka = vysledkovka_rocniku(rocnik) | 	context = vysledkovka_rocniku( | ||||||
| 	cisla = cisla_rocniku(rocnik) | 		rocnik=rocnik, | ||||||
|  | 		request=request, | ||||||
|  | 		sneverejnou=True | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
| 	# kdyz neni verejna vysledkovka, tak zobraz starou | 	# kdyz neni verejna vysledkovka, tak zobraz starou | ||||||
| 	if not vysledkovka or not any(map(lambda it: it.verejna_vysledkovka, cisla)): | 	if len(context['cisla']) == 0: | ||||||
| 		try: | 		try: | ||||||
| 			minuly_rocnik = Rocnik.objects.get( | 			minuly_rocnik = Rocnik.objects.get( | ||||||
| 				prvni_rok=(rocnik.prvni_rok-1)) | 				prvni_rok=(rocnik.prvni_rok-1)) | ||||||
| 			rocnik = minuly_rocnik | 			rocnik = minuly_rocnik | ||||||
| 			vysledkovka = vysledkovka_rocniku(minuly_rocnik) | 
 | ||||||
| 			cisla = cisla_rocniku(minuly_rocnik) | 			# Přepíšeme prázdnou výsledkovku výsledkovkou z minulého ročníku | ||||||
|  | 			context = vysledkovka_rocniku( | ||||||
|  | 				rocnik=rocnik, | ||||||
|  | 				context=context, | ||||||
|  | 				request=request, | ||||||
|  | 				sneverejnou=True | ||||||
|  | 			) | ||||||
| 		except ObjectDoesNotExist: | 		except ObjectDoesNotExist: | ||||||
| 			pass | 			pass | ||||||
| 	# vysledkovka s neverejnyma vysledkama | 
 | ||||||
| 	vysledkovka_s_neverejnymi = vysledkovka_rocniku(nastaveni.aktualni_rocnik, jen_verejne=False) | 	context['rocnik'] = rocnik | ||||||
| 	cisla_s_neverejnymi = cisla_rocniku(nastaveni.aktualni_rocnik, jen_verejne=False) |  | ||||||
| 	return render( | 	return render( | ||||||
| 		request, | 		request, | ||||||
| 		'seminar/zadani/AktualniVysledkovka.html', | 		'seminar/zadani/AktualniVysledkovka.html', | ||||||
| 		{ | 		context | ||||||
| 			'rocnik': rocnik, |  | ||||||
| 			'radky_vysledkovky': vysledkovka, |  | ||||||
| 			'cisla': cisla, |  | ||||||
| 			'vysledkovka_s_neverejnymi': vysledkovka_s_neverejnymi, |  | ||||||
| 			'cisla_s_neverejnymi': cisla_s_neverejnymi, |  | ||||||
| 		} |  | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -664,18 +517,12 @@ class RocnikView(generic.DetailView): | ||||||
| 	def get_context_data(self, **kwargs): | 	def get_context_data(self, **kwargs): | ||||||
| 		start = time.time() | 		start = time.time() | ||||||
| 		context = super(RocnikView, self).get_context_data(**kwargs) | 		context = super(RocnikView, self).get_context_data(**kwargs) | ||||||
| 
 | 		context = vysledkovka_rocniku( | ||||||
| 		# vysledkovka = True zajistí vykreslení, | 			rocnik=context["rocnik"], | ||||||
| 		# zkontrolovat, kdy se má a nemá vykreslovat | 			context=context, | ||||||
| 		cisla = cisla_rocniku(context["rocnik"]) | 			request=self.request, | ||||||
| 		context['vysledkovka'] = any(map(lambda it: it.verejna_vysledkovka, cisla)) | 			sneverejnou=True | ||||||
| 		if self.request.user.je_org: | 		) | ||||||
| 			context['cisla_s_neverejnymi'] = cisla_rocniku(context["rocnik"], jen_verejne=False) |  | ||||||
| 			context['radky_vysledkovky_s_neverejnymi'] = vysledkovka_rocniku(context["rocnik"], jen_verejne=False) |  | ||||||
| 			context['hlavni_problemy_v_rocniku_s_neverejnymi'] = hlavni_problemy_f(problemy_rocniku(context["rocnik"], jen_verejne=False)) |  | ||||||
| 		context['cisla'] = cisla |  | ||||||
| 		context['radky_vysledkovky'] = vysledkovka_rocniku(context["rocnik"]) |  | ||||||
| 		context['hlavni_problemy_v_rocniku'] = hlavni_problemy_f(problemy_rocniku(context["rocnik"])) |  | ||||||
| 		end = time.time() | 		end = time.time() | ||||||
| 		print("Kontext:", end-start) | 		print("Kontext:", end-start) | ||||||
| 
 | 
 | ||||||
|  | @ -836,51 +683,6 @@ def oldObalkovaniView(request, rocnik, cislo): | ||||||
| 		{'cislo': cislo, 'problemy': problemy, 'reseni': reseni} | 		{'cislo': cislo, 'problemy': problemy, 'reseni': reseni} | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
| ### Orgostránky |  | ||||||
| 
 |  | ||||||
| class OrgoRozcestnikView(TemplateView): |  | ||||||
| 	''' Zobrazí organizátorský rozcestník.''' |  | ||||||
| 
 |  | ||||||
| 	template_name = 'seminar/orgorozcestnik.html' |  | ||||||
| 
 |  | ||||||
| 	def get_context_data(self, **kwargs): |  | ||||||
| 		context = super().get_context_data(**kwargs) |  | ||||||
| 		context['posledni_soustredeni'] = Soustredeni.objects.order_by('-datum_konce').first() |  | ||||||
| 		nastaveni = Nastaveni.objects.first() |  | ||||||
| 		aktualni_rocnik = nastaveni.aktualni_rocnik |  | ||||||
| 		context['posledni_cislo_url'] = nastaveni.aktualni_cislo.verejne_url() |  | ||||||
| 		# TODO možná chceme odkazovat na právě rozpracované číslo, a ne to poslední vydané |  | ||||||
| 		# pokud nechceme haluzit kód (= poradi) dalšího čísla, bude asi potřeba jít  |  | ||||||
| 		# přes treenody (a dát si přitom pozor na MezicisloNode) |  | ||||||
| 
 |  | ||||||
| 		neobodovana_reseni = s.Hodnoceni.objects.filter(body__isnull=True) |  | ||||||
| 		reseni_mimo_cislo = s.Hodnoceni.objects.filter(cislo_body__isnull=True) |  | ||||||
| 		context['pocet_neobodovanych_reseni'] = neobodovana_reseni.count() |  | ||||||
| 		context['pocet_reseni_mimo_cislo'] = reseni_mimo_cislo.count() |  | ||||||
| 
 |  | ||||||
| 		u = self.request.user |  | ||||||
| 		os = s.Osoba.objects.get(user=u) |  | ||||||
| 		organizator = s.Organizator.objects.get(osoba=os) |  | ||||||
| 
 |  | ||||||
| 		context['muj_pocet_neobodovanych_reseni'] = neobodovana_reseni.filter(Q(problem__garant=organizator) | Q(problem__autor=organizator) | Q(problem__opravovatele__in=[organizator])).distinct().count() |  | ||||||
| 		context['muj_pocet_reseni_mimo_cislo'] = reseni_mimo_cislo.filter(Q(problem__garant=organizator) | Q(problem__autor=organizator) | Q(problem__opravovatele__in=[organizator])).count() |  | ||||||
| 
 |  | ||||||
| 		#FIXME: přidat stav='STAV_ZADANY' |  | ||||||
| 		temata = s.Tema.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]), |  | ||||||
| 			rocnik=aktualni_rocnik).distinct() |  | ||||||
| 		ulohy = s.Uloha.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]), |  | ||||||
| 			cislo_zadani__rocnik=aktualni_rocnik).distinct() |  | ||||||
| 		clanky = s.Clanek.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]), |  | ||||||
| 			cislo__rocnik=aktualni_rocnik).distinct() |  | ||||||
| 
 |  | ||||||
| 		context['temata'] = temata |  | ||||||
| 		context['ulohy'] = ulohy |  | ||||||
| 		context['clanky'] = clanky |  | ||||||
| 		context['organizator'] = organizator |  | ||||||
| 		return context |  | ||||||
| 
 |  | ||||||
| 		#content_type = 'text/plain; charset=UTF8' |  | ||||||
| 	#XXX |  | ||||||
| 
 | 
 | ||||||
| ### Tituly | ### Tituly | ||||||
| 
 | 
 | ||||||
|  | @ -911,53 +713,6 @@ def TitulyView(request, rocnik, cislo): | ||||||
| 	return render(request, 'seminar/archiv/tituly.tex', | 	return render(request, 'seminar/archiv/tituly.tex', | ||||||
| 		{'resitele': resitele,'jmenovci':jmenovci},content_type="text/plain") | 		{'resitele': resitele,'jmenovci':jmenovci},content_type="text/plain") | ||||||
| 
 | 
 | ||||||
| ### Soustredeni |  | ||||||
| 
 |  | ||||||
| class SoustredeniListView(generic.ListView): |  | ||||||
| 	model = Soustredeni |  | ||||||
| 	template_name = 'seminar/soustredeni/seznam_soustredeni.html' |  | ||||||
| 
 |  | ||||||
| def soustredeniObalkyView(request,soustredeni): |  | ||||||
| 	soustredeni = get_object_or_404(Soustredeni,id = soustredeni) |  | ||||||
| 	return obalkyView(request,soustredeni.ucastnici.all()) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class SoustredeniUcastniciBaseView(generic.ListView): |  | ||||||
| 	model = Soustredeni_Ucastnici |  | ||||||
| 
 |  | ||||||
| 	def get_queryset(self): |  | ||||||
| 		soustredeni = get_object_or_404( |  | ||||||
| 			Soustredeni, |  | ||||||
| 			pk=self.kwargs["soustredeni"] |  | ||||||
| 		) |  | ||||||
| 		return Soustredeni_Ucastnici.objects.filter( |  | ||||||
| 			soustredeni=soustredeni).select_related('resitel') |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class SoustredeniMailyUcastnikuView(SoustredeniUcastniciBaseView): |  | ||||||
| 	""" Seznam e-mailů řešitelů oddělených čárkami. """ |  | ||||||
| 	model = Soustredeni_Ucastnici |  | ||||||
| 	template_name = 'seminar/soustredeni/maily_ucastniku.txt' |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class SoustredeniUcastniciView(SoustredeniUcastniciBaseView): |  | ||||||
| 	""" HTML tabulka účastníků pro tisk. """ |  | ||||||
| 	model = Soustredeni_Ucastnici |  | ||||||
| 	template_name = 'seminar/soustredeni/seznam_ucastniku.html' |  | ||||||
| 
 |  | ||||||
| def soustredeniUcastniciExportView(request,soustredeni): |  | ||||||
| 	soustredeni = get_object_or_404(Soustredeni,id = soustredeni) |  | ||||||
| 	ucastnici = Resitel.objects.filter(soustredeni=soustredeni) |  | ||||||
| 	response = HttpResponse(content_type='text/csv') |  | ||||||
| 	response['Content-Disposition'] = 'attachment; filename="ucastnici.csv"' |  | ||||||
| 
 |  | ||||||
| 	writer = csv.writer(response) |  | ||||||
| 	writer.writerow(["jmeno", "prijmeni", "rok_maturity", "telefon", "email", "ulice", "mesto", "psc","stat"]) |  | ||||||
| 	for u in ucastnici: |  | ||||||
| 		o = u.osoba |  | ||||||
| 		writer.writerow([o.jmeno, o.prijmeni, str(u.rok_maturity), o.telefon, o.email, o.ulice, o.mesto, o.psc, o.stat.name]) |  | ||||||
| 	return response |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| ### Články | ### Články | ||||||
| def group_by_rocnik(clanky): | def group_by_rocnik(clanky): | ||||||
|  | @ -1026,361 +781,6 @@ def StavDatabazeView(request): | ||||||
| 			}) | 			}) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ResitelView(LoginRequiredMixin,generic.DetailView): |  | ||||||
| 	model = Resitel |  | ||||||
| 	template_name = 'seminar/profil/resitel.html' |  | ||||||
| 
 |  | ||||||
| 	def get_object(self, queryset=None): |  | ||||||
| 		print(self.request.user) |  | ||||||
| 		return Resitel.objects.get(osoba__user=self.request.user) |  | ||||||
| 
 |  | ||||||
| ### Formulare |  | ||||||
| 
 |  | ||||||
| # pro přidání políčka do formuláře je potřeba |  | ||||||
| # - mít v modelu tu položku, kterou chci upravovat |  | ||||||
| # - přidat do views (prihlaskaView, resitelEditView) |  | ||||||
| # - přidat do forms |  | ||||||
| # - includovat do html |  | ||||||
| 
 |  | ||||||
| class AddSolutionView(LoginRequiredMixin, FormView): |  | ||||||
| 	template_name = 'seminar/org/vloz_reseni.html' |  | ||||||
| 	form_class = f.VlozReseniForm |  | ||||||
| 
 |  | ||||||
| 	def form_valid(self, form): |  | ||||||
| 		data = form.cleaned_data |  | ||||||
| 		nove_reseni = m.Reseni.objects.create( |  | ||||||
| 			cas_doruceni=data['cas_doruceni'], |  | ||||||
| 			forma=data['forma'], |  | ||||||
| 			poznamka=data['poznamka'], |  | ||||||
| 			) |  | ||||||
| 		nove_reseni.resitele.add(data['resitel']) |  | ||||||
| 		nove_reseni.problem.add(data['problem']) |  | ||||||
| 		nove_reseni.save() |  | ||||||
| 		# Chtěl jsem, aby bylo vidět, že se to uložilo, tak přesměrovávám na profil. |  | ||||||
| 		return redirect(reverse('profil')) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class NahrajReseniView(LoginRequiredMixin, CreateView): |  | ||||||
| 	model = s.Reseni |  | ||||||
| 	template_name = 'seminar/profil/nahraj_reseni.html' |  | ||||||
| 	form_class = f.NahrajReseniForm |  | ||||||
| 
 |  | ||||||
| 	def get(self, request, *args, **kwargs): |  | ||||||
| 		# Zaříznutí starých řešitelů: |  | ||||||
| 		# FIXME: Je to tady dost naprasené, mělo by to asi být jinde… |  | ||||||
| 		osoba = m.Osoba.objects.get(user=self.request.user) |  | ||||||
| 		resitel = osoba.resitel |  | ||||||
| 		if resitel.rok_maturity <= m.Nastaveni.get_solo().aktualni_rocnik.prvni_rok: |  | ||||||
| 			return render(request, 'universal.html', { |  | ||||||
| 				'title': 'Nelze odevzdat', |  | ||||||
| 				'error': 'Zdá se, že jsi již odmaturoval/a, a tedy nemůžeš odevzdat do našeho semináře řešení.', |  | ||||||
| 				'text': 'Pokud se ti zdá, že to je chyba, napiš nám prosím e-mail. Díky.', |  | ||||||
| 				}) |  | ||||||
| 		return super().get(request, *args, **kwargs) |  | ||||||
| 
 |  | ||||||
| 	def get_context_data(self,**kwargs): |  | ||||||
| 		data = super().get_context_data(**kwargs) |  | ||||||
| 		if self.request.POST: |  | ||||||
| 			data['prilohy'] = f.ReseniSPrilohamiFormSet(self.request.POST,self.request.FILES) |  | ||||||
| 		else: |  | ||||||
| 			data['prilohy'] = f.ReseniSPrilohamiFormSet() |  | ||||||
| 		return data |  | ||||||
| 
 |  | ||||||
| 	# FIXME prepsat tak, aby form_valid se volalo jen tehdy, kdyz je form i formset validni |  | ||||||
| 	# Inspirace: https://stackoverflow.com/questions/41599809/using-a-django-filefield-in-an-inline-formset |  | ||||||
| 	def form_valid(self,form): |  | ||||||
| 		context = self.get_context_data() |  | ||||||
| 		prilohy = context['prilohy'] |  | ||||||
| 		if not prilohy.is_valid(): |  | ||||||
| 			return super().form_invalid(form) |  | ||||||
| 		with transaction.atomic(): |  | ||||||
| 			self.object = form.save() |  | ||||||
| 			self.object.resitele.add(Resitel.objects.get(osoba__user = self.request.user)) |  | ||||||
| 			self.object.cas_doruceni = timezone.now() |  | ||||||
| 			self.object.forma = s.Reseni.FORMA_UPLOAD |  | ||||||
| 			self.object.save() |  | ||||||
| 
 |  | ||||||
| 			prilohy.instance = self.object |  | ||||||
| 			prilohy.save() |  | ||||||
| 
 |  | ||||||
| 		# Pošleme mail opravovatelům a garantovi |  | ||||||
| 		# FIXME: Nechat spočítat databázi? Je to pár dotazů (pravděpodobně), takže to za to možná nestojí |  | ||||||
| 		prijemci = set() |  | ||||||
| 		problemy = [] |  | ||||||
| 		for prob in form.cleaned_data['problem']: |  | ||||||
| 			prijemci.update(prob.opravovatele.all()) |  | ||||||
| 			if prob.garant is not None: |  | ||||||
| 				prijemci.add(prob.garant) |  | ||||||
| 			problemy.append(prob) |  | ||||||
| 			# FIXME: Možná poslat mail i relevantním orgům nadproblémů? |  | ||||||
| 		if len(prijemci) < 1: |  | ||||||
| 			logger.warning(f"Pozor, neposílám e-mail nikomu. Problémy: {problemy}") |  | ||||||
| 		# FIXME: Víc informativní obsah mailů, možná vč. příloh? |  | ||||||
| 		prijemci = map(lambda it: it.osoba.email, prijemci) |  | ||||||
| 
 |  | ||||||
| 		resitel = Osoba.objects.get(user = self.request.user) |  | ||||||
| 
 |  | ||||||
| 		seznam = "problému " + str(problemy[0]) if len(problemy) == 1 else 'následujícím problémům:\n' + ', \n'.join(map(str, problemy)) |  | ||||||
| 		seznam_do_subjectu = "problému " + str(problemy[0]) + ("" if len(problemy) == 1 else f" (a dalším { len(problemy) - 1 })") |  | ||||||
| 
 |  | ||||||
| 		send_mail( |  | ||||||
| 			subject="Nové řešení k " + seznam_do_subjectu, |  | ||||||
| 			message=f"Řešitel{ '' if resitel.pohlavi_muz else 'ka' } { resitel } právě nahrál{'' if resitel.pohlavi_muz else 'a' } nové řešení k { seznam }.\n\nHurá do opravování: { self.object.absolute_url() }", |  | ||||||
| 			from_email="submitovatko@mam.mff.cuni.cz", # FIXME: Chceme to mít radši tady, nebo v nastavení? |  | ||||||
| 			recipient_list=list(prijemci), |  | ||||||
| 			) |  | ||||||
| 
 |  | ||||||
| 		return formularOKView(self.request, text='Řešení úspěšně odevzdáno') |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def prihlaska_log_gdpr_safe(logger, gdpr_logger, msg, form_data): |  | ||||||
| 	msg = "{}, form_hash:{}".format(msg,hash(frozenset(form_data.items))) |  | ||||||
| 	logger.warn(msg) |  | ||||||
| 	gdpr_logger.warn(msg+", form:{}".format(form_data)) |  | ||||||
| 
 |  | ||||||
| from django.forms.models import model_to_dict |  | ||||||
| @sensitive_post_parameters('jmeno', 'prijmeni', 'email', 'telefon', 'datum_narozeni', 'ulice', 'mesto', 'psc', 'skola') |  | ||||||
| def resitelEditView(request): |  | ||||||
| 	err_logger = logging.getLogger('seminar.prihlaska.problem') |  | ||||||
| 	## Načtení objektů Osoba a Resitel patřících k aktuálně přihlášenému uživateli |  | ||||||
| 	u = request.user |  | ||||||
| 	osoba_edit = Osoba.objects.get(user=u) |  | ||||||
| 	if hasattr(osoba_edit,'resitel'): |  | ||||||
| 		resitel_edit = osoba_edit.resitel |  | ||||||
| 	else: |  | ||||||
| 		resitel_edit = None |  | ||||||
| 	user_edit = osoba_edit.user |  | ||||||
| 	## Vytvoření slovníku, kterým předvyplním formulář  |  | ||||||
| 	prefill_1=model_to_dict(user_edit) |  | ||||||
| 	if resitel_edit: |  | ||||||
| 		prefill_2=model_to_dict(resitel_edit) |  | ||||||
| 		prefill_1.update(prefill_2) |  | ||||||
| 	prefill_3=model_to_dict(osoba_edit) |  | ||||||
| 	prefill_1.update(prefill_3) |  | ||||||
| 	if 'datum_narozeni' in prefill_1: |  | ||||||
| 		prefill_1['datum_narozeni'] = str(prefill_1['datum_narozeni']) |  | ||||||
| 	if 'rok_maturity' not in prefill_1 or prefill_1['rok_maturity'] < date.today().year: |  | ||||||
| 		form = PoMaturiteProfileEditForm(initial=prefill_1) |  | ||||||
| 	else: |  | ||||||
| 		form = ProfileEditForm(initial=prefill_1) |  | ||||||
| 	## Změna údajů a jejich uložení |  | ||||||
| 	if request.method == 'POST': |  | ||||||
| 		POST = request.POST.copy() |  | ||||||
| 		POST["username"] = osoba_edit.user.username |  | ||||||
| 
 |  | ||||||
| 		if 'rok_maturity' not in prefill_1 or prefill_1['rok_maturity'] < date.today().year: |  | ||||||
| 			form = PoMaturiteProfileEditForm(POST) |  | ||||||
| 		else: |  | ||||||
| 			form = ProfileEditForm(POST) |  | ||||||
| 		form.username = user_edit.username |  | ||||||
| 		if form.is_valid(): |  | ||||||
| 			## Změny v osobě |  | ||||||
| 			fcd = form.cleaned_data |  | ||||||
| 			form_hash = hash(frozenset(fcd.items())) |  | ||||||
| 			form_logger = logging.getLogger('seminar.prihlaska.form') |  | ||||||
| 			form_logger.info("EDIT:" + str(fcd) + str(form_hash))  # TODO možná logovat jinak |  | ||||||
| 			osoba_edit.jmeno = fcd['jmeno'] |  | ||||||
| 			osoba_edit.prijmeni = fcd['prijmeni'] |  | ||||||
| 			osoba_edit.pohlavi_muz = fcd['pohlavi_muz'] |  | ||||||
| 			osoba_edit.email = fcd['email'] |  | ||||||
| 			osoba_edit.telefon = fcd['telefon'] |  | ||||||
| 			osoba_edit.ulice = fcd['ulice'] |  | ||||||
| 			osoba_edit.mesto = fcd['mesto'] |  | ||||||
| 			osoba_edit.psc = fcd['psc'] |  | ||||||
| 			osoba_edit.datum_narozeni = fcd['datum_narozeni'] |  | ||||||
| 			## Změny v osobě s podmínkami |  | ||||||
| 			if fcd.get('spam',False): |  | ||||||
| 				osoba_edit.datum_souhlasu_zasilani = date.today() |  | ||||||
| 			if fcd.get('stat','') in ('CZ','SK'): |  | ||||||
| 				osoba_edit.stat = fcd['stat'] |  | ||||||
| 			else: |  | ||||||
| 				## Neznámá země |  | ||||||
| 				msg = "Unknown country {}".format(fcd['stat_text']) |  | ||||||
| 
 |  | ||||||
| 			if resitel_edit: |  | ||||||
| 				## Změny v řešiteli |  | ||||||
| 				resitel_edit.skola = fcd['skola'] |  | ||||||
| 				resitel_edit.rok_maturity = fcd['rok_maturity'] |  | ||||||
| 				resitel_edit.zasilat = fcd['zasilat'] |  | ||||||
| 				resitel_edit.zasilat_cislo_emailem = fcd['zasilat_cislo_emailem'] |  | ||||||
| 				if fcd.get('skola'): |  | ||||||
| 					resitel_edit.skola = fcd['skola'] |  | ||||||
| 				else: |  | ||||||
| 					# Unknown school - log it |  | ||||||
| 					msg = "Unknown school {}, {}".format(fcd['skola_nazev'],fcd['skola_adresa']) |  | ||||||
| 				resitel_edit.save() |  | ||||||
| 			osoba_edit.save() |  | ||||||
| 			return formularOKView(request, text=f'Údaje byly úspěšně uloženy. <a href="{reverse("profil")}">Vrátit se zpět na profil.</a>') |  | ||||||
| 
 |  | ||||||
| 	return render(request, 'seminar/profil/edit.html', {'form': form}) |  | ||||||
| 
 |  | ||||||
| @sensitive_post_parameters('jmeno', 'prijmeni', 'email', 'telefon', 'datum_narozeni', 'ulice', 'mesto', 'psc', 'skola') |  | ||||||
| def prihlaskaView(request): |  | ||||||
| 	generic_logger = logging.getLogger('seminar.prihlaska') |  | ||||||
| 	err_logger = logging.getLogger('seminar.prihlaska.problem') |  | ||||||
| 	form_logger = logging.getLogger('seminar.prihlaska.form') |  | ||||||
| 	if request.method == 'POST': |  | ||||||
| 		form = PrihlaskaForm(request.POST) |  | ||||||
| 		# TODO vyresit, co se bude v jakych situacich zobrazovat |  | ||||||
| 		if form.is_valid(): |  | ||||||
| 			generic_logger.info("Form valid") |  | ||||||
| 			fcd = form.cleaned_data |  | ||||||
| 			form_hash = hash(frozenset(fcd.items())) |  | ||||||
| 			form_logger.info(str(fcd) + str(form_hash))  # TODO možná logovat jinak |  | ||||||
| 
 |  | ||||||
| 			with transaction.atomic(): |  | ||||||
| 				u = User.objects.create_user( |  | ||||||
| 					username=fcd['username'], |  | ||||||
| 					email = fcd['email']) |  | ||||||
| 				u.save() |  | ||||||
| 				resitel_perm = Permission.objects.filter(codename__exact='resitel').first() |  | ||||||
| 				u.user_permissions.add(resitel_perm) |  | ||||||
| 				resitel_grp = Group.objects.filter(name__exact='resitel').first() |  | ||||||
| 				u.groups.add(resitel_grp) |  | ||||||
| 
 |  | ||||||
| 				o = Osoba( |  | ||||||
| 					jmeno = fcd['jmeno'], |  | ||||||
| 					prijmeni = fcd['prijmeni'], |  | ||||||
| 					pohlavi_muz = fcd['pohlavi_muz'], |  | ||||||
| 					email = fcd['email'], |  | ||||||
| 					telefon = fcd.get('telefon',''), |  | ||||||
| 					datum_narozeni = fcd.get('datum_narozeni',None), |  | ||||||
| 					datum_souhlasu_udaje = date.today(), |  | ||||||
| 					datum_registrace = date.today(), |  | ||||||
| 					ulice = fcd.get('ulice',''), |  | ||||||
| 					mesto = fcd.get('mesto',''), |  | ||||||
| 					psc = fcd.get('psc',''), |  | ||||||
| 					poznamka = str(fcd) |  | ||||||
| 					) |  | ||||||
| 
 |  | ||||||
| 				if fcd.get('spam',False): |  | ||||||
| 					o.datum_souhlasu_zasilani = date.today() |  | ||||||
| 				if fcd.get('stat','') in ('CZ','SK'): |  | ||||||
| 					o.stat = fcd['stat'] |  | ||||||
| 				else: |  | ||||||
| 					# Unknown country - log it |  | ||||||
| 					msg = "Unknown country {}".format(fcd['stat_text']) |  | ||||||
| 					err_logger.warn(msg + str(form_hash)) |  | ||||||
| 
 |  | ||||||
| 				 |  | ||||||
| 				# Dovolujeme doregistraci uživatele pro existující mail, takže naopak chceme doplnit/aktualizovat údaje do stávajícího objektu |  | ||||||
| 				try: |  | ||||||
| 					orig_osoba = m.Osoba.objects.get(email=fcd['email']) |  | ||||||
| 					orig_osoba.poznamka += '\nDOREGISTRACE K EXISTUJÍCÍMU E-MAILU, diff níže.' |  | ||||||
| 				except m.Osoba.DoesNotExist: |  | ||||||
| 					# Trik: Budeme aktualizovat údaje nové osoby, takže se asi nic nezmění, ale fungovat to bude. |  | ||||||
| 					orig_osoba = o |  | ||||||
| 
 |  | ||||||
| 				# Porovnání údajů |  | ||||||
| 				assert orig_osoba.user is None, "Právě-registrující-se osoba už má Uživatele!" |  | ||||||
| 				osoba_attrs = ['jmeno', 'prijmeni', 'pohlavi_muz', 'email', 'telefon', 'datum_narozeni', 'ulice', 'mesto', 'psc', 'stat', 'datum_souhlasu_udaje', 'datum_souhlasu_zasilani', 'datum_registrace'] |  | ||||||
| 				diffattrs = [] |  | ||||||
| 				for attr in osoba_attrs: |  | ||||||
| 					new = getattr(o, attr) |  | ||||||
| 					old = getattr(orig_osoba, attr) |  | ||||||
| 					if new != old: |  | ||||||
| 						orig_osoba.poznamka += f'\nRozdíl v {attr}: Původní {old}, nový {new}' |  | ||||||
| 						diffattrs.append(f'Osoba.{attr}') |  | ||||||
| 						setattr(orig_osoba, attr, new) |  | ||||||
| 				# Datum registrace chceme původní / nižší: |  | ||||||
| 				orig_osoba.datum_registrace = min(orig_osoba.datum_registrace, o.datum_registrace) |  | ||||||
| 
 |  | ||||||
| 				# Od této chvíle dál je správná osoba ta "původní", novou podle formuláře si ale zachováme |  | ||||||
| 				o, o_form = orig_osoba, o |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 				o.save() |  | ||||||
| 				o.user = u |  | ||||||
| 				o.save() |  | ||||||
| 
 |  | ||||||
| 				# Jednoduchá kvazi-kontrola duplicitních Osob |  | ||||||
| 				kolize = m.Osoba.objects.filter(jmeno=o.jmeno, prijmeni=o.prijmeni) |  | ||||||
| 				if kolize.count() > 1:	# Jednu z nich jsme právě uložili |  | ||||||
| 					err_logger.warning(f'Zaregistrovala se osoba s kolizním jménem. ID osob: {[o.id for o in kolize]}') |  | ||||||
| 
 |  | ||||||
| 				r = Resitel( |  | ||||||
| 					rok_maturity = fcd['rok_maturity'], |  | ||||||
| 					zasilat = fcd['zasilat'], |  | ||||||
| 					zasilat_cislo_emailem = fcd['zasilat_cislo_emailem'] |  | ||||||
| 					) |  | ||||||
| 
 |  | ||||||
| 				if fcd.get('skola'): |  | ||||||
| 					r.skola = fcd['skola'] |  | ||||||
| 				else: |  | ||||||
| 					# Unknown school - log it |  | ||||||
| 					msg = "Unknown school {}, {}".format(fcd['skola_nazev'],fcd['skola_adresa']) |  | ||||||
| 					err_logger.warn(msg + str(form_hash)) |  | ||||||
| 
 |  | ||||||
| 				# Porovnání údajů u řešitele |  | ||||||
| 				try: |  | ||||||
| 					orig_resitel = o.resitel |  | ||||||
| 					orig_resitel.poznamka += '\nDOREGISTRACE ŘEŠITELE, diff:' |  | ||||||
| 				except m.Resitel.DoesNotExist: |  | ||||||
| 					# Stejný trik: |  | ||||||
| 					orig_resitel = r |  | ||||||
| 				resitel_attrs = ['skola', 'rok_maturity', 'zasilat', 'zasilat_cislo_emailem'] |  | ||||||
| 				for attr in resitel_attrs: |  | ||||||
| 					new = getattr(r, attr) |  | ||||||
| 					old = getattr(orig_resitel, attr) |  | ||||||
| 					if new != old: |  | ||||||
| 						orig_resitel.poznamka += f'\nRozdíl v {attr}: Původní {old}, nový {new}' |  | ||||||
| 						diffattrs.append(f'Resitel.{attr}') |  | ||||||
| 						setattr(orig_resitel, attr, new) |  | ||||||
| 				r, r_form = orig_resitel, r |  | ||||||
| 
 |  | ||||||
| 				r.osoba = o	# Tohle by mělo být bezpečné… |  | ||||||
| 				r.save() |  | ||||||
| 
 |  | ||||||
| 				if diffattrs: err_logger.warning(f'Different fields when matching Řešitel id {r.id} or Osoba id {o.id}: {diffattrs}') |  | ||||||
| 
 |  | ||||||
| 			posli_reset_hesla(u, request) |  | ||||||
| 			return formularOKView(request, text='Na tvůj e-mail jsme právě poslali odkaz pro nastavení hesla.') |  | ||||||
| 
 |  | ||||||
| 	# if a GET (or any other method) we'll create a blank form |  | ||||||
| 	else: |  | ||||||
| 		form = PrihlaskaForm() |  | ||||||
| 
 |  | ||||||
| 	return render(request, 'seminar/profil/prihlaska.html', {'form': form}) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class VueTestView(generic.TemplateView): |  | ||||||
| 	template_name = 'seminar/vuetest.html' |  | ||||||
| 
 |  | ||||||
| class NahrajObrazekKTreeNoduView(LoginRequiredMixin, CreateView): |  | ||||||
| 	model = s.Obrazek |  | ||||||
| 	form_class = f.NahrajObrazekKTreeNoduForm |  | ||||||
| 
 |  | ||||||
| 	def get_initial(self): |  | ||||||
| 		initial = super().get_initial() |  | ||||||
| 		initial['na_web'] = self.request.FILES['upload'] |  | ||||||
| 		return initial |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 	def form_valid(self,form): |  | ||||||
| 		print(self.request.headers) |  | ||||||
| 		print(self.request.headers['Textid']) |  | ||||||
| 		print(form.instance) |  | ||||||
| 		print(form) |  | ||||||
| 		self.object = form.save(commit=False) |  | ||||||
| 		print(self.object.na_web) |  | ||||||
| 		self.object.text = m.Text.objects.get(pk=int(self.request.headers['Textid'])) |  | ||||||
| 		self.object.save() |  | ||||||
| 
 |  | ||||||
| 		return JsonResponse({"url":self.object.na_web.url}) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| # Jen hloupé rozhazovátko |  | ||||||
| def profilView(request): |  | ||||||
| 	user = request.user |  | ||||||
| 	if user.has_perm('auth.org'): |  | ||||||
| 		return OrgoRozcestnikView.as_view()(request) |  | ||||||
| 	if user.has_perm('auth.resitel'): |  | ||||||
| 		return ResitelView.as_view()(request) |  | ||||||
| 	else: |  | ||||||
| 		return LoginView.as_view()(request) |  | ||||||
| 
 |  | ||||||
| # Interní, nemá se nikdy objevit v urls (jinak to účastníci vytrolí) | # Interní, nemá se nikdy objevit v urls (jinak to účastníci vytrolí) | ||||||
| def formularOKView(request, text=''): | def formularOKView(request, text=''): | ||||||
| 	template_name = 'seminar/formular_ok.html' | 	template_name = 'seminar/formular_ok.html' | ||||||
|  |  | ||||||
							
								
								
									
										5
									
								
								soustredeni/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								soustredeni/__init__.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | """ | ||||||
|  | Obsahuje vše (až na přednášky) ohledně soustředění. | ||||||
|  | 
 | ||||||
|  | TODO stvrzenky? | ||||||
|  | """ | ||||||
							
								
								
									
										43
									
								
								soustredeni/admin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								soustredeni/admin.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,43 @@ | ||||||
|  | from django.contrib import admin | ||||||
|  | from django.forms import widgets | ||||||
|  | from django.db import models | ||||||
|  | 
 | ||||||
|  | from seminar.models import soustredeni as m | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class SoustredeniUcastniciInline(admin.TabularInline): | ||||||
|  |     model = m.Soustredeni_Ucastnici | ||||||
|  |     extra = 1 | ||||||
|  |     fields = ['resitel','poznamka'] | ||||||
|  |     autocomplete_fields = ['resitel'] | ||||||
|  |     ordering = ['resitel__osoba__jmeno', 'resitel__osoba__prijmeni'] | ||||||
|  |     formfield_overrides = { | ||||||
|  |         models.TextField: {'widget': widgets.TextInput} | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     def get_queryset(self,request): | ||||||
|  |         qs = super().get_queryset(request) | ||||||
|  |         return qs.select_related('resitel','soustredeni') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class SoustredeniOrganizatoriInline(admin.TabularInline): | ||||||
|  |     model = m.Soustredeni.organizatori.through | ||||||
|  |     extra = 1 | ||||||
|  |     fields = ['organizator','poznamka'] | ||||||
|  |     autocomplete_fields = ['organizator'] | ||||||
|  |     ordering = ['organizator__osoba__jmeno','organizator__prijmeni'] | ||||||
|  |     formfield_overrides = { | ||||||
|  |         models.TextField: {'widget': widgets.TextInput} | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     def get_queryset(self,request): | ||||||
|  |         qs = super().get_queryset(request) | ||||||
|  |         return qs.select_related('organizator', 'soustredeni') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @admin.register(m.Soustredeni) | ||||||
|  | class SoustredeniAdmin(admin.ModelAdmin): | ||||||
|  |     model = m.Soustredeni | ||||||
|  |     inline_type = 'tabular' | ||||||
|  |     inlines = [SoustredeniUcastniciInline, SoustredeniOrganizatoriInline] | ||||||
|  | 
 | ||||||
							
								
								
									
										5
									
								
								soustredeni/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								soustredeni/apps.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | from django.apps import AppConfig | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class SoustredeniConfig(AppConfig): | ||||||
|  |     name = 'soustredeni' | ||||||
							
								
								
									
										0
									
								
								soustredeni/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								soustredeni/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										35
									
								
								soustredeni/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								soustredeni/urls.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,35 @@ | ||||||
|  | from django.urls import path, include | ||||||
|  | from . import views | ||||||
|  | from seminar.utils import org_required | ||||||
|  | 
 | ||||||
|  | urlpatterns = [ | ||||||
|  | 	path( | ||||||
|  | 		'soustredeni/probehlo/', | ||||||
|  | 		views.SoustredeniListView.as_view(), | ||||||
|  | 		name='seminar_seznam_soustredeni' | ||||||
|  | 	), | ||||||
|  | 	path( | ||||||
|  | 		'soustredeni/<int:soustredeni>/seznam_ucastniku', | ||||||
|  | 		org_required(views.SoustredeniUcastniciView.as_view()), | ||||||
|  | 		name='soustredeni_ucastnici' | ||||||
|  | 	), | ||||||
|  | 	path( | ||||||
|  | 		'soustredeni/<int:soustredeni>/maily_ucastniku', | ||||||
|  | 		org_required(views.SoustredeniMailyUcastnikuView.as_view()), | ||||||
|  | 		name='maily_ucastniku' | ||||||
|  | 	), | ||||||
|  | 	path( | ||||||
|  | 		'soustredeni/<int:soustredeni>/export_ucastniku', | ||||||
|  | 		org_required(views.soustredeniUcastniciExportView), | ||||||
|  | 		name='soustredeni_ucastnici_export' | ||||||
|  | 	), | ||||||
|  | 	path( | ||||||
|  | 		'soustredeni/<int:soustredeni>/obalky.pdf', | ||||||
|  | 		org_required(views.soustredeniObalkyView), | ||||||
|  | 		name='seminar_soustredeni_obalky' | ||||||
|  | 	), | ||||||
|  | 	path( | ||||||
|  | 		'soustredeni/<int:soustredeni>/fotogalerie/', | ||||||
|  | 		include('galerie.urls') | ||||||
|  | 	), | ||||||
|  | ] | ||||||
							
								
								
									
										55
									
								
								soustredeni/views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								soustredeni/views.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,55 @@ | ||||||
|  | from django.shortcuts import get_object_or_404 | ||||||
|  | from django.http import HttpResponse | ||||||
|  | from django.views import generic | ||||||
|  | from seminar.models import Soustredeni, Resitel, Soustredeni_Ucastnici # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci | ||||||
|  | import csv | ||||||
|  | 
 | ||||||
|  | from seminar.views import obalkyView | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class SoustredeniListView(generic.ListView): | ||||||
|  | 	model = Soustredeni | ||||||
|  | 	template_name = 'soustredeni/seznam_soustredeni.html' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def soustredeniObalkyView(request, soustredeni): | ||||||
|  | 	soustredeni = get_object_or_404(Soustredeni, id=soustredeni) | ||||||
|  | 	return obalkyView(request, soustredeni.ucastnici.all()) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class SoustredeniUcastniciBaseView(generic.ListView): | ||||||
|  | 	model = Soustredeni_Ucastnici | ||||||
|  | 
 | ||||||
|  | 	def get_queryset(self): | ||||||
|  | 		soustredeni = get_object_or_404( | ||||||
|  | 			Soustredeni, | ||||||
|  | 			pk=self.kwargs["soustredeni"] | ||||||
|  | 		) | ||||||
|  | 		return Soustredeni_Ucastnici.objects.filter( | ||||||
|  | 			soustredeni=soustredeni).select_related('resitel') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class SoustredeniMailyUcastnikuView(SoustredeniUcastniciBaseView): | ||||||
|  | 	""" Seznam e-mailů řešitelů oddělených čárkami. """ | ||||||
|  | 	model = Soustredeni_Ucastnici | ||||||
|  | 	template_name = 'soustredeni/maily_ucastniku.txt' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class SoustredeniUcastniciView(SoustredeniUcastniciBaseView): | ||||||
|  | 	""" HTML tabulka účastníků pro tisk. """ | ||||||
|  | 	model = Soustredeni_Ucastnici | ||||||
|  | 	template_name = 'soustredeni/seznam_ucastniku.html' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def soustredeniUcastniciExportView(request, soustredeni): | ||||||
|  | 	soustredeni = get_object_or_404(Soustredeni, id=soustredeni) | ||||||
|  | 	ucastnici = Resitel.objects.filter(soustredeni=soustredeni) | ||||||
|  | 	response = HttpResponse(content_type='text/csv') | ||||||
|  | 	response['Content-Disposition'] = 'attachment; filename="ucastnici.csv"' | ||||||
|  | 
 | ||||||
|  | 	writer = csv.writer(response) | ||||||
|  | 	writer.writerow(["jmeno", "prijmeni", "rok_maturity", "telefon", "email", "ulice", "mesto", "psc","stat"]) | ||||||
|  | 	for u in ucastnici: | ||||||
|  | 		o = u.osoba | ||||||
|  | 		writer.writerow([o.jmeno, o.prijmeni, str(u.rok_maturity), o.telefon, o.email, o.ulice, o.mesto, o.psc, o.stat.name]) | ||||||
|  | 	return response | ||||||
							
								
								
									
										0
									
								
								treenode/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								treenode/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										88
									
								
								treenode/admin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								treenode/admin.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,88 @@ | ||||||
|  | from django.contrib import admin | ||||||
|  | 
 | ||||||
|  | from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter | ||||||
|  | 
 | ||||||
|  | import seminar.models as m | ||||||
|  | 
 | ||||||
|  | # Polymorfismus pro stromy | ||||||
|  | # TODO: Inlines podle https://django-polymorphic.readthedocs.io/en/stable/admin.html | ||||||
|  | 
 | ||||||
|  | @admin.register(m.TreeNode) | ||||||
|  | class TreeNodeAdmin(PolymorphicParentModelAdmin): | ||||||
|  |     base_model = m.TreeNode | ||||||
|  |     child_models = [ | ||||||
|  |         m.RocnikNode, | ||||||
|  |         m.CisloNode, | ||||||
|  |         m.MezicisloNode, | ||||||
|  |         m.TemaVCisleNode, | ||||||
|  |         m.UlohaZadaniNode, | ||||||
|  |         m.PohadkaNode, | ||||||
|  |         m.UlohaVzorakNode, | ||||||
|  |         m.TextNode, | ||||||
|  |         m.CastNode, | ||||||
|  |         m.OrgTextNode, | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     actions = ['aktualizuj_nazvy'] | ||||||
|  | 
 | ||||||
|  |     # XXX: nejspíš je to totální DB HOG, nechcete to použít moc často. | ||||||
|  |     def aktualizuj_nazvy(self, request, queryset): | ||||||
|  |         newqs = queryset.get_real_instances() | ||||||
|  |         for tn in newqs: | ||||||
|  |             tn.aktualizuj_nazev() | ||||||
|  |             tn.save() | ||||||
|  |         self.message_user(request, "Názvy aktualizovány.") | ||||||
|  |     aktualizuj_nazvy.short_description = "Aktualizuj vybraným TreeNodům názvy" | ||||||
|  | 
 | ||||||
|  | @admin.register(m.RocnikNode) | ||||||
|  | class RocnikNodeAdmin(PolymorphicChildModelAdmin): | ||||||
|  |     base_model = m.RocnikNode | ||||||
|  |     show_in_index = True | ||||||
|  | 
 | ||||||
|  | @admin.register(m.CisloNode) | ||||||
|  | class CisloNodeAdmin(PolymorphicChildModelAdmin): | ||||||
|  |     base_model = m.CisloNode | ||||||
|  |     show_in_index = True | ||||||
|  | 
 | ||||||
|  | @admin.register(m.MezicisloNode) | ||||||
|  | class MezicisloNodeAdmin(PolymorphicChildModelAdmin): | ||||||
|  |     base_model = m.MezicisloNode | ||||||
|  |     show_in_index = True | ||||||
|  | 
 | ||||||
|  | @admin.register(m.TemaVCisleNode) | ||||||
|  | class TemaVCisleNodeAdmin(PolymorphicChildModelAdmin): | ||||||
|  |     base_model = m.TemaVCisleNode | ||||||
|  |     show_in_index = True | ||||||
|  | 
 | ||||||
|  | @admin.register(m.UlohaZadaniNode) | ||||||
|  | class UlohaZadaniNodeAdmin(PolymorphicChildModelAdmin): | ||||||
|  |     base_model = m.UlohaZadaniNode | ||||||
|  |     show_in_index = True | ||||||
|  | 
 | ||||||
|  | @admin.register(m.PohadkaNode) | ||||||
|  | class PohadkaNodeAdmin(PolymorphicChildModelAdmin): | ||||||
|  |     base_model = m.PohadkaNode | ||||||
|  |     show_in_index = True | ||||||
|  | 
 | ||||||
|  | @admin.register(m.UlohaVzorakNode) | ||||||
|  | class UlohaVzorakNodeAdmin(PolymorphicChildModelAdmin): | ||||||
|  |     base_model = m.UlohaVzorakNode | ||||||
|  |     show_in_index = True | ||||||
|  | 
 | ||||||
|  | @admin.register(m.TextNode) | ||||||
|  | class TextNodeAdmin(PolymorphicChildModelAdmin): | ||||||
|  |     base_model = m.TextNode | ||||||
|  |     show_in_index = True | ||||||
|  | 
 | ||||||
|  | @admin.register(m.CastNode) | ||||||
|  | class TextNodeAdmin(PolymorphicChildModelAdmin): | ||||||
|  |     base_model = m.CastNode | ||||||
|  |     show_in_index = True | ||||||
|  |     fields = ('nadpis',) | ||||||
|  | 
 | ||||||
|  | @admin.register(m.OrgTextNode) | ||||||
|  | class TextNodeAdmin(PolymorphicChildModelAdmin): | ||||||
|  |     base_model = m.OrgTextNode | ||||||
|  |     show_in_index = True | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
							
								
								
									
										5
									
								
								treenode/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								treenode/apps.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | from django.apps import AppConfig | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class TreenodeConfig(AppConfig): | ||||||
|  |     name = 'treenode' | ||||||
							
								
								
									
										14
									
								
								treenode/forms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								treenode/forms.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | ||||||
|  | from django import forms | ||||||
|  | import seminar.models as m | ||||||
|  | 
 | ||||||
|  | # 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 NahrajObrazekKTreeNoduForm(forms.ModelForm): | ||||||
|  | 	class Meta: | ||||||
|  | 		model = m.Obrazek | ||||||
|  | 		fields = ('na_web',) | ||||||
							
								
								
									
										0
									
								
								treenode/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								treenode/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -1,5 +1,5 @@ | ||||||
| from rest_framework import routers | from rest_framework import routers | ||||||
| from seminar import viewsets as vs | from treenode import viewsets as vs | ||||||
| 
 | 
 | ||||||
| router = routers.DefaultRouter() | router = routers.DefaultRouter() | ||||||
| 
 | 
 | ||||||
|  | @ -2,7 +2,7 @@ from rest_framework import serializers | ||||||
| from rest_polymorphic.serializers import PolymorphicSerializer | from rest_polymorphic.serializers import PolymorphicSerializer | ||||||
| 
 | 
 | ||||||
| import seminar.models as m | import seminar.models as m | ||||||
| from seminar import treelib | from treenode import treelib | ||||||
| 
 | 
 | ||||||
| DEFAULT_NODE_DEPTH = 2 | DEFAULT_NODE_DEPTH = 2 | ||||||
| 
 | 
 | ||||||
|  | @ -17,7 +17,7 @@ def nodeType(value): | ||||||
| 	if isinstance(value,UlohaZadaniNode): return "Zadání úlohy" | 	if isinstance(value,UlohaZadaniNode): return "Zadání úlohy" | ||||||
| 	if isinstance(value,PohadkaNode): return "Pohádka" | 	if isinstance(value,PohadkaNode): return "Pohádka" | ||||||
| 
 | 
 | ||||||
| ### NASLEDUJICI FUNKCE SE POUZIVAJI VE views_all.py V SEKCI PRIPRAVJICI TNLData | ### NASLEDUJICI FUNKCE SE POUZIVAJI VE views.py V SEKCI PRIPRAVJICI TNLData | ||||||
| ### NEMAZAT, PRESUNOUT S TNLDaty NEKAM BOKEM | ### NEMAZAT, PRESUNOUT S TNLDaty NEKAM BOKEM | ||||||
| 
 | 
 | ||||||
| @register.filter | @register.filter | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| from django.test import TestCase | from django.test import TestCase | ||||||
| import seminar.treelib as tl | import treenode.treelib as tl | ||||||
| import seminar.models as m | import seminar.models as m | ||||||
| 
 | 
 | ||||||
| class SimpleTreeLibTests(TestCase): | class SimpleTreeLibTests(TestCase): | ||||||
							
								
								
									
										18
									
								
								treenode/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								treenode/urls.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | ||||||
|  | from django.urls import path, re_path | ||||||
|  | from . import views | ||||||
|  | 
 | ||||||
|  | urlpatterns = [ | ||||||
|  | 	#path('treenode/<int:pk>/', views.TreeNodeView.as_view(), name='seminar_treenode'), | ||||||
|  | 	#path('treenode/<int:pk>/json/', views.TreeNodeJSONView.as_view(), name='seminar_treenode_json'), | ||||||
|  | 	#path('treenode/text/<int:pk>/', views.TextWebView.as_view(), name='seminar_textnode_web'), | ||||||
|  | 	#path('treenode/editor/pridat/<str:co>/<int:pk>/<str:kam>/', views.TreeNodePridatView.as_view(), name='treenode_pridat'), | ||||||
|  | 	#path('treenode/editor/smazat/<int:pk>/', views.TreeNodeSmazatView.as_view(), name='treenode_smazat'), | ||||||
|  | 	#path('treenode/editor/odvesitpryc/<int:pk>/', views.TreeNodeOdvesitPrycView.as_view(), name='treenode_odvesitpryc'), | ||||||
|  | 	#path('treenode/editor/podvesit/<int:pk>/<str:kam>/', views.TreeNodePodvesitView.as_view(), name='treenode_podvesit'), | ||||||
|  | 	#path('treenode/editor/prohodit/<int:pk>/', views.TreeNodeProhoditView.as_view(), name='treenode_prohodit'), | ||||||
|  | 	#path('treenode/sirotcinec/', views.SirotcinecView.as_view(), name='seminar_treenode_sirotcinec'), | ||||||
|  | 	#path('problem/(?P<pk>\d+)/(?P<prispevek>\d+)/', views.PrispevekView.as_view(), name='seminar_problem_prispevek'), | ||||||
|  | 
 | ||||||
|  | 	re_path(r'^temp/vue/.*$',views.VueTestView.as_view(),name='vue_test_view'), | ||||||
|  | 	path('temp/image_upload/', views.NahrajObrazekKTreeNoduView.as_view()), | ||||||
|  | ] | ||||||
							
								
								
									
										322
									
								
								treenode/views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										322
									
								
								treenode/views.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,322 @@ | ||||||
|  | from django.forms import model_to_dict | ||||||
|  | from django.shortcuts import redirect | ||||||
|  | from django.http import JsonResponse | ||||||
|  | from django.views import generic | ||||||
|  | from django.views.generic.edit import CreateView | ||||||
|  | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
|  | from django.core.exceptions import PermissionDenied | ||||||
|  | 
 | ||||||
|  | import seminar.models as s | ||||||
|  | import seminar.models as m | ||||||
|  | from treenode import treelib | ||||||
|  | import treenode.forms as f | ||||||
|  | import treenode.templatetags as tnltt | ||||||
|  | import treenode.serializers as vr | ||||||
|  | 
 | ||||||
|  | import logging | ||||||
|  | 
 | ||||||
|  | logger = logging.getLogger(__name__) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class TNLData(object): | ||||||
|  | 	def __init__(self,anode,parent=None, index=None): | ||||||
|  | 		self.node = anode | ||||||
|  | 		self.sernode = vr.TreeNodeSerializer(anode) | ||||||
|  | 		self.children = [] | ||||||
|  | 		self.parent = parent | ||||||
|  | 		self.tema_in_path = False | ||||||
|  | 		self.index = index | ||||||
|  | 
 | ||||||
|  | 		if parent: | ||||||
|  | 			self.tema_in_path = parent.tema_in_path | ||||||
|  | 		if isinstance(anode, m.TemaVCisleNode): | ||||||
|  | 			self.tema_in_path = True | ||||||
|  | 
 | ||||||
|  | 	def add_edit_options(self): | ||||||
|  | 		self.deletable = tnltt.deletable(self) | ||||||
|  | 		self.editable_siblings = tnltt.editableSiblings(self) | ||||||
|  | 		self.editable_children = tnltt.editableChildren(self) | ||||||
|  | 		self.text_only_subtree = tnltt.textOnlySubtree(self) | ||||||
|  | 		self.can_podvesit_za = tnltt.canPodvesitZa(self) | ||||||
|  | 		self.can_podvesit_pred = tnltt.canPodvesitPred(self) | ||||||
|  | 		self.appendable_children = tnltt.appendableChildren(self) | ||||||
|  | 		print("appChld",self.appendable_children) | ||||||
|  | 		if self.parent: | ||||||
|  | 			self.appendable_siblings = tnltt.appendableChildren(self.parent) | ||||||
|  | 		else: | ||||||
|  | 			self.appendable_siblings = [] | ||||||
|  | 	@classmethod | ||||||
|  | 	def public_above(cls, anode): | ||||||
|  | 		""" Returns output of verejne for closest Rocnik, Cislo or Problem above. | ||||||
|  | 		(All of them have method verejne.)""" | ||||||
|  | 		parent = anode # chceme začít už od konkrétního node včetně | ||||||
|  | 		while True: | ||||||
|  | 			rocnik = isinstance(parent, s.RocnikNode) | ||||||
|  | 			cislo = isinstance(parent, s.CisloNode) | ||||||
|  | 			uloha = (isinstance(parent, s.UlohaVzorakNode) or | ||||||
|  | 				isinstance(parent, s.UlohaZadaniNode)) | ||||||
|  | 			tema = isinstance(parent, s.TemaVCisleNode) | ||||||
|  | 
 | ||||||
|  | 			if (rocnik or cislo or uloha or tema) or parent==None: | ||||||
|  | 				break | ||||||
|  | 			else: | ||||||
|  | 				parent = treelib.get_parent(parent) | ||||||
|  | 		if rocnik: | ||||||
|  | 			return parent.rocnik.verejne() | ||||||
|  | 		elif cislo: | ||||||
|  | 			return parent.cislo.verejne() | ||||||
|  | 		elif uloha: | ||||||
|  | 			return parent.uloha.verejne() | ||||||
|  | 		elif tema: | ||||||
|  | 			return parent.tema.verejne() | ||||||
|  | 		elif None: | ||||||
|  | 			print("Existuje TreeNode, který není pod číslem, ročníkem, úlohou" | ||||||
|  | 			"ani tématem. {}".format(anode)) | ||||||
|  | 			return False | ||||||
|  | 
 | ||||||
|  | 	@classmethod | ||||||
|  | 	def all_public_children(cls, anode): | ||||||
|  | 		for ch in treelib.all_children(anode): | ||||||
|  | 			if TNLData.public_above(ch): | ||||||
|  | 				yield ch | ||||||
|  | 			else: | ||||||
|  | 				continue | ||||||
|  | 
 | ||||||
|  | 	@classmethod | ||||||
|  | 	def from_treenode(cls, anode, user, parent=None, index=None): | ||||||
|  | 		if TNLData.public_above(anode) or user.has_perm('auth.org'): | ||||||
|  | 			out = cls(anode,parent,index) | ||||||
|  | 		else: | ||||||
|  | 			raise PermissionDenied() | ||||||
|  | 
 | ||||||
|  | 		if user.has_perm('auth.org'): | ||||||
|  | 			enum_children = enumerate(treelib.all_children(anode)) | ||||||
|  | 		else: | ||||||
|  | 			enum_children = enumerate(TNLData.all_public_children(anode)) | ||||||
|  | 
 | ||||||
|  | 		for (idx,ch) in enum_children: | ||||||
|  | 			outitem = cls.from_treenode(ch, user, out,  idx) | ||||||
|  | 			out.children.append(outitem) | ||||||
|  | 		out.add_edit_options() | ||||||
|  | 		return out | ||||||
|  | 
 | ||||||
|  | 	@classmethod | ||||||
|  | 	def from_tnldata_list(cls, tnllist): | ||||||
|  | 		"""Vyrobíme virtuální TNL, který nemá obsah, ale má za potomky všechna zadaná TNLData""" | ||||||
|  | 		result = cls(None) | ||||||
|  | 		for idx, tnl in enumerate(tnllist): | ||||||
|  | 			result.children.append(tnl) | ||||||
|  | 			tnl.parent = result | ||||||
|  | 			tnl.index = idx | ||||||
|  | 		result.add_edit_options() | ||||||
|  | 		return result | ||||||
|  | 
 | ||||||
|  | 	@classmethod | ||||||
|  | 	def filter_treenode(cls, treenode, predicate): | ||||||
|  | 		tnll = cls._filter_treenode_recursive(treenode, predicate) # TreeNodeList List :-) | ||||||
|  | 		return TNLData.from_tnldata_list(tnll) | ||||||
|  | 
 | ||||||
|  | 	@classmethod | ||||||
|  | 	def _filter_treenode_recursive(cls, treenode, predicate): | ||||||
|  | 		if predicate(treenode): | ||||||
|  | 			return [cls.from_treenode(treenode)] | ||||||
|  | 		else: | ||||||
|  | 			found = [] | ||||||
|  | 			for tn in treelib.all_children(treenode): | ||||||
|  | 				result = cls.filter_treenode(tn, predicate) | ||||||
|  | 				# Result by v tuhle chvíli měl být seznam TNLDat odpovídající treenodům, jež matchnuly predikát. | ||||||
|  | 				for tnl in result: | ||||||
|  | 					found.append(tnl) | ||||||
|  | 			return found | ||||||
|  | 
 | ||||||
|  | 	def to_json(self): | ||||||
|  | 		#self.node = anode | ||||||
|  | 		#self.children = [] | ||||||
|  | 		#self.parent = parent | ||||||
|  | 		#self.tema_in_path = False | ||||||
|  | 		#self.index = index | ||||||
|  | 		out = {} | ||||||
|  | 		out['node'] = self.sernode.data | ||||||
|  | 		out['children'] = [n.to_json() for n in self.children] | ||||||
|  | 		out['tema_in_path'] = self.tema_in_path | ||||||
|  | 		out['index'] = self.index | ||||||
|  | 		out['deletable'] = self.deletable | ||||||
|  | 		out['editable_siblings'] = self.editable_siblings | ||||||
|  | 		out['editable_children'] = self.editable_children | ||||||
|  | 		out['text_only_subtree'] = self.text_only_subtree | ||||||
|  | 		out['can_podvesit_za'] = self.can_podvesit_za | ||||||
|  | 		out['can_podvesit_pod'] = self.can_podvesit_pred | ||||||
|  | 		out['appendable_children'] = self.appendable_children | ||||||
|  | 		out['appendable_siblings'] = self.appendable_siblings | ||||||
|  | 
 | ||||||
|  | 		return out | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 	def __repr__(self): | ||||||
|  | 		return("TNL({})".format(self.node)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class TreeNodeView(generic.DetailView): | ||||||
|  | 	model = s.TreeNode | ||||||
|  | 	template_name = 'treenode/treenode.html' | ||||||
|  | 
 | ||||||
|  | 	def get_context_data(self,**kwargs): | ||||||
|  | 		context = super().get_context_data(**kwargs) | ||||||
|  | 		context['tnldata'] = TNLData.from_treenode(self.object,self.request.user) | ||||||
|  | 		return context | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class TreeNodeJSONView(generic.DetailView): | ||||||
|  | 	model = s.TreeNode | ||||||
|  | 
 | ||||||
|  | 	def get(self,request,*args, **kwargs): | ||||||
|  | 		self.object = self.get_object() | ||||||
|  | 		data = TNLData.from_treenode(self.object,self.request.user).to_json() | ||||||
|  | 		return JsonResponse(data) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class TreeNodePridatView(generic.View): | ||||||
|  | 	type_from_str = { | ||||||
|  | 		'rocnikNode': m.RocnikNode, | ||||||
|  | 		'cisloNode': m.CisloNode, | ||||||
|  | 		'castNode': m.CastNode, | ||||||
|  | 		'textNode': m.TextNode, | ||||||
|  | 		'temaVCisleNode': m.TemaVCisleNode, | ||||||
|  | 		'reseniNode': m.ReseniNode, | ||||||
|  | 		'ulohaZadaniNode': m.UlohaZadaniNode, | ||||||
|  | 		'ulohaVzorakNode': m.UlohaVzorakNode, | ||||||
|  | 		'pohadkaNode': m.PohadkaNode, | ||||||
|  | 		'orgText': m.OrgTextNode, | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	def post(self, request, *args, **kwargs): | ||||||
|  | 		######## FIXME: ROZEPSANE, NEFUNGUJE, DOPSAT !!!!!! ########### | ||||||
|  | 		node = s.TreeNode.objects.get(pk=self.kwargs['pk']) | ||||||
|  | 		kam = self.kwargs['kam'] | ||||||
|  | 		co = self.kwargs['co'] | ||||||
|  | 		typ = self.type_from_str[co] | ||||||
|  | 
 | ||||||
|  | 		raise NotImplementedError('Neni to dopsane, dopis to!') | ||||||
|  | 
 | ||||||
|  | 		if kam not in ('pred','syn','za'): | ||||||
|  | 			raise ValidationError('Přidat lze pouze před nebo za node nebo jako syna') | ||||||
|  | 
 | ||||||
|  | 		if co == m.TextNode: | ||||||
|  | 			new_obj = m.Text() | ||||||
|  | 			new_obj.save() | ||||||
|  | 		elif co == m.CastNode: | ||||||
|  | 			new_obj = m.CastNode() | ||||||
|  | 			new_obj.nadpis = request.POST.get('pridat-castNode-{}-{}'.format(node.id,kam)) | ||||||
|  | 			new_obj.save() | ||||||
|  | 		elif co == m.ReseniNode: | ||||||
|  | 			new_obj = m | ||||||
|  | 			pass | ||||||
|  | 		elif co == m.UlohaZadaniNode: | ||||||
|  | 			pass | ||||||
|  | 		elif co == m.UlohaReseniNode: | ||||||
|  | 			pass | ||||||
|  | 		else: | ||||||
|  | 			new_obj = None | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 		if kam == 'pred': | ||||||
|  | 			pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 		if kam == 'syn': | ||||||
|  | 			if typ == m.TextNode: | ||||||
|  | 				text_obj = m.Text() | ||||||
|  | 				text_obj.save() | ||||||
|  | 				node = treelib.create_child(node, typ, text=text_obj) | ||||||
|  | 			else: | ||||||
|  | 				node = treelib.create_child(node, typ) | ||||||
|  | 		if kam == 'za': | ||||||
|  | 			if typ == m.TextNode: | ||||||
|  | 				text_obj = m.Text() | ||||||
|  | 				text_obj.save() | ||||||
|  | 				node = treelib.create_node_after(node, typ, text=text_obj) | ||||||
|  | 			else: | ||||||
|  | 				node = treelib.create_node_after(node, typ) | ||||||
|  | 
 | ||||||
|  | 		return redirect(node.get_admin_url()) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class TreeNodeSmazatView(generic.base.View): | ||||||
|  | 	def post(self, request, *args, **kwargs): | ||||||
|  | 		node = s.TreeNode.objects.get(pk=self.kwargs['pk']) | ||||||
|  | 		if node.first_child: | ||||||
|  | 			raise NotImplementedError('Mazání TreeNode se syny není zatím podporováno!') | ||||||
|  | 		treelib.disconnect_node(node) | ||||||
|  | 		node.delete() | ||||||
|  | 		return redirect(request.headers.get('referer')) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class TreeNodeOdvesitPrycView(generic.base.View): | ||||||
|  | 	def post(self, request, *args, **kwargs): | ||||||
|  | 		node = s.TreeNode.objects.get(pk=self.kwargs['pk']) | ||||||
|  | 		treelib.disconnect_node(node) | ||||||
|  | 		node.root = None | ||||||
|  | 		node.save() | ||||||
|  | 		return redirect(request.headers.get('referer')) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class TreeNodePodvesitView(generic.base.View): | ||||||
|  | 	def post(self, request, *args, **kwargs): | ||||||
|  | 		node = s.TreeNode.objects.get(pk=self.kwargs['pk']) | ||||||
|  | 		kam = self.kwargs['kam'] | ||||||
|  | 		if kam == 'pred': | ||||||
|  | 			treelib.lower_node(node) | ||||||
|  | 		elif kam == 'za': | ||||||
|  | 			raise NotImplementedError('Podvěsit za není zatím podporováno') | ||||||
|  | 		return redirect(request.headers.get('referer')) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class TreeNodeProhoditView(generic.base.View): | ||||||
|  | 	def post(self, request, *args, **kwargs): | ||||||
|  | 		node = s.TreeNode.objects.get(pk=self.kwargs['pk']) | ||||||
|  | 		treelib.swap_succ(node) | ||||||
|  | 		return redirect(request.headers.get('referer')) | ||||||
|  | 		#FIXME ve formulari predat puvodni url a vratit redirect na ni | ||||||
|  | 
 | ||||||
|  | class SirotcinecView(generic.ListView): | ||||||
|  | 	model = s.TreeNode | ||||||
|  | 	template_name = 'treenode/orphanage.html' | ||||||
|  | 
 | ||||||
|  | 	def get_queryset(self): | ||||||
|  | 		return s.TreeNode.objects.not_instance_of(s.RocnikNode).filter(root=None,prev=None,succ=None,father_of_first=None) | ||||||
|  | 
 | ||||||
|  | # FIXME pouzit Django REST Framework | ||||||
|  | class TextWebView(generic.DetailView): | ||||||
|  | 	model = s.Text | ||||||
|  | 
 | ||||||
|  | 	def get(self,request,*args, **kwargs): | ||||||
|  | 		self.object = self.get_object() | ||||||
|  | 		return JsonResponse(model_to_dict(self.object,exclude='do_cisla')) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class VueTestView(generic.TemplateView): | ||||||
|  | 	template_name = 'treenode/vuetest.html' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class NahrajObrazekKTreeNoduView(LoginRequiredMixin, CreateView): | ||||||
|  | 	model = s.Obrazek | ||||||
|  | 	form_class = f.NahrajObrazekKTreeNoduForm | ||||||
|  | 
 | ||||||
|  | 	def get_initial(self): | ||||||
|  | 		initial = super().get_initial() | ||||||
|  | 		initial['na_web'] = self.request.FILES['upload'] | ||||||
|  | 		return initial | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 	def form_valid(self,form): | ||||||
|  | 		print(self.request.headers) | ||||||
|  | 		print(self.request.headers['Textid']) | ||||||
|  | 		print(form.instance) | ||||||
|  | 		print(form) | ||||||
|  | 		self.object = form.save(commit=False) | ||||||
|  | 		print(self.object.na_web) | ||||||
|  | 		self.object.text = m.Text.objects.get(pk=int(self.request.headers['Textid'])) | ||||||
|  | 		self.object.save() | ||||||
|  | 
 | ||||||
|  | 		return JsonResponse({"url":self.object.na_web.url}) | ||||||
|  | @ -3,10 +3,10 @@ from rest_framework import status | ||||||
| from rest_framework.response import Response | from rest_framework.response import Response | ||||||
| from django.core.exceptions import PermissionDenied | 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 seminar import models as m | ||||||
| from . import views | import treenode.serializers as views | ||||||
| 
 | 
 | ||||||
| from seminar.permissions import AllowWrite  | from treenode.permissions import AllowWrite | ||||||
| 
 | 
 | ||||||
| class PermissionMixin(object): | class PermissionMixin(object): | ||||||
| 	""" Redefines get_permissions so that only organizers can make changes. """ | 	""" Redefines get_permissions so that only organizers can make changes. """ | ||||||
							
								
								
									
										3
									
								
								vysledkovky/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								vysledkovky/__init__.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | ||||||
|  | """ | ||||||
|  | Obsahuje výsledkovky a vše, co se týká sčítání bodů. | ||||||
|  | """ | ||||||
							
								
								
									
										5
									
								
								vysledkovky/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								vysledkovky/apps.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | from django.apps import AppConfig | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class VysledkovkyConfig(AppConfig): | ||||||
|  |     name = 'vysledkovky' | ||||||
							
								
								
									
										0
									
								
								vysledkovky/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								vysledkovky/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										66
									
								
								vysledkovky/templates/vysledkovky/vysledkovka_cisla.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								vysledkovky/templates/vysledkovky/vysledkovka_cisla.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,66 @@ | ||||||
|  | <table class='vysledkovka'> | ||||||
|  |     <tr class='border-b'> | ||||||
|  |         <th class='border-r'># | ||||||
|  |         <th class='border-r'>Jméno | ||||||
|  |             {% for p in problemy %} | ||||||
|  |                 <th class='border-r' id="problem{{ forloop.counter }}">{# <a href="{{ p.verejne_url }}"> #}{{ p.kod_v_rocniku }}{# </a> #} | ||||||
|  | 
 | ||||||
|  |                     {# TODELETE #} | ||||||
|  |                     {% for podproblemy in podproblemy_iter.next %} | ||||||
|  |                         <th class='border-r podproblem{{ forloop.parentloop.counter }} podproblem'>{# <a href="{{ podproblemy.verejne_url }}"> #}{{ podproblemy.kod_v_rocniku }}{# </a> #} | ||||||
|  |                     {% endfor %} | ||||||
|  |                 {# TODELETE #} | ||||||
|  | 
 | ||||||
|  |             {% endfor %} | ||||||
|  |         {% if ostatni %}<th class='border-r'>Ostatní {% endif %} | ||||||
|  | 
 | ||||||
|  |         {# TODELETE #} | ||||||
|  |         {% for podproblemy in podproblemy_iter.next %} | ||||||
|  |             <th class='border-r podproblem{{ problemy.len }} podproblem'>{# <a href="{{ podproblemy.verejne_url }}"> #}{{ podproblemy.kod_v_rocniku }}{# </a> #} | ||||||
|  |         {% endfor %} | ||||||
|  |         {# TODELETE #} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         <th class='border-r'>Za číslo | ||||||
|  |         <th class='border-r'>Za ročník | ||||||
|  |         <th class='border-r'>Odjakživa | ||||||
|  |             {% for rv in radky_vysledkovky %} | ||||||
|  |                 <tr> | ||||||
|  |                     <td class='border-r'>{% autoescape off %}{{ rv.poradi }}{% endautoescape %} | ||||||
|  |                     <th class='border-r'> | ||||||
|  |                         {% if rv.titul %} | ||||||
|  |                             {{ rv.titul }}<sup>MM</sup> | ||||||
|  |                         {% endif %} | ||||||
|  |                         {{ rv.resitel.osoba.plne_jmeno }} | ||||||
|  |                         {% for b in rv.body_problemy_sezn %} | ||||||
|  |                             <td class='border-r'>{{ b }} | ||||||
|  | 
 | ||||||
|  |                                 {# TODELETE #} | ||||||
|  |                                 {% for body_podproblemu in rv.body_podproblemy_iter.next %} | ||||||
|  |                                     <td class='border-r podproblem{{ forloop.parentloop.counter }} podproblem'>{{ body_podproblemu }} | ||||||
|  |                                 {% endfor %} | ||||||
|  |                             {# TODELETE #} | ||||||
|  | 
 | ||||||
|  |                         {% endfor %} | ||||||
|  |                     <td class='border-r'>{{ rv.body_cislo }} | ||||||
|  |                     <td class='border-r'><b>{{ rv.body_rocnik }}</b> | ||||||
|  |                     <td class='border-r'>{{ rv.body_celkem_odjakziva }} | ||||||
|  |                 </tr> | ||||||
|  |             {% endfor %} | ||||||
|  | </table> | ||||||
|  | 
 | ||||||
|  | {# TODELETE #} | ||||||
|  | <script> | ||||||
|  |     {% for p in problemy %} | ||||||
|  |         $(".podproblem{{ forloop.counter }}").css("display", "none") | ||||||
|  |         $("#problem{{ forloop.counter }}")[0].addEventListener('mouseover', podproblem{{ forloop.counter }}) | ||||||
|  |         $("#problem{{ forloop.counter }}")[0].addEventListener('mouseout', podproblem{{ forloop.counter }}end) | ||||||
|  |         function podproblem{{ forloop.counter }}(event) { | ||||||
|  |             $(".podproblem{{ forloop.counter }}").css("display", "") | ||||||
|  |         } | ||||||
|  |         function podproblem{{ forloop.counter }}end(event) { | ||||||
|  |             $(".podproblem{{ forloop.counter }}").css("display", "none") | ||||||
|  |         } | ||||||
|  |     {% endfor %} | ||||||
|  | </script> | ||||||
|  | {# TODELETE #} | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | {% include "vysledkovky/vysledkovka_rocnik.html" with radky_vysledkovky=radky_vysledkovky_s_neverejnymi cisla=cisla_s_neverejnymi %} | ||||||
|  | @ -2,7 +2,6 @@ import seminar.models as m | ||||||
| from django.db.models import Q, Sum, Count | from django.db.models import Q, Sum, Count | ||||||
| from seminar.utils import aktivniResitele, resi_v_rocniku, cisla_rocniku, hlavni_problem, hlavni_problemy_f, problemy_cisla, podproblemy_v_cislu | from seminar.utils import aktivniResitele, resi_v_rocniku, cisla_rocniku, hlavni_problem, hlavni_problemy_f, problemy_cisla, podproblemy_v_cislu | ||||||
| import time | import time | ||||||
| ### Výsledky |  | ||||||
| 
 | 
 | ||||||
| ROCNIK_ZRUSENI_TEMAT = 25 | ROCNIK_ZRUSENI_TEMAT = 25 | ||||||
| 
 | 
 | ||||||
|  | @ -142,7 +141,7 @@ def setrid_resitele_a_body(slov_resitel_body): | ||||||
| 	setrizene_body = [dvojice[1] for dvojice in slov_resitel_body] | 	setrizene_body = [dvojice[1] for dvojice in slov_resitel_body] | ||||||
| 	return setrizeni_resitele_id,  setrizene_body | 	return setrizeni_resitele_id,  setrizene_body | ||||||
| 
 | 
 | ||||||
| def vysledkovka_rocniku(rocnik, jen_verejne=True): | def data_vysledkovky_rocniku(rocnik, jen_verejne=True): | ||||||
| 	""" Přebírá ročník (např. context["rocnik"]) a vrací výsledkovou listinu ve | 	""" Přebírá ročník (např. context["rocnik"]) a vrací výsledkovou listinu ve | ||||||
| 	formě vhodné pro šablonu "seminar/vysledkovka_rocniku.html" | 	formě vhodné pro šablonu "seminar/vysledkovka_rocniku.html" | ||||||
| 	""" | 	""" | ||||||
|  | @ -197,7 +196,7 @@ def vysledkovka_rocniku(rocnik, jen_verejne=True): | ||||||
| 	end = time.time() | 	end = time.time() | ||||||
| 	print("Vysledkovka rocniku",end-start) | 	print("Vysledkovka rocniku",end-start) | ||||||
| 
 | 
 | ||||||
| 	return radky_vysledkovky | 	return radky_vysledkovky, cisla | ||||||
| 
 | 
 | ||||||
| class RadekVysledkovkyCisla(object): | class RadekVysledkovkyCisla(object): | ||||||
| 	"""Obsahuje věci, které se hodí vědět při konstruování výsledkovky. | 	"""Obsahuje věci, které se hodí vědět při konstruování výsledkovky. | ||||||
|  | @ -372,9 +371,7 @@ class FixedIterator: | ||||||
| # TODELETE | # TODELETE | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def vysledkovka_cisla(cislo, context=None): | def data_vysledkovky_cisla(cislo): | ||||||
| 	if context is None: |  | ||||||
| 		context = {} |  | ||||||
| 	problemy = problemy_cisla(cislo) | 	problemy = problemy_cisla(cislo) | ||||||
| 	hlavni_problemy = hlavni_problemy_f(problemy) | 	hlavni_problemy = hlavni_problemy_f(problemy) | ||||||
| 	## TODO možná chytřeji vybírat aktivní řešitele | 	## TODO možná chytřeji vybírat aktivní řešitele | ||||||
|  | @ -453,13 +450,11 @@ def vysledkovka_cisla(cislo, context=None): | ||||||
| 		i += 1 | 		i += 1 | ||||||
| 
 | 
 | ||||||
| 	# vytahané informace předáváme do kontextu | 	# vytahané informace předáváme do kontextu | ||||||
| 	context['cislo'] = cislo |  | ||||||
| 	context['radky_vysledkovky'] = radky_vysledkovky |  | ||||||
| 	context['problemy'] = temata_a_spol |  | ||||||
| 	context['ostatni'] = je_nejake_ostatni |  | ||||||
| 	pt = [podproblemy[it.id] for it in temata_a_spol]+[podproblemy[-1]] | 	pt = [podproblemy[it.id] for it in temata_a_spol]+[podproblemy[-1]] | ||||||
| 	context['podproblemy'] = pt | 	return ( | ||||||
| 	context['podproblemy_iter'] = FixedIterator(pt.__iter__())  # TODELETE | 		radky_vysledkovky, | ||||||
| 	#context['v_cisle_zadane'] = TODO | 		temata_a_spol, | ||||||
| 	#context['resene_problemy'] = resene_problemy | 		je_nejake_ostatni, | ||||||
| 	return context | 		pt, | ||||||
|  | 		FixedIterator(pt.__iter__()) | ||||||
|  | 	) | ||||||
							
								
								
									
										37
									
								
								vysledkovky/views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								vysledkovky/views.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | ||||||
|  | from .utils import data_vysledkovky_cisla, \ | ||||||
|  |     data_vysledkovky_rocniku | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def vysledkovka_cisla(cislo, context=None): | ||||||
|  |     if context is None: | ||||||
|  |         context = {} | ||||||
|  |     context['cislo'] = cislo | ||||||
|  | 
 | ||||||
|  |     ( | ||||||
|  |         context['radky_vysledkovky'], | ||||||
|  |         context['problemy'], | ||||||
|  |         context['ostatni'], | ||||||
|  |         context['podproblemy'], | ||||||
|  |         context['podproblemy_iter'] | ||||||
|  |     ) = data_vysledkovky_cisla(cislo) | ||||||
|  |     return context | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def vysledkovka_rocniku(rocnik, context=None, request=None, sneverejnou=False): | ||||||
|  |     if context is None: | ||||||
|  |         context = {} | ||||||
|  | 
 | ||||||
|  |     ( | ||||||
|  |         context['radky_vysledkovky'], | ||||||
|  |         context['cisla'] | ||||||
|  |     ) = data_vysledkovky_rocniku(rocnik) | ||||||
|  | 
 | ||||||
|  |     context['vysledkovka'] = len(context['cisla']) != 0 | ||||||
|  | 
 | ||||||
|  |     if sneverejnou and request and request.user.je_org: | ||||||
|  |         ( | ||||||
|  |             context['radky_vysledkovky_s_neverejnymi'], | ||||||
|  |             context['cisla_s_neverejnymi'] | ||||||
|  |         ) = data_vysledkovky_rocniku(rocnik, jen_verejne=False) | ||||||
|  | 
 | ||||||
|  |     return context | ||||||
		Loading…
	
		Reference in a new issue