Vyrábíme personální v personálních + oprava sem/models.
Nezapomenout na závislost v migraci!
This commit is contained in:
		
							parent
							
								
									8cc5864257
								
							
						
					
					
						commit
						17b4a4764c
					
				
					 3 changed files with 584 additions and 6 deletions
				
			
		
							
								
								
									
										124
									
								
								personalni/migrations/0003_initial.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								personalni/migrations/0003_initial.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,124 @@ | ||||||
|  | # Generated by Django 4.2.8 on 2024-03-12 21:10 | ||||||
|  | 
 | ||||||
|  | from django.db import migrations, models | ||||||
|  | import django.utils.timezone | ||||||
|  | import django_countries.fields | ||||||
|  | import imagekit.models.fields | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     initial = True | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('personalni', '0002_auto_20240312_2118'), | ||||||
|  |         ('seminar', '0118_alter_organizator_options_alter_osoba_options_and_more'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Organizator', | ||||||
|  |             fields=[ | ||||||
|  |                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||||
|  |                 ('vytvoreno', models.DateTimeField(blank=True, default=django.utils.timezone.now, editable=False, verbose_name='Vytvořeno')), | ||||||
|  |                 ('organizuje_od', models.DateTimeField(blank=True, null=True, verbose_name='Organizuje od')), | ||||||
|  |                 ('organizuje_do', models.DateTimeField(blank=True, null=True, verbose_name='Organizuje do')), | ||||||
|  |                 ('studuje', models.CharField(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'", max_length=256, null=True, verbose_name='Studium aj.')), | ||||||
|  |                 ('strucny_popis_organizatora', models.TextField(blank=True, null=True, verbose_name='Stručný popis organizátora')), | ||||||
|  |                 ('skola', models.CharField(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ášť', max_length=256, null=True, verbose_name='Škola, kterou studuje')), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'verbose_name': 'Organizátor', | ||||||
|  |                 'verbose_name_plural': 'Organizátoři', | ||||||
|  |                 'ordering': ['-organizuje_do', 'osoba__jmeno', 'osoba__prijmeni'], | ||||||
|  |                 'managed': False, | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Osoba', | ||||||
|  |             fields=[ | ||||||
|  |                 ('id', models.AutoField(primary_key=True, serialize=False)), | ||||||
|  |                 ('jmeno', models.CharField(max_length=256, verbose_name='jméno')), | ||||||
|  |                 ('prijmeni', models.CharField(max_length=256, verbose_name='příjmení')), | ||||||
|  |                 ('prezdivka', models.CharField(blank=True, max_length=256, null=True, verbose_name='přezdívka')), | ||||||
|  |                 ('pohlavi_muz', models.BooleanField(default=False, verbose_name='pohlaví (muž)')), | ||||||
|  |                 ('email', models.EmailField(blank=True, default='', max_length=256, verbose_name='e-mail')), | ||||||
|  |                 ('telefon', models.CharField(blank=True, default='', max_length=256, verbose_name='telefon')), | ||||||
|  |                 ('datum_narozeni', models.DateField(blank=True, null=True, verbose_name='datum narození')), | ||||||
|  |                 ('datum_souhlasu_udaje', models.DateField(blank=True, help_text='Datum souhlasu se zpracováním osobních údajů', null=True, verbose_name='datum souhlasu (údaje)')), | ||||||
|  |                 ('datum_souhlasu_zasilani', models.DateField(blank=True, help_text='Datum souhlasu se zasíláním MFF materiálů', null=True, verbose_name='datum souhlasu (spam)')), | ||||||
|  |                 ('datum_registrace', models.DateField(default=django.utils.timezone.now, verbose_name='datum registrace do semináře')), | ||||||
|  |                 ('ulice', models.CharField(blank=True, default='', max_length=256, verbose_name='ulice')), | ||||||
|  |                 ('mesto', models.CharField(blank=True, default='', max_length=256, verbose_name='město')), | ||||||
|  |                 ('psc', models.CharField(blank=True, default='', max_length=32, verbose_name='PSČ')), | ||||||
|  |                 ('stat', django_countries.fields.CountryField(default='CZ', help_text='ISO 3166-1 kód země velkými písmeny (CZ, SK, ...)', max_length=2, verbose_name='stát')), | ||||||
|  |                 ('jak_se_dozvedeli', models.TextField(blank=True, verbose_name='Jak se dozvěděli')), | ||||||
|  |                 ('poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka k osobě (plain text)', verbose_name='neveřejná poznámka')), | ||||||
|  |                 ('foto', imagekit.models.fields.ProcessedImageField(blank=True, help_text='Vlož fotografii osoby o libovolné velikosti', null=True, upload_to='image_osoby/velke/%Y/', verbose_name='Fotografie osoby')), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'verbose_name': 'Osoba', | ||||||
|  |                 'verbose_name_plural': 'Osoby', | ||||||
|  |                 'db_table': 'seminar_osoby', | ||||||
|  |                 'ordering': ['prijmeni', 'jmeno'], | ||||||
|  |                 'managed': False, | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Prijemce', | ||||||
|  |             fields=[ | ||||||
|  |                 ('id', models.AutoField(primary_key=True, serialize=False)), | ||||||
|  |                 ('poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka k příemci čísel (plain text)', verbose_name='neveřejná poznámka')), | ||||||
|  |                 ('zasilat_cislo_emailem', models.BooleanField(default=False, help_text='True pokud chce příjemce dostávat číslo emailem', verbose_name='zasílat číslo emailem')), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'verbose_name': 'příjemce', | ||||||
|  |                 'verbose_name_plural': 'příjemce', | ||||||
|  |                 'db_table': 'seminar_prijemce', | ||||||
|  |                 'managed': False, | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Resitel', | ||||||
|  |             fields=[ | ||||||
|  |                 ('id', models.AutoField(primary_key=True, serialize=False)), | ||||||
|  |                 ('prezdivka_resitele', models.CharField(blank=True, max_length=256, null=True, unique=True, verbose_name='přezdívka řešitele')), | ||||||
|  |                 ('rok_maturity', models.IntegerField(blank=True, null=True, verbose_name='rok maturity')), | ||||||
|  |                 ('zasilat', models.CharField(choices=[('domu', 'Domů'), ('do_skoly', 'Do školy'), ('nikam', 'Nezasílat papírově')], default='domu', max_length=32, verbose_name='kam zasílat')), | ||||||
|  |                 ('zasilat_cislo_emailem', models.BooleanField(default=False, help_text='True pokud chce řešitel dostávat číslo emailem', verbose_name='zasílat číslo emailem')), | ||||||
|  |                 ('zasilat_cislo_papirove', models.BooleanField(default=True, help_text='True pokud chce řešitel dostávat číslo papírově', verbose_name='zasílat číslo papírově')), | ||||||
|  |                 ('poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka k řešiteli (plain text)', verbose_name='neveřejná poznámka')), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'verbose_name': 'Řešitel', | ||||||
|  |                 'verbose_name_plural': 'Řešitelé', | ||||||
|  |                 'db_table': 'seminar_resitele', | ||||||
|  |                 'ordering': ['osoba'], | ||||||
|  |                 'managed': False, | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Skola', | ||||||
|  |             fields=[ | ||||||
|  |                 ('id', models.AutoField(primary_key=True, serialize=False)), | ||||||
|  |                 ('aesop_id', models.CharField(blank=True, default='', help_text='Aesopi ID typu "izo:..." nebo "aesop:..."', max_length=32, verbose_name='Aesop ID')), | ||||||
|  |                 ('izo', models.CharField(blank=True, help_text='IZO školy (jen české školy)', max_length=32, verbose_name='IZO')), | ||||||
|  |                 ('nazev', models.CharField(help_text='Celý název školy', max_length=256, verbose_name='název')), | ||||||
|  |                 ('kratky_nazev', models.CharField(blank=True, help_text='Zkrácený název pro zobrazení ve výsledkovce', max_length=256, verbose_name='zkrácený název')), | ||||||
|  |                 ('ulice', models.CharField(max_length=256, verbose_name='ulice')), | ||||||
|  |                 ('mesto', models.CharField(max_length=256, verbose_name='město')), | ||||||
|  |                 ('psc', models.CharField(max_length=32, verbose_name='PSČ')), | ||||||
|  |                 ('stat', django_countries.fields.CountryField(default='CZ', help_text='ISO 3166-1 kód země velkými písmeny (CZ, SK, ...)', max_length=2, verbose_name='stát')), | ||||||
|  |                 ('je_zs', models.BooleanField(default=True, verbose_name='základní stupeň')), | ||||||
|  |                 ('je_ss', models.BooleanField(default=True, verbose_name='střední stupeň')), | ||||||
|  |                 ('poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka ke škole (plain text)', verbose_name='neveřejná poznámka')), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'verbose_name': 'Škola', | ||||||
|  |                 'verbose_name_plural': 'Školy', | ||||||
|  |                 'db_table': 'seminar_skoly', | ||||||
|  |                 'ordering': ['mesto', 'nazev'], | ||||||
|  |                 'managed': False, | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										453
									
								
								personalni/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										453
									
								
								personalni/models.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,453 @@ | ||||||
|  | # -*- 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 seminar.models.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'] | ||||||
|  | 		managed = False | ||||||
|  | 	 | ||||||
|  | 	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, ...)') | ||||||
|  | 
 | ||||||
|  | 	jak_se_dozvedeli = models.TextField('Jak se dozvěděli', blank=True) | ||||||
|  | 
 | ||||||
|  | 	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'] | ||||||
|  | 		managed = False | ||||||
|  | 
 | ||||||
|  | 	# 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' | ||||||
|  | 		managed = False | ||||||
|  | 	 | ||||||
|  | 
 | ||||||
|  | 	# 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) | ||||||
|  | 
 | ||||||
|  | 	zasilat_cislo_emailem = models.BooleanField('zasílat číslo emailem', help_text='True pokud chce příjemce dostávat číslo emailem', default=False) | ||||||
|  | 
 | ||||||
|  | 	# 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'] | ||||||
|  | 		managed = False | ||||||
|  | 
 | ||||||
|  | 	# Interní ID | ||||||
|  | 	id = models.AutoField(primary_key = True) | ||||||
|  | 
 | ||||||
|  | 	prezdivka_resitele = models.CharField('přezdívka řešitele', blank=True, null=True, max_length=256, unique=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, 'Nezasílat papírově'), | ||||||
|  | 		] | ||||||
|  | 
 | ||||||
|  | 	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) | ||||||
|  | 
 | ||||||
|  | 	zasilat_cislo_papirove = models.BooleanField('zasílat číslo papírově', help_text='True pokud chce řešitel dostávat číslo papírově', default=True) | ||||||
|  | 
 | ||||||
|  | 	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(deadline_body__cislo__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(deadline_body__cislo__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): | ||||||
|  | 
 | ||||||
|  | 	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'] | ||||||
|  | 		managed = False | ||||||
|  | 
 | ||||||
|  | 	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 | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	# Ne, date to nebude. SQLite: invalid literal for int() with base 10: b'17 23:00:00' | ||||||
|  | 	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) | ||||||
|  | @ -37,7 +37,8 @@ class Osoba(SeminarModelBase): | ||||||
| 
 | 
 | ||||||
| 	# User, pokud má na webu účet | 	# User, pokud má na webu účet | ||||||
| 	user = models.OneToOneField(settings.AUTH_USER_MODEL, blank=True, null=True,  | 	user = models.OneToOneField(settings.AUTH_USER_MODEL, blank=True, null=True,  | ||||||
| 				verbose_name='uživatel', on_delete=models.DO_NOTHING) | 				verbose_name='uživatel', on_delete=models.DO_NOTHING, | ||||||
|  | 				related_name='user_old') | ||||||
| 
 | 
 | ||||||
| 	# Pohlaví. Že ho neznáme se snad nestane (a ušetří to práci při programování) | 	# 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) | 	pohlavi_muz = models.BooleanField('pohlaví (muž)', default=False) | ||||||
|  | @ -172,7 +173,7 @@ class Skola(SeminarModelBase): | ||||||
| 		help_text='Neveřejná poznámka ke škole (plain text)') | 		help_text='Neveřejná poznámka ke škole (plain text)') | ||||||
| 	 | 	 | ||||||
| 	kontaktni_osoba = models.ForeignKey(Osoba, verbose_name='Kontaktní osoba',  | 	kontaktni_osoba = models.ForeignKey(Osoba, verbose_name='Kontaktní osoba',  | ||||||
| 			blank=True, null=True, on_delete=models.SET_NULL) | 			blank=True, null=True, on_delete=models.SET_NULL, related_name='kontaktni_osoba_old') | ||||||
| 
 | 
 | ||||||
| 	def __str__(self): | 	def __str__(self): | ||||||
| 		return '{}, {}, {}'.format(self.nazev, self.ulice, self.mesto) | 		return '{}, {}, {}'.format(self.nazev, self.ulice, self.mesto) | ||||||
|  | @ -193,7 +194,7 @@ class Prijemce(SeminarModelBase): | ||||||
| 
 | 
 | ||||||
| 	osoba = models.OneToOneField(Osoba, verbose_name='komu', blank=False, null=False, | 	osoba = models.OneToOneField(Osoba, verbose_name='komu', blank=False, null=False, | ||||||
| 		help_text='Které osobě či na jakou adresu se mají zasílat čísla', | 		help_text='Které osobě či na jakou adresu se mají zasílat čísla', | ||||||
| 		on_delete=models.CASCADE) | 		on_delete=models.CASCADE, related_name='osobad_old1') | ||||||
| 
 | 
 | ||||||
| 	zasilat_cislo_emailem = models.BooleanField('zasílat číslo emailem', help_text='True pokud chce příjemce dostávat číslo emailem', default=False) | 	zasilat_cislo_emailem = models.BooleanField('zasílat číslo emailem', help_text='True pokud chce příjemce dostávat číslo emailem', default=False) | ||||||
| 
 | 
 | ||||||
|  | @ -220,11 +221,11 @@ class Resitel(SeminarModelBase): | ||||||
| 	prezdivka_resitele = models.CharField('přezdívka řešitele', blank=True, null=True, max_length=256, unique=True) | 	prezdivka_resitele = models.CharField('přezdívka řešitele', blank=True, null=True, max_length=256, unique=True) | ||||||
| 
 | 
 | ||||||
| 	osoba = models.OneToOneField(Osoba, blank=False, null=False, verbose_name='osoba', | 	osoba = models.OneToOneField(Osoba, blank=False, null=False, verbose_name='osoba', | ||||||
| 		on_delete=models.PROTECT) | 		on_delete=models.PROTECT, related_name='osoba_old2') | ||||||
| 	 | 	 | ||||||
| 
 | 
 | ||||||
| 	skola = models.ForeignKey(Skola, blank=True, null=True, verbose_name='škola', | 	skola = models.ForeignKey(Skola, blank=True, null=True, verbose_name='škola', | ||||||
| 		on_delete=models.SET_NULL) | 		on_delete=models.SET_NULL, related_name='skola_old3') | ||||||
| 
 | 
 | ||||||
| 	# Očekávaný rok maturity a vyřazení z aktivních řešitelů | 	# Očekávaný rok maturity a vyřazení z aktivních řešitelů | ||||||
| 	rok_maturity = models.IntegerField('rok maturity', blank=True, null=True) | 	rok_maturity = models.IntegerField('rok maturity', blank=True, null=True) | ||||||
|  | @ -399,7 +400,7 @@ class Resitel(SeminarModelBase): | ||||||
| 
 | 
 | ||||||
| @reversion.register(ignore_duplicates=True) | @reversion.register(ignore_duplicates=True) | ||||||
| class Organizator(SeminarModelBase): | class Organizator(SeminarModelBase): | ||||||
| 	osoba = models.OneToOneField(Osoba, verbose_name='osoba', related_name='org_old', | 	osoba = models.OneToOneField(Osoba, verbose_name='osoba', related_name='org_old4', | ||||||
| 		help_text='osobní údaje organizátora', null=False, blank=False, | 		help_text='osobní údaje organizátora', null=False, blank=False, | ||||||
| 		on_delete=models.PROTECT) | 		on_delete=models.PROTECT) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Pavel 'LEdoian' Turinsky
						Pavel 'LEdoian' Turinsky