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 = 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í) | ||||
| 	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)') | ||||
| 	 | ||||
| 	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): | ||||
| 		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, | ||||
| 		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) | ||||
| 
 | ||||
|  | @ -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) | ||||
| 
 | ||||
| 	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', | ||||
| 		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ů | ||||
| 	rok_maturity = models.IntegerField('rok maturity', blank=True, null=True) | ||||
|  | @ -399,7 +400,7 @@ class Resitel(SeminarModelBase): | |||
| 
 | ||||
| @reversion.register(ignore_duplicates=True) | ||||
| 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, | ||||
| 		on_delete=models.PROTECT) | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Pavel 'LEdoian' Turinsky
						Pavel 'LEdoian' Turinsky