285 lines
9.1 KiB
Python
285 lines
9.1 KiB
Python
import reversion
|
|
|
|
from django.db import models
|
|
|
|
from mamweb.models.base import SeminarModelBase
|
|
from .osoba import Osoba
|
|
from .skola import Skola
|
|
|
|
|
|
class _ResitelManager(models.Manager):
|
|
def resi_v_rocniku(self, rocnik, cislo=None):
|
|
""" Vrátí seznam řešitelů, co vyřešili nějaký problém v daném ročníku, do daného čísla.
|
|
Parametry:
|
|
rocnik (typu Rocnik) ročník, ze kterého chci řešitele, co něco odevzdali
|
|
cislo (typu Cislo) číslo, do kterého včetně se počítá, že v daném
|
|
ročníku řešitel něco poslal.
|
|
Pokud není zadané, počítají se všechna řešení z daného ročníku.
|
|
Výstup:
|
|
QuerySet objektů typu Resitel """
|
|
|
|
if cislo is None:
|
|
# filtrujeme pouze podle ročníku
|
|
return self.filter(
|
|
rok_maturity__gte=rocnik.druhy_rok(),
|
|
reseni__hodnoceni__deadline_body__cislo__rocnik=rocnik
|
|
).distinct()
|
|
else: # filtrujeme podle ročníku i čísla
|
|
return self.filter(
|
|
rok_maturity__gte=rocnik.druhy_rok(),
|
|
reseni__hodnoceni__deadline_body__cislo__rocnik=rocnik,
|
|
reseni__hodnoceni__deadline_body__cislo__poradi__lte=cislo.poradi
|
|
).distinct()
|
|
|
|
def aktivni_resitele(self, cislo, pouze_letosni=False):
|
|
""" Vrací QuerySet aktivních řešitelů, což jsou ti, co ještě neodmaturovali
|
|
a letos něco poslali (anebo loni něco poslali, pokud jde o první tři čísla).
|
|
Parametry:
|
|
cislo (typu Cislo) číslo, o které se jedná
|
|
pouze_letosni jen řešitelé, kteří tento rok něco poslali
|
|
|
|
"""
|
|
letos = cislo.rocnik
|
|
|
|
# detekujeme, zda jde o první tři čísla či nikoli (tj. zda spamovat řešitele z minulého roku)
|
|
zacatek_rocniku = True
|
|
try:
|
|
if int(cislo.poradi) > 3:
|
|
zacatek_rocniku = False
|
|
except ValueError:
|
|
# if cislo.poradi != '7-8':
|
|
# raise ValueError(f'{cislo} je neplatné číslo čísla (není int a není 7-8)')
|
|
zacatek_rocniku = False
|
|
|
|
# nehledě na číslo chceme jen řešitele, kteří letos něco odevzdali
|
|
if pouze_letosni:
|
|
zacatek_rocniku = False
|
|
|
|
from django.core.exceptions import ObjectDoesNotExist
|
|
try:
|
|
from tvorba.models.rocnik import Rocnik
|
|
loni = Rocnik.objects.get(rocnik=letos.rocnik - 1)
|
|
except ObjectDoesNotExist:
|
|
# Pro první ročník neexistuje ročník předchozí
|
|
zacatek_rocniku = False
|
|
|
|
if not zacatek_rocniku:
|
|
return self.resi_v_rocniku(letos, cislo)
|
|
else:
|
|
# spojíme querysety s řešiteli loni a letos do daného čísla
|
|
return (self.resi_v_rocniku(loni) | self.resi_v_rocniku(letos, cislo)).distinct()
|
|
|
|
|
|
@reversion.register(ignore_duplicates=True)
|
|
class Resitel(SeminarModelBase):
|
|
objects = _ResitelManager()
|
|
|
|
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)
|
|
|
|
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.models 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.models 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.models 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()
|