Move personalni do aplikace personalni
This commit is contained in:
		
							parent
							
								
									61b6f4bfd9
								
							
						
					
					
						commit
						0cd1c3ef1a
					
				
					 27 changed files with 1330 additions and 1248 deletions
				
			
		|  | @ -140,6 +140,7 @@ INSTALLED_APPS = ( | ||||||
|     'aesop', |     'aesop', | ||||||
|     'odevzdavatko', |     'odevzdavatko', | ||||||
|     'vysledkovky', |     'vysledkovky', | ||||||
|  |     'personalni', | ||||||
| 
 | 
 | ||||||
|     # Admin upravy: |     # Admin upravy: | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -26,6 +26,10 @@ urlpatterns = [ | ||||||
| 	# Prednaskova aplikace (ma vlastni podadresare) | 	# Prednaskova aplikace (ma vlastni podadresare) | ||||||
| 	path('', include('prednasky.urls')), | 	path('', include('prednasky.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')), | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										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. | ||||||
|  | """ | ||||||
							
								
								
									
										48
									
								
								personalni/admin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								personalni/admin.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,48 @@ | ||||||
|  | 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" | ||||||
|  | 
 | ||||||
|  | @admin.register(m.Organizator) | ||||||
|  | class OrganizatorAdmin(admin.ModelAdmin): | ||||||
|  | 	search_fields = ['osoba__jmeno', 'osoba__prijmeni', 'osoba__prezdivka'] | ||||||
|  | 
 | ||||||
|  | class OsobaInline(admin.TabularInline): | ||||||
|  | 	model = m.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' | ||||||
							
								
								
									
										221
									
								
								personalni/forms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								personalni/forms.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,221 @@ | ||||||
|  | from django import forms | ||||||
|  | from dal import autocomplete | ||||||
|  | from django.contrib.auth.forms import PasswordResetForm | ||||||
|  | from django.core.exceptions import ObjectDoesNotExist | ||||||
|  | from django.contrib.auth.models import User | ||||||
|  | 
 | ||||||
|  | from seminar.models import Skola, Resitel, Osoba | ||||||
|  | 
 | ||||||
|  | from datetime import date | ||||||
|  | import logging | ||||||
|  | 
 | ||||||
|  | # pro přidání políčka do formuláře je potřeba | ||||||
|  | # - mít v modelu tu položku, kterou chci upravovat | ||||||
|  | # - přidat do views (prihlaskaView, resitelEditView) | ||||||
|  | # - přidat do forms | ||||||
|  | # - includovat do html | ||||||
|  | 
 | ||||||
|  | class DateInput(forms.DateInput): | ||||||
|  | 	# aby se datum dalo vybírat z kalendáře | ||||||
|  | 	input_type = 'date' | ||||||
|  | 
 | ||||||
|  | class TelInput(forms.TextInput): | ||||||
|  | 	# tohle je možná k niřemu, ale alepsoň to mění input type a nic to nekazí | ||||||
|  | 	input_type = 'tel' | ||||||
|  | 	input_pattern="^[+]?[()/0-9. -]{9,}$" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class PrihlaskaForm(PasswordResetForm): | ||||||
|  | 	username = forms.CharField(label='Přihlašovací jméno',  | ||||||
|  | 			max_length=256,  | ||||||
|  | 			required=True, | ||||||
|  | 			help_text='Tímto jménem se následně budeš přihlašovat pro odevzdání řešení a další činnosti v semináři') | ||||||
|  | 
 | ||||||
|  | 	jmeno = forms.CharField(label='Jméno', max_length=256, required=True) | ||||||
|  | 	prijmeni = forms.CharField(label='Příjmení', max_length=256, required=True) | ||||||
|  | 	pohlavi_muz = forms.ChoiceField(label='Pohlaví', | ||||||
|  | 			choices = ((True,'muž'),(False,'žena')), required=True) | ||||||
|  | 	email = forms.EmailField(label='E-mail',max_length=256, required=True) | ||||||
|  | 	telefon = forms.CharField(widget=TelInput(),label='Telefon',max_length=256, required=False) | ||||||
|  | 	datum_narozeni = forms.DateField(widget=DateInput(),label='Datum narození', required=False) | ||||||
|  | 	ulice = forms.CharField(label='Ulice a číslo popisné', max_length=256, required=False) | ||||||
|  | 	mesto = forms.CharField(label='Město', max_length=256, required=False) | ||||||
|  | 	psc = forms.CharField(label='PSČ', max_length=32, required=False) | ||||||
|  | 	stat = forms.ChoiceField(label='Stát',  | ||||||
|  | 			choices = (('CZ', 'Česká Republika'), | ||||||
|  | 				('SK', 'Slovenská Republika'), | ||||||
|  | 				('other', 'Jiné')), | ||||||
|  | 			required=False) | ||||||
|  | 	stat_text = forms.CharField(label='Stát', max_length=256, required=False) | ||||||
|  | 
 | ||||||
|  | 	skola = forms.ModelChoiceField(label="Škola", | ||||||
|  | 		queryset=Skola.objects.all(), | ||||||
|  | 		widget=autocomplete.ModelSelect2( | ||||||
|  | 			url='autocomplete_skola', | ||||||
|  | 			attrs = {'data-placeholder--id': '-1', | ||||||
|  | 				'data-placeholder--text' : '---', | ||||||
|  | 				'data-allow-clear': 'true'}) | ||||||
|  | 			,required=False) | ||||||
|  | 	 | ||||||
|  | 	skola_nazev = forms.CharField(label='Název školy', max_length=256, required=False) | ||||||
|  | 	skola_adresa = forms.CharField(label='Adresa školy', max_length=256, required=False) | ||||||
|  | 
 | ||||||
|  | #	trida = forms.CharField(label='Třída',max_length=10, required=True) | ||||||
|  | 
 | ||||||
|  | 	rok_maturity = forms.IntegerField( | ||||||
|  | 		label='Rok maturity',  | ||||||
|  | 		min_value=date.today().year,  | ||||||
|  | 		max_value=date.today().year+8, | ||||||
|  | 		required=True) | ||||||
|  | 	zasilat = forms.ChoiceField(label='Kam zasílat čísla a řešení',choices = Resitel.ZASILAT_CHOICES, required=True) | ||||||
|  | 	zasilat_cislo_emailem = forms.BooleanField(label='Chci dostávat e-mailem upozornění na vydání nového čísla', required=False) | ||||||
|  | 
 | ||||||
|  | 	gdpr = forms.BooleanField(label='Souhlasím se zpracováním osobních údajů', required=True) | ||||||
|  | 	spam = forms.BooleanField(label='Souhlasím se zasíláním materiálů od MFF UK', required=False) | ||||||
|  | 	 | ||||||
|  | 	def clean_username(self): | ||||||
|  | 		err_logger = logging.getLogger('seminar.prihlaska.problem') | ||||||
|  | 		username = self.cleaned_data.get('username') | ||||||
|  | 		try: | ||||||
|  | 			User.objects.get(username=username) | ||||||
|  | 			msg = "Username {} exists".format(username) | ||||||
|  | 			err_logger.info(msg) | ||||||
|  | 			raise forms.ValidationError('Přihlašovací jméno je již použito') | ||||||
|  | 
 | ||||||
|  | 		except ObjectDoesNotExist: | ||||||
|  | 			pass | ||||||
|  | 		return username | ||||||
|  | 
 | ||||||
|  | 	def clean_email(self): | ||||||
|  | 		err_logger = logging.getLogger('seminar.prihlaska.problem') | ||||||
|  | 		email = self.cleaned_data.get('email') | ||||||
|  | 		try: | ||||||
|  | 			osoba = Osoba.objects.get(email=email) | ||||||
|  | 			msg = "Email {} exists".format(email) | ||||||
|  | 			if osoba.user is not None: | ||||||
|  | 				err_logger.info(msg) | ||||||
|  | 				raise forms.ValidationError('E-mail je již použit') | ||||||
|  | 			else: | ||||||
|  | 				msg += ', but currently has no User, so allowing registration.' | ||||||
|  | 				err_logger.info(msg) | ||||||
|  | 
 | ||||||
|  | 		except ObjectDoesNotExist: | ||||||
|  | 			pass | ||||||
|  | 		return email | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 	def clean(self): | ||||||
|  | 		super().clean() | ||||||
|  | 		 | ||||||
|  | 		err_logger = logging.getLogger('seminar.prihlaska.problem') | ||||||
|  | 
 | ||||||
|  | 		data = self.cleaned_data | ||||||
|  | 		if data.get('stat') != 'other' and data.get('stat_text') != '': | ||||||
|  | 			self.add_error('stat',forms.ValidationError('Nelze mít vybraný stát z menu a zároven zapsaný textem')) | ||||||
|  | 		if data.get('skola') and (data.get('skola_nazev') or data.get('skola_adresa')): | ||||||
|  | 			self.add_error('skola',forms.ValidationError('Pokud je škola v seznamu, nevypisujte ji ručně, pokud není, zrušte výběr ze seznamu (křížek vpravo)')) | ||||||
|  | 		if not data.get('skola'): | ||||||
|  | 			if data.get('skola_nazev')=='' and data.get('skola_adresa')=='': | ||||||
|  | 				self.add_error('skola',forms.ValidationError('Je nutné vyplnit školu')) | ||||||
|  | 			elif data.get('skola_nazev')=='': | ||||||
|  | 				self.add_error('skola_nazev',forms.ValidationError('Je nutné vyplnit název školy')) | ||||||
|  | 			elif data.get('skola_adresa')=='': | ||||||
|  | 				self.add_error('skola_adresa',forms.ValidationError('Je nutné vyplnit adresu školy')) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ProfileEditForm(forms.Form): | ||||||
|  | 	username = forms.CharField(label='Přihlašovací jméno', | ||||||
|  | 			max_length=256, | ||||||
|  | 			required=False, | ||||||
|  | 			disabled=True) | ||||||
|  | 
 | ||||||
|  | 	jmeno = forms.CharField(label='Jméno', max_length=256, required=True) | ||||||
|  | 	prijmeni = forms.CharField(label='Příjmení', max_length=256, required=True) | ||||||
|  | 	pohlavi_muz = forms.ChoiceField(label='Pohlaví', | ||||||
|  | 			choices = ((True,'muž'),(False,'žena')), required=True) | ||||||
|  | 	email = forms.EmailField(label='E-mail',max_length=256, required=True) | ||||||
|  | 	telefon = forms.CharField(widget=TelInput(),label='Telefon',max_length=256, required=False) | ||||||
|  | 	datum_narozeni = forms.DateField(widget=DateInput(),label='Datum narození', required=False) | ||||||
|  | 	ulice = forms.CharField(label='Ulice', max_length=256, required=False) | ||||||
|  | 	mesto = forms.CharField(label='Město', max_length=256, required=False) | ||||||
|  | 	psc = forms.CharField(label='PSČ', max_length=32, required=False) | ||||||
|  | 	stat = forms.ChoiceField(label='Stát',  | ||||||
|  | 			choices = (('CZ', 'Česká republika'), | ||||||
|  | 				('SK', 'Slovenská republika'), | ||||||
|  | 				('other', 'Jiné')), | ||||||
|  | 			required=False) | ||||||
|  | 	stat_text = forms.CharField(label='Stát', max_length=256, required=False) | ||||||
|  | 
 | ||||||
|  | 	skola = forms.ModelChoiceField(label="Škola", | ||||||
|  | 		queryset=Skola.objects.all(), | ||||||
|  | 		widget=autocomplete.ModelSelect2( | ||||||
|  | 			url='autocomplete_skola', | ||||||
|  | 			attrs = {'data-placeholder--id': '-1', | ||||||
|  | 				'data-placeholder--text' : '---', | ||||||
|  | 				'data-allow-clear': 'true'}) | ||||||
|  | 			,required=False) | ||||||
|  | 	 | ||||||
|  | 	skola_nazev = forms.CharField(label='Název školy', max_length=256, required=False) | ||||||
|  | 	skola_adresa = forms.CharField(label='Adresa školy', max_length=256, required=False) | ||||||
|  | 
 | ||||||
|  | #	trida = forms.CharField(label='Třída',max_length=10, required=True) | ||||||
|  | 
 | ||||||
|  | 	rok_maturity = forms.IntegerField( | ||||||
|  | 		label='Rok maturity',  | ||||||
|  | 		min_value=date.today().year,  | ||||||
|  | 		max_value=date.today().year+8, | ||||||
|  | 		required=True) | ||||||
|  | 	zasilat = forms.ChoiceField(label='Kam zasílat čísla a řešení',choices = Resitel.ZASILAT_CHOICES, required=True) | ||||||
|  | 	zasilat_cislo_emailem = forms.BooleanField(label='Chci dostávat email s upozorněním na vydání nového čísla', required=False) | ||||||
|  | 
 | ||||||
|  | 	spam = forms.BooleanField(label='Souhlasím se zasíláním materiálů od MFF UK', required=False) | ||||||
|  | #	def clean_username(self): | ||||||
|  | #		err_logger = logging.getLogger('seminar.prihlaska.problem') | ||||||
|  | #		username = self.cleaned_data.get('username') | ||||||
|  | #		try: | ||||||
|  | #			User.objects.get(username=username) | ||||||
|  | #			msg = "Username {} exists".format(username) | ||||||
|  | #			err_logger.info(msg) | ||||||
|  | #			raise forms.ValidationError('Přihlašovací jméno je již použito') | ||||||
|  | # | ||||||
|  | #		except ObjectDoesNotExist: | ||||||
|  | #			pass | ||||||
|  | #		return username | ||||||
|  | # | ||||||
|  | 	def clean_email(self): | ||||||
|  | 		err_logger = logging.getLogger('seminar.prihlaska.problem') | ||||||
|  | 		email = self.cleaned_data.get('email') | ||||||
|  | 		try: | ||||||
|  | 			Osoba.objects.exclude(user__username=self.username).get(email=email) | ||||||
|  | 			msg = "Email {} exists (in edit)".format(email) | ||||||
|  | 			err_logger.info(msg) | ||||||
|  | 			raise forms.ValidationError('Email je již použit') | ||||||
|  | 
 | ||||||
|  | 		except ObjectDoesNotExist: | ||||||
|  | 			pass | ||||||
|  | 		return email | ||||||
|  | 	#def clean(self): | ||||||
|  | 	#	super().clean() | ||||||
|  | 	#	 | ||||||
|  | 	#	err_logger = logging.getLogger('seminar.prihlaska.problem') | ||||||
|  | 
 | ||||||
|  | 	#	data = self.cleaned_data | ||||||
|  | 	#	if data.get('password') != data.get('password_check'): | ||||||
|  | 	#		self.add_error('password_check',forms.ValidationError('Hesla se neshodují')) | ||||||
|  | 	#	if data.get('stat') != '' and data.get('stat_text') != '': | ||||||
|  | 	#		self.add_error('stat',forms.ValidationError('Nelze mít vybraný stát z menu a zároven zapsaný textem')) | ||||||
|  | 	#	if data.get('skola') and (data.get('skola_nazev') or data.get('skola_adresa')): | ||||||
|  | 	#		self.add_error('skola',forms.ValidationError('Pokud je škola v seznamu, nevypisujte ji ručně, pokud není, zrušte výběr ze seznamu (křížek vpravo)')) | ||||||
|  | 	#	if not data.get('skola'): | ||||||
|  | 	#		if data.get('skola_nazev')=='' and data.get('skola_adresa')=='': | ||||||
|  | 	#			self.add_error('skola',forms.ValidationError('Je nutné vyplnit školu')) | ||||||
|  | 	#		elif data.get('skola_nazev')=='': | ||||||
|  | 	#			self.add_error('skola_nazev',forms.ValidationError('Je nutné vyplnit název školy')) | ||||||
|  | 	#		elif data.get('skola_adresa')=='': | ||||||
|  | 	#			self.add_error('skola_adresa',forms.ValidationError('Je nutné vyplnit adresu školy')) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class PoMaturiteProfileEditForm(ProfileEditForm): | ||||||
|  | 	rok_maturity = forms.IntegerField( | ||||||
|  | 		label='Rok maturity', | ||||||
|  | 		required=True) | ||||||
							
								
								
									
										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) | ||||||
|  | @ -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 | ||||||
| 
 | 
 | ||||||
|  | @ -17,8 +14,6 @@ from seminar.utils import hlavni_problem | ||||||
| import seminar.models as m | import seminar.models as m | ||||||
| import seminar.treelib as tl | 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): | ||||||
|  | @ -105,45 +100,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" |  | ||||||
| 
 |  | ||||||
| @admin.register(m.Organizator) |  | ||||||
| class OrganizatorAdmin(admin.ModelAdmin): |  | ||||||
| 	search_fields = ['osoba__jmeno', 'osoba__prijmeni', 'osoba__prezdivka'] |  | ||||||
| 
 |  | ||||||
| class OsobaInline(admin.TabularInline): |  | ||||||
| 	model = m.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): | ||||||
|  |  | ||||||
							
								
								
									
										213
									
								
								seminar/forms.py
									
									
									
									
									
								
							
							
						
						
									
										213
									
								
								seminar/forms.py
									
									
									
									
									
								
							|  | @ -1,225 +1,12 @@ | ||||||
| from django import forms | from django import forms | ||||||
| from dal import autocomplete |  | ||||||
| from django.contrib.auth.forms import PasswordResetForm |  | ||||||
| from django.core.exceptions import ObjectDoesNotExist |  | ||||||
| from django.contrib.auth.models import User |  | ||||||
| 
 |  | ||||||
| from .models import Skola, Resitel, Osoba |  | ||||||
| import seminar.models as m | import seminar.models as m | ||||||
| 
 | 
 | ||||||
| from datetime import date |  | ||||||
| import logging |  | ||||||
| 
 |  | ||||||
| # pro přidání políčka do formuláře je potřeba | # pro přidání políčka do formuláře je potřeba | ||||||
| # - mít v modelu tu položku, kterou chci upravovat | # - mít v modelu tu položku, kterou chci upravovat | ||||||
| # - přidat do views (prihlaskaView, resitelEditView) | # - přidat do views (prihlaskaView, resitelEditView) | ||||||
| # - přidat do forms | # - přidat do forms | ||||||
| # - includovat do html | # - includovat do html | ||||||
| 
 | 
 | ||||||
| class DateInput(forms.DateInput): |  | ||||||
|     # aby se datum dalo vybírat z kalendáře |  | ||||||
|     input_type = 'date'  |  | ||||||
|      |  | ||||||
| class TelInput(forms.TextInput): |  | ||||||
|      # tohle je možná k niřemu, ale alepsoň to mění input type a nic to nekazí |  | ||||||
|     input_type = 'tel' |  | ||||||
|     input_pattern="^[+]?[()/0-9. -]{9,}$" |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class PrihlaskaForm(PasswordResetForm): |  | ||||||
| 	username = forms.CharField(label='Přihlašovací jméno',  |  | ||||||
| 			max_length=256,  |  | ||||||
| 			required=True, |  | ||||||
| 			help_text='Tímto jménem se následně budeš přihlašovat pro odevzdání řešení a další činnosti v semináři') |  | ||||||
| 
 |  | ||||||
| 	jmeno = forms.CharField(label='Jméno', max_length=256, required=True) |  | ||||||
| 	prijmeni = forms.CharField(label='Příjmení', max_length=256, required=True) |  | ||||||
| 	pohlavi_muz = forms.ChoiceField(label='Pohlaví', |  | ||||||
| 			choices = ((True,'muž'),(False,'žena')), required=True) |  | ||||||
| 	email = forms.EmailField(label='E-mail',max_length=256, required=True) |  | ||||||
| 	telefon = forms.CharField(widget=TelInput(),label='Telefon',max_length=256, required=False) |  | ||||||
| 	datum_narozeni = forms.DateField(widget=DateInput(),label='Datum narození', required=False) |  | ||||||
| 	ulice = forms.CharField(label='Ulice a číslo popisné', max_length=256, required=False) |  | ||||||
| 	mesto = forms.CharField(label='Město', max_length=256, required=False) |  | ||||||
| 	psc = forms.CharField(label='PSČ', max_length=32, required=False) |  | ||||||
| 	stat = forms.ChoiceField(label='Stát',  |  | ||||||
| 			choices = (('CZ', 'Česká Republika'), |  | ||||||
| 				('SK', 'Slovenská Republika'), |  | ||||||
| 				('other', 'Jiné')), |  | ||||||
| 			required=False) |  | ||||||
| 	stat_text = forms.CharField(label='Stát', max_length=256, required=False) |  | ||||||
| 
 |  | ||||||
| 	skola = forms.ModelChoiceField(label="Škola", |  | ||||||
| 		queryset=Skola.objects.all(), |  | ||||||
| 		widget=autocomplete.ModelSelect2( |  | ||||||
| 			url='autocomplete_skola', |  | ||||||
| 			attrs = {'data-placeholder--id': '-1', |  | ||||||
| 				'data-placeholder--text' : '---', |  | ||||||
| 				'data-allow-clear': 'true'}) |  | ||||||
|     		,required=False) |  | ||||||
| 	 |  | ||||||
| 	skola_nazev = forms.CharField(label='Název školy', max_length=256, required=False) |  | ||||||
| 	skola_adresa = forms.CharField(label='Adresa školy', max_length=256, required=False) |  | ||||||
| 
 |  | ||||||
| #	trida = forms.CharField(label='Třída',max_length=10, required=True) |  | ||||||
| 
 |  | ||||||
| 	rok_maturity = forms.IntegerField( |  | ||||||
| 		label='Rok maturity',  |  | ||||||
| 		min_value=date.today().year,  |  | ||||||
| 		max_value=date.today().year+8, |  | ||||||
| 		required=True) |  | ||||||
| 	zasilat = forms.ChoiceField(label='Kam zasílat čísla a řešení',choices = Resitel.ZASILAT_CHOICES, required=True) |  | ||||||
| 	zasilat_cislo_emailem = forms.BooleanField(label='Chci dostávat e-mailem upozornění na vydání nového čísla', required=False) |  | ||||||
| 
 |  | ||||||
| 	gdpr = forms.BooleanField(label='Souhlasím se zpracováním osobních údajů', required=True) |  | ||||||
| 	spam = forms.BooleanField(label='Souhlasím se zasíláním materiálů od MFF UK', required=False) |  | ||||||
| 	 |  | ||||||
| 	def clean_username(self): |  | ||||||
| 		err_logger = logging.getLogger('seminar.prihlaska.problem') |  | ||||||
| 		username = self.cleaned_data.get('username') |  | ||||||
| 		try: |  | ||||||
| 			User.objects.get(username=username) |  | ||||||
| 			msg = "Username {} exists".format(username) |  | ||||||
| 			err_logger.info(msg) |  | ||||||
| 			raise forms.ValidationError('Přihlašovací jméno je již použito') |  | ||||||
| 
 |  | ||||||
| 		except ObjectDoesNotExist: |  | ||||||
| 			pass |  | ||||||
| 		return username |  | ||||||
| 
 |  | ||||||
| 	def clean_email(self): |  | ||||||
| 		err_logger = logging.getLogger('seminar.prihlaska.problem') |  | ||||||
| 		email = self.cleaned_data.get('email') |  | ||||||
| 		try: |  | ||||||
| 			osoba = Osoba.objects.get(email=email) |  | ||||||
| 			msg = "Email {} exists".format(email) |  | ||||||
| 			if osoba.user is not None: |  | ||||||
| 				err_logger.info(msg) |  | ||||||
| 				raise forms.ValidationError('E-mail je již použit') |  | ||||||
| 			else: |  | ||||||
| 				msg += ', but currently has no User, so allowing registration.' |  | ||||||
| 				err_logger.info(msg) |  | ||||||
| 
 |  | ||||||
| 		except ObjectDoesNotExist: |  | ||||||
| 			pass |  | ||||||
| 		return email |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 	def clean(self): |  | ||||||
| 		super().clean() |  | ||||||
| 		 |  | ||||||
| 		err_logger = logging.getLogger('seminar.prihlaska.problem') |  | ||||||
| 
 |  | ||||||
| 		data = self.cleaned_data |  | ||||||
| 		if data.get('stat') != 'other' and data.get('stat_text') != '': |  | ||||||
| 			self.add_error('stat',forms.ValidationError('Nelze mít vybraný stát z menu a zároven zapsaný textem')) |  | ||||||
| 		if data.get('skola') and (data.get('skola_nazev') or data.get('skola_adresa')): |  | ||||||
| 			self.add_error('skola',forms.ValidationError('Pokud je škola v seznamu, nevypisujte ji ručně, pokud není, zrušte výběr ze seznamu (křížek vpravo)')) |  | ||||||
| 		if not data.get('skola'): |  | ||||||
| 			if data.get('skola_nazev')=='' and data.get('skola_adresa')=='': |  | ||||||
| 				self.add_error('skola',forms.ValidationError('Je nutné vyplnit školu')) |  | ||||||
| 			elif data.get('skola_nazev')=='': |  | ||||||
| 				self.add_error('skola_nazev',forms.ValidationError('Je nutné vyplnit název školy')) |  | ||||||
| 			elif data.get('skola_adresa')=='': |  | ||||||
| 				self.add_error('skola_adresa',forms.ValidationError('Je nutné vyplnit adresu školy')) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class ProfileEditForm(forms.Form): |  | ||||||
| 	username = forms.CharField(label='Přihlašovací jméno', |  | ||||||
| 			max_length=256, |  | ||||||
| 			required=False, |  | ||||||
| 			disabled=True) |  | ||||||
| 
 |  | ||||||
| 	jmeno = forms.CharField(label='Jméno', max_length=256, required=True) |  | ||||||
| 	prijmeni = forms.CharField(label='Příjmení', max_length=256, required=True) |  | ||||||
| 	pohlavi_muz = forms.ChoiceField(label='Pohlaví', |  | ||||||
| 			choices = ((True,'muž'),(False,'žena')), required=True) |  | ||||||
| 	email = forms.EmailField(label='E-mail',max_length=256, required=True) |  | ||||||
| 	telefon = forms.CharField(widget=TelInput(),label='Telefon',max_length=256, required=False) |  | ||||||
| 	datum_narozeni = forms.DateField(widget=DateInput(),label='Datum narození', required=False) |  | ||||||
| 	ulice = forms.CharField(label='Ulice', max_length=256, required=False) |  | ||||||
| 	mesto = forms.CharField(label='Město', max_length=256, required=False) |  | ||||||
| 	psc = forms.CharField(label='PSČ', max_length=32, required=False) |  | ||||||
| 	stat = forms.ChoiceField(label='Stát',  |  | ||||||
| 			choices = (('CZ', 'Česká republika'), |  | ||||||
| 				('SK', 'Slovenská republika'), |  | ||||||
| 				('other', 'Jiné')), |  | ||||||
| 			required=False) |  | ||||||
| 	stat_text = forms.CharField(label='Stát', max_length=256, required=False) |  | ||||||
| 
 |  | ||||||
| 	skola = forms.ModelChoiceField(label="Škola", |  | ||||||
| 		queryset=Skola.objects.all(), |  | ||||||
| 		widget=autocomplete.ModelSelect2( |  | ||||||
| 			url='autocomplete_skola', |  | ||||||
| 			attrs = {'data-placeholder--id': '-1', |  | ||||||
| 				'data-placeholder--text' : '---', |  | ||||||
| 				'data-allow-clear': 'true'}) |  | ||||||
|     		,required=False) |  | ||||||
| 	 |  | ||||||
| 	skola_nazev = forms.CharField(label='Název školy', max_length=256, required=False) |  | ||||||
| 	skola_adresa = forms.CharField(label='Adresa školy', max_length=256, required=False) |  | ||||||
| 
 |  | ||||||
| #	trida = forms.CharField(label='Třída',max_length=10, required=True) |  | ||||||
| 
 |  | ||||||
| 	rok_maturity = forms.IntegerField( |  | ||||||
| 		label='Rok maturity',  |  | ||||||
| 		min_value=date.today().year,  |  | ||||||
| 		max_value=date.today().year+8, |  | ||||||
| 		required=True) |  | ||||||
| 	zasilat = forms.ChoiceField(label='Kam zasílat čísla a řešení',choices = Resitel.ZASILAT_CHOICES, required=True) |  | ||||||
| 	zasilat_cislo_emailem = forms.BooleanField(label='Chci dostávat email s upozorněním na vydání nového čísla', required=False) |  | ||||||
| 
 |  | ||||||
| 	spam = forms.BooleanField(label='Souhlasím se zasíláním materiálů od MFF UK', required=False) |  | ||||||
| #	def clean_username(self): |  | ||||||
| #		err_logger = logging.getLogger('seminar.prihlaska.problem') |  | ||||||
| #		username = self.cleaned_data.get('username') |  | ||||||
| #		try: |  | ||||||
| #			User.objects.get(username=username) |  | ||||||
| #			msg = "Username {} exists".format(username) |  | ||||||
| #			err_logger.info(msg) |  | ||||||
| #			raise forms.ValidationError('Přihlašovací jméno je již použito') |  | ||||||
| # |  | ||||||
| #		except ObjectDoesNotExist: |  | ||||||
| #			pass |  | ||||||
| #		return username |  | ||||||
| # |  | ||||||
| 	def clean_email(self): |  | ||||||
| 		err_logger = logging.getLogger('seminar.prihlaska.problem') |  | ||||||
| 		email = self.cleaned_data.get('email') |  | ||||||
| 		try: |  | ||||||
| 			Osoba.objects.exclude(user__username=self.username).get(email=email) |  | ||||||
| 			msg = "Email {} exists (in edit)".format(email) |  | ||||||
| 			err_logger.info(msg) |  | ||||||
| 			raise forms.ValidationError('Email je již použit') |  | ||||||
| 
 |  | ||||||
| 		except ObjectDoesNotExist: |  | ||||||
| 			pass |  | ||||||
| 		return email |  | ||||||
| 	#def clean(self): |  | ||||||
| 	#	super().clean() |  | ||||||
| 	#	 |  | ||||||
| 	#	err_logger = logging.getLogger('seminar.prihlaska.problem') |  | ||||||
| 
 |  | ||||||
| 	#	data = self.cleaned_data |  | ||||||
| 	#	if data.get('password') != data.get('password_check'): |  | ||||||
| 	#		self.add_error('password_check',forms.ValidationError('Hesla se neshodují')) |  | ||||||
| 	#	if data.get('stat') != '' and data.get('stat_text') != '': |  | ||||||
| 	#		self.add_error('stat',forms.ValidationError('Nelze mít vybraný stát z menu a zároven zapsaný textem')) |  | ||||||
| 	#	if data.get('skola') and (data.get('skola_nazev') or data.get('skola_adresa')): |  | ||||||
| 	#		self.add_error('skola',forms.ValidationError('Pokud je škola v seznamu, nevypisujte ji ručně, pokud není, zrušte výběr ze seznamu (křížek vpravo)')) |  | ||||||
| 	#	if not data.get('skola'): |  | ||||||
| 	#		if data.get('skola_nazev')=='' and data.get('skola_adresa')=='': |  | ||||||
| 	#			self.add_error('skola',forms.ValidationError('Je nutné vyplnit školu')) |  | ||||||
| 	#		elif data.get('skola_nazev')=='': |  | ||||||
| 	#			self.add_error('skola_nazev',forms.ValidationError('Je nutné vyplnit název školy')) |  | ||||||
| 	#		elif data.get('skola_adresa')=='': |  | ||||||
| 	#			self.add_error('skola_adresa',forms.ValidationError('Je nutné vyplnit adresu školy')) |  | ||||||
| 
 |  | ||||||
| class PoMaturiteProfileEditForm(ProfileEditForm): |  | ||||||
| 	rok_maturity = forms.IntegerField( |  | ||||||
| 		label='Rok maturity', |  | ||||||
| 		required=True) |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| class NahrajObrazekKTreeNoduForm(forms.ModelForm): | class NahrajObrazekKTreeNoduForm(forms.ModelForm): | ||||||
| 	class Meta: | 	class Meta: | ||||||
|  |  | ||||||
|  | @ -1,2 +1,4 @@ | ||||||
| from .models_all import * | from .models_all import * | ||||||
| from .odevzdavatko import * | from .odevzdavatko import * | ||||||
|  | from .base import * | ||||||
|  | from .personalni 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 | ||||||
|  | 
 | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||||
| import os | import os | ||||||
| import random |  | ||||||
| import subprocess | import subprocess | ||||||
| import pathlib | import pathlib | ||||||
| import tempfile | import tempfile | ||||||
|  | @ -8,21 +7,17 @@ import logging | ||||||
| 
 | 
 | ||||||
| from django.contrib.sites.shortcuts import get_current_site | from django.contrib.sites.shortcuts import get_current_site | ||||||
| from django.db import models | from django.db import models | ||||||
| from django.contrib import auth |  | ||||||
| from django.utils import timezone | from django.utils import timezone | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.utils.encoding import force_text | from django.urls import reverse | ||||||
| from django.utils.text import slugify |  | ||||||
| from django.urls import reverse, reverse_lazy |  | ||||||
| from django.core.cache import cache | from django.core.cache import cache | ||||||
| from django.core.exceptions import ObjectDoesNotExist, ValidationError | from django.core.exceptions import ObjectDoesNotExist, ValidationError | ||||||
| from django.contrib.contenttypes.models import ContentType | from django.contrib.contenttypes.models import ContentType | ||||||
| from django.utils.text import get_valid_filename | from django.utils.text import get_valid_filename | ||||||
| from imagekit.models import ImageSpecField, ProcessedImageField | from imagekit.models import ImageSpecField | ||||||
| from imagekit.processors import ResizeToFit, Transpose | from imagekit.processors import ResizeToFit | ||||||
| from django.utils.functional import cached_property | from django.utils.functional import cached_property | ||||||
| 
 | 
 | ||||||
| from django_countries.fields import CountryField |  | ||||||
| from solo.models import SingletonModel | from solo.models import SingletonModel | ||||||
| from taggit.managers import TaggableManager | from taggit.managers import TaggableManager | ||||||
| 
 | 
 | ||||||
|  | @ -39,394 +34,12 @@ from polymorphic.models import PolymorphicModel | ||||||
| from django.core.mail import EmailMessage | from django.core.mail import EmailMessage | ||||||
| from seminar.utils import aktivniResitele | from seminar.utils import aktivniResitele | ||||||
| 
 | 
 | ||||||
|  | from . import personalni as pm | ||||||
|  | 
 | ||||||
|  | from .base import SeminarModelBase | ||||||
|  | 
 | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
| 
 | 
 | ||||||
| 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 |  | ||||||
| 
 |  | ||||||
| @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) | @reversion.register(ignore_duplicates=True) | ||||||
| class Rocnik(SeminarModelBase): | class Rocnik(SeminarModelBase): | ||||||
|  | @ -703,59 +316,6 @@ class Cislo(SeminarModelBase): | ||||||
| 			if self.datum_deadline_soustredeni is not None and self.datum_deadline_soustredeni > self.datum_deadline: | 			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"}) | 				raise ValidationError({'datum_deadline_soustredeni': "Soustřeďkový deadline musí předcházet finálnímu deadlinu"}) | ||||||
| 
 | 
 | ||||||
| @reversion.register(ignore_duplicates=True) |  | ||||||
| class Organizator(SeminarModelBase): |  | ||||||
| # zmena dedicnosti z models.Model na SeminarModelBase, potencialni vznik bugu |  | ||||||
| 
 |  | ||||||
| 	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'] |  | ||||||
| 
 | 
 | ||||||
| @reversion.register(ignore_duplicates=True) | @reversion.register(ignore_duplicates=True) | ||||||
| class Soustredeni(SeminarModelBase): | class Soustredeni(SeminarModelBase): | ||||||
|  | @ -783,10 +343,10 @@ class Soustredeni(SeminarModelBase): | ||||||
| 	misto = models.CharField('místo soustředění', max_length=256, blank=True, default='', | 	misto = models.CharField('místo soustředění', max_length=256, blank=True, default='', | ||||||
| 		help_text='Místo (název obce, volitelně též objektu') | 		help_text='Místo (název obce, volitelně též objektu') | ||||||
| 
 | 
 | ||||||
| 	ucastnici = models.ManyToManyField(Resitel, verbose_name='účastníci soustředění', | 	ucastnici = models.ManyToManyField(pm.Resitel, verbose_name='účastníci soustředění', | ||||||
| 		help_text='Seznam účastníků soustředění', through='Soustredeni_Ucastnici') | 		help_text='Seznam účastníků soustředění', through='Soustredeni_Ucastnici') | ||||||
| 
 | 
 | ||||||
| 	organizatori = models.ManyToManyField(Organizator, | 	organizatori = models.ManyToManyField(pm.Organizator, | ||||||
| 			verbose_name='Organizátoři soustředění', | 			verbose_name='Organizátoři soustředění', | ||||||
| 			help_text='Seznam organizátorů soustředění', | 			help_text='Seznam organizátorů soustředění', | ||||||
| 			through='Soustredeni_Organizatori') | 			through='Soustredeni_Organizatori') | ||||||
|  | @ -865,15 +425,15 @@ class Problem(SeminarModelBase,PolymorphicModel): | ||||||
| 	poznamka = models.TextField('org poznámky (HTML)', 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 ...') | 		help_text='Neveřejný návrh úlohy, návrh řešení, text zadání, poznámky ...') | ||||||
| 
 | 
 | ||||||
| 	autor = models.ForeignKey(Organizator, verbose_name='autor problému',  | 	autor = models.ForeignKey(pm.Organizator, verbose_name='autor problému', | ||||||
| 		related_name='autor_problemu_%(class)s', null=True, blank=True, | 		related_name='autor_problemu_%(class)s', null=True, blank=True, | ||||||
| 		on_delete=models.SET_NULL) | 		on_delete=models.SET_NULL) | ||||||
| 
 | 
 | ||||||
| 	garant = models.ForeignKey(Organizator, verbose_name='garant zadaného problému',  | 	garant = models.ForeignKey(pm.Organizator, verbose_name='garant zadaného problému', | ||||||
| 		related_name='garant_problemu_%(class)s', null=True, blank=True, | 		related_name='garant_problemu_%(class)s', null=True, blank=True, | ||||||
| 		on_delete=models.SET_NULL) | 		on_delete=models.SET_NULL) | ||||||
| 
 | 
 | ||||||
| 	opravovatele = models.ManyToManyField(Organizator, verbose_name='opravovatelé',  | 	opravovatele = models.ManyToManyField(pm.Organizator, verbose_name='opravovatelé', | ||||||
| 		blank=True, related_name='opravovatele_%(class)s') | 		blank=True, related_name='opravovatele_%(class)s') | ||||||
| 
 | 
 | ||||||
| 	kod = models.CharField('lokální kód', max_length=32, blank=True, default='', | 	kod = models.CharField('lokální kód', max_length=32, blank=True, default='', | ||||||
|  | @ -1126,7 +686,7 @@ class Pohadka(SeminarModelBase): | ||||||
| 	id = models.AutoField(primary_key=True) | 	id = models.AutoField(primary_key=True) | ||||||
| 
 | 
 | ||||||
| 	autor = models.ForeignKey( | 	autor = models.ForeignKey( | ||||||
| 		Organizator, | 		pm.Organizator, | ||||||
| 		verbose_name="Autor pohádky", | 		verbose_name="Autor pohádky", | ||||||
| 
 | 
 | ||||||
| 		# Při nahrávání z TeXu není vyplnění vyžadováno, v adminu je | 		# Při nahrávání z TeXu není vyplnění vyžadováno, v adminu je | ||||||
|  | @ -1171,7 +731,7 @@ class Soustredeni_Ucastnici(SeminarModelBase): | ||||||
| 	# Interní ID | 	# Interní ID | ||||||
| 	id = models.AutoField(primary_key = True) | 	id = models.AutoField(primary_key = True) | ||||||
| 
 | 
 | ||||||
| 	resitel = models.ForeignKey(Resitel, verbose_name='řešitel', on_delete=models.PROTECT) | 	resitel = models.ForeignKey(pm.Resitel, verbose_name='řešitel', on_delete=models.PROTECT) | ||||||
| 
 | 
 | ||||||
| 	soustredeni = models.ForeignKey(Soustredeni, verbose_name='soustředění', | 	soustredeni = models.ForeignKey(Soustredeni, verbose_name='soustředění', | ||||||
| 		on_delete=models.PROTECT) | 		on_delete=models.PROTECT) | ||||||
|  | @ -1196,7 +756,7 @@ class Soustredeni_Organizatori(SeminarModelBase): | ||||||
| 	# Interní ID | 	# Interní ID | ||||||
| 	id = models.AutoField(primary_key = True) | 	id = models.AutoField(primary_key = True) | ||||||
| 
 | 
 | ||||||
| 	organizator = models.ForeignKey(Organizator, verbose_name='organizátor', | 	organizator = models.ForeignKey(pm.Organizator, verbose_name='organizátor', | ||||||
| 		on_delete=models.PROTECT) | 		on_delete=models.PROTECT) | ||||||
| 
 | 
 | ||||||
| 	soustredeni = models.ForeignKey(Soustredeni, verbose_name='soustředění', | 	soustredeni = models.ForeignKey(Soustredeni, verbose_name='soustředění', | ||||||
|  | @ -1225,7 +785,7 @@ class Konfera(Problem): | ||||||
| 		help_text='Abstrakt konfery tak, jak byl uveden ve sborníku') | 		help_text='Abstrakt konfery tak, jak byl uveden ve sborníku') | ||||||
| 	 | 	 | ||||||
| 	# FIXME: Umíme omezit jen na účastníky daného soustřeďka? | 	# FIXME: Umíme omezit jen na účastníky daného soustřeďka? | ||||||
| 	ucastnici = models.ManyToManyField(Resitel, verbose_name='účastníci konfery', | 	ucastnici = models.ManyToManyField(pm.Resitel, verbose_name='účastníci konfery', | ||||||
| 		help_text='Seznam účastníků konfery', through='Konfery_Ucastnici') | 		help_text='Seznam účastníků konfery', through='Konfery_Ucastnici') | ||||||
| 
 | 
 | ||||||
| 	soustredeni = models.ForeignKey(Soustredeni, verbose_name='soustředění',  | 	soustredeni = models.ForeignKey(Soustredeni, verbose_name='soustředění',  | ||||||
|  | @ -1266,7 +826,7 @@ class Konfery_Ucastnici(models.Model): | ||||||
| 	# Interní ID | 	# Interní ID | ||||||
| 	id = models.AutoField(primary_key = True) | 	id = models.AutoField(primary_key = True) | ||||||
| 
 | 
 | ||||||
| 	resitel = models.ForeignKey(Resitel, verbose_name='řešitel', on_delete=models.PROTECT) | 	resitel = models.ForeignKey(pm.Resitel, verbose_name='řešitel', on_delete=models.PROTECT) | ||||||
| 
 | 
 | ||||||
| 	konfera = models.ForeignKey(Konfera, verbose_name='konfera', on_delete=models.CASCADE) | 	konfera = models.ForeignKey(Konfera, verbose_name='konfera', on_delete=models.CASCADE) | ||||||
| 
 | 
 | ||||||
|  | @ -1452,7 +1012,7 @@ class OrgTextNode(TreeNode): | ||||||
| 		verbose_name = 'Organizátorský článek (Node)' | 		verbose_name = 'Organizátorský článek (Node)' | ||||||
| 		verbose_name_plural = 'Organizátorské články (Node)' | 		verbose_name_plural = 'Organizátorské články (Node)' | ||||||
| 	 | 	 | ||||||
| 	organizator = models.ForeignKey(Organizator, | 	organizator = models.ForeignKey(pm.Organizator, | ||||||
| 		null=False, | 		null=False, | ||||||
| 		blank=False, | 		blank=False, | ||||||
| 		on_delete=models.DO_NOTHING, | 		on_delete=models.DO_NOTHING, | ||||||
|  | @ -1598,7 +1158,7 @@ class Novinky(models.Model): | ||||||
| 			], | 			], | ||||||
| 			options={'quality': 95}) | 			options={'quality': 95}) | ||||||
| 
 | 
 | ||||||
| 	autor = models.ForeignKey(Organizator, verbose_name='Autor novinky', null=True, | 	autor = models.ForeignKey(pm.Organizator, verbose_name='Autor novinky', null=True, | ||||||
| 		on_delete=models.SET_NULL) | 		on_delete=models.SET_NULL) | ||||||
| 
 | 
 | ||||||
| 	zverejneno = models.BooleanField('Zveřejněno', default=False) | 	zverejneno = models.BooleanField('Zveřejněno', default=False) | ||||||
|  |  | ||||||
|  | @ -10,6 +10,8 @@ from django.utils import timezone | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| 
 | 
 | ||||||
| from seminar.models import models_all as am | from seminar.models import models_all as am | ||||||
|  | from seminar.models import personalni as pm | ||||||
|  | from seminar.models.base import SeminarModelBase | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @reversion.register(ignore_duplicates=True) | @reversion.register(ignore_duplicates=True) | ||||||
|  | @ -29,7 +31,7 @@ class Reseni(am.SeminarModelBase): | ||||||
|     problem = models.ManyToManyField(am.Problem, verbose_name='problém', help_text='Problém', |     problem = models.ManyToManyField(am.Problem, verbose_name='problém', help_text='Problém', | ||||||
|                                      through='Hodnoceni') |                                      through='Hodnoceni') | ||||||
| 
 | 
 | ||||||
|     resitele = models.ManyToManyField(am.Resitel, verbose_name='autoři řešení', |     resitele = models.ManyToManyField(pm.Resitel, verbose_name='autoři řešení', | ||||||
|                                       help_text='Seznam autorů řešení', through='Reseni_Resitele') |                                       help_text='Seznam autorů řešení', through='Reseni_Resitele') | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -160,7 +162,7 @@ class Reseni_Resitele(models.Model): | ||||||
|     # Interní ID |     # Interní ID | ||||||
|     id = models.AutoField(primary_key = True) |     id = models.AutoField(primary_key = True) | ||||||
| 
 | 
 | ||||||
|     resitele = models.ForeignKey(am.Resitel, verbose_name='řešitel', on_delete=models.PROTECT) |     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) |     reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										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'] | ||||||
|  | @ -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 %} |  | ||||||
|  | @ -1,5 +1,4 @@ | ||||||
| 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 | from .utils import org_required | ||||||
| 
 | 
 | ||||||
|  | @ -107,23 +106,6 @@ urlpatterns = [ | ||||||
| 		org_required(views.soustredeniObalkyView), | 		org_required(views.soustredeniObalkyView), | ||||||
| 		name='seminar_soustredeni_obalky' | 		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/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'), |  | ||||||
| 
 | 
 | ||||||
| 	re_path(r'^temp/vue/.*$',views.VueTestView.as_view(),name='vue_test_view'), | 	re_path(r'^temp/vue/.*$',views.VueTestView.as_view(),name='vue_test_view'), | ||||||
| 	path('temp/image_upload/', views.NahrajObrazekKTreeNoduView.as_view()), | 	path('temp/image_upload/', views.NahrajObrazekKTreeNoduView.as_view()), | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | from django.forms import model_to_dict | ||||||
| from django.shortcuts import get_object_or_404, render, redirect | from django.shortcuts import get_object_or_404, render, redirect | ||||||
| from django.http import HttpResponse, JsonResponse | from django.http import HttpResponse, JsonResponse | ||||||
| from django.urls import reverse | from django.urls import reverse | ||||||
|  | @ -6,20 +7,16 @@ from django.views import generic | ||||||
| from django.utils.translation import ugettext as _ | from django.utils.translation import ugettext as _ | ||||||
| from django.http import Http404 | 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.debug import sensitive_post_parameters |  | ||||||
| from django.views.generic.edit import CreateView | from django.views.generic.edit import CreateView | ||||||
| from django.views.generic.base import TemplateView, RedirectView | from django.views.generic.base import RedirectView | ||||||
| from django.contrib.auth.models import User, Permission, Group |  | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
| from django.db import transaction |  | ||||||
| from django.core.exceptions import PermissionDenied | from django.core.exceptions import PermissionDenied | ||||||
| 
 | 
 | ||||||
| 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, Tema, Clanek, Osoba # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci | from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni, Organizator, Resitel, Novinky, Soustredeni_Ucastnici, 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, treelib | ||||||
| from seminar.forms import PrihlaskaForm, ProfileEditForm, PoMaturiteProfileEditForm |  | ||||||
| import seminar.forms as f | import seminar.forms as f | ||||||
| import seminar.templatetags.treenodes as tnltt | import seminar.templatetags.treenodes as tnltt | ||||||
| import seminar.views.views_rest as vr | import seminar.views.views_rest as vr | ||||||
|  | @ -41,9 +38,7 @@ import csv | ||||||
| import logging | import logging | ||||||
| import time | import time | ||||||
| 
 | 
 | ||||||
| from seminar.utils import aktivniResitele, 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): | ||||||
|  | @ -825,51 +820,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 | ||||||
| 
 | 
 | ||||||
|  | @ -1014,15 +964,6 @@ def StavDatabazeView(request): | ||||||
| 				'jmena_zen': utils.histogram([r.osoba.jmeno for r in zeny]), | 				'jmena_zen': utils.histogram([r.osoba.jmeno for r in zeny]), | ||||||
| 			}) | 			}) | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| 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 | ### Formulare | ||||||
| 
 | 
 | ||||||
| # pro přidání políčka do formuláře je potřeba | # pro přidání políčka do formuláře je potřeba | ||||||
|  | @ -1031,216 +972,6 @@ class ResitelView(LoginRequiredMixin,generic.DetailView): | ||||||
| # - přidat do forms | # - přidat do forms | ||||||
| # - includovat do html | # - 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)) |  | ||||||
| 
 |  | ||||||
| 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): | class VueTestView(generic.TemplateView): | ||||||
| 	template_name = 'seminar/vuetest.html' | 	template_name = 'seminar/vuetest.html' | ||||||
|  | @ -1268,17 +999,6 @@ class NahrajObrazekKTreeNoduView(LoginRequiredMixin, CreateView): | ||||||
| 		return JsonResponse({"url":self.object.na_web.url}) | 		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' | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue