# -*- coding: utf-8 -*- import os import random import subprocess import pathlib import tempfile from django.db import models from django.contrib import auth from django.utils import timezone from django.conf import settings from django.utils.encoding import force_text from django.utils.text import slugify from django.urls import reverse from django.core.cache import cache from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.contrib.contenttypes.models import ContentType from django.utils.text import get_valid_filename from imagekit.models import ImageSpecField, ProcessedImageField from imagekit.processors import ResizeToFit, Transpose from django_countries.fields import CountryField from solo.models import SingletonModel from taggit.managers import TaggableManager from reversion import revisions as reversion from seminar.utils import roman, FirstTagParser # Pro získání úryvku z TextNode from unidecode import unidecode # Používám pro získání ID odkazu (ještě je to někde po někom zakomentované) from polymorphic.models import PolymorphicModel class SeminarModelBase(models.Model): class Meta: abstract = True def verejne(self): return False def get_absolute_url(self): return self.verejne_url() # TODO "absolute" 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=True, verbose_name='osoba', on_delete=models.SET_NULL) # FIXME opravit po prvni migraci 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) 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() vsechna_hodnoceni = Hodnoceni.objects.filter( reseni__in=vsechna_reseni) return sum(h.body for h in list(vsechna_hodnoceni)) 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 # Titul pro 26. ročník stary_titul = None if body is None: # 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. 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) stare_body = body_z_hodnoceni(hodnoceni_do_25_rocniku) nove_body = body_z_hodnoceni(novejsi_hodnoceni) logicke_body = 2*stare_body + nove_body hodnoceni_do_26_rocniku = Hodnoceni.objects.filter(cislo_body__rocnik__rocnik__lte=26,reseni__in=self.reseni_set.all()) 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 stary_titul = titul_do_26_rocniku(body_z_hodnoceni(hodnoceni_do_26_rocniku)) else: # Prostě titul podle aktuálních bodů logicke_body = body titul = Titul.z_bodu(logicke_body) if stary_titul is None: return str(titul) return str(max(titul, stary_titul)) def __str__(self): return self.osoba.plne_jmeno() @reversion.register(ignore_duplicates=True) class Rocnik(SeminarModelBase): class Meta: db_table = 'seminar_rocniky' verbose_name = 'Ročník' verbose_name_plural = 'Ročníky' ordering = ['-rocnik'] # Interní ID id = models.AutoField(primary_key = True) prvni_rok = models.IntegerField('první rok', db_index=True, unique=True) rocnik = models.IntegerField('číslo ročníku', db_index=True, unique=True) exportovat = models.BooleanField('export do AESOPa', db_column='exportovat', default=False, help_text='Exportuje se jen podle tohoto flagu (ne veřejnosti),' ' a to jen čísla s veřejnou výsledkovkou') # má OneToOneField s: # RocnikNode def __str__(self): return '{} ({}/{})'.format(self.rocnik, self.prvni_rok, self.prvni_rok+1) # Ročník v římských číslech def roman(self): return roman(int(self.rocnik)) def verejne(self): return len(self.verejna_cisla()) > 0 verejne.boolean = True verejne.short_description = 'Veřejný (jen dle čísel)' def verejna_cisla(self): vc = [c for c in self.cisla.all() if c.verejne()] vc.sort(key=lambda c: c.poradi) return vc def posledni_verejne_cislo(self): vc = self.verejna_cisla() return vc[-1] if vc else None def verejne_vysledkovky_cisla(self): vc = list(self.cisla.filter(verejna_vysledkovka=True)) vc.sort(key=lambda c: c.poradi) return vc def posledni_zverejnena_vysledkovka_cislo(self): vc = self.verejne_vysledkovky_cisla() return vc[-1] if vc else None def druhy_rok(self): return self.prvni_rok + 1 def verejne_url(self): return reverse('seminar_rocnik', kwargs={'rocnik': self.rocnik}) @classmethod def cached_rocnik(cls, r_id): name = 'rocnik_%s' % (r_id, ) c = cache.get(name) if c is None: c = cls.objects.get(id=r_id) cache.set(name, c, 300) return c def save(self, *args, **kwargs): super().save(*args, **kwargs) # *Node.save() aktualizuje název *Nodu. try: self.rocniknode.save() except ObjectDoesNotExist: # Neexistující *Node nemá smysl aktualizovat. pass def cislo_pdf_filename(self, filename): rocnik = str(self.rocnik.rocnik) return pathlib.Path('cislo', 'pdf', rocnik, '{}-{}.pdf'.format(rocnik, self.poradi)) def cislo_png_filename(self, filename): rocnik = str(self.rocnik.rocnik) return pathlib.Path('cislo', 'png', rocnik, '{}-{}.png'.format(rocnik, self.poradi)) @reversion.register(ignore_duplicates=True) class Cislo(SeminarModelBase): class Meta: db_table = 'seminar_cisla' verbose_name = 'Číslo' verbose_name_plural = 'Čísla' ordering = ['-rocnik__rocnik', '-poradi'] # Interní ID id = models.AutoField(primary_key = True) rocnik = models.ForeignKey(Rocnik, verbose_name='ročník', related_name='cisla', db_index=True,on_delete=models.PROTECT) poradi = models.CharField('název čísla', max_length=32, db_index=True, help_text='Většinou jen "1", vyjímečně "7-8", lexikograficky určuje pořadí v ročníku!') datum_vydani = models.DateField('datum vydání', blank=True, null=True, help_text='Datum vydání finální verze') datum_deadline = models.DateField('datum deadline', blank=True, null=True, help_text='Datum pro příjem řešení úloh zadaných v tomto čísle') datum_preddeadline = models.DateField('datum předdeadline', blank=True, null=True, help_text='Datum pro příjem řešení, která se otisknou v dalším čísle') datum_deadline_soustredeni = models.DateField( 'datum deadline soustředění', blank=True, null=True, help_text='Datum pro příjem řešení pro účast na soustředění') verejne_db = models.BooleanField('číslo zveřejněno', db_column='verejne', default=False) verejna_vysledkovka = models.BooleanField( 'zveřejněna výsledkovka', default=False, help_text='Je-li false u veřejného čísla, ' 'není výsledkovka zatím veřejná.') poznamka = models.TextField('neveřejná poznámka', blank=True, help_text='Neveřejná poznámka k číslu (plain text)') pdf = models.FileField('pdf', upload_to=cislo_pdf_filename, null=True, blank=True, help_text='PDF čísla, které si mohou řešitelé stáhnout') titulka_nahled = models.ImageField('Obrázek titulní strany', upload_to=cislo_png_filename, null=True, blank=True, help_text='Obrázek titulní strany, generuje se automaticky') # má OneToOneField s: # CisloNode def kod(self): return '%s.%s' % (self.rocnik.rocnik, self.poradi) kod.short_description = 'Kód čísla' def __str__(self): # Potenciální DB HOG, pokud by se ročník necachoval r = Rocnik.cached_rocnik(self.rocnik_id) return '{}.{}'.format(r.rocnik, self.poradi) def verejne(self): return self.verejne_db verejne.boolean = True def verejne_url(self): return reverse('seminar_cislo', kwargs={'rocnik': self.rocnik.rocnik, 'cislo': self.poradi}) def nasledujici(self): "Vrací None, pokud je toto poslední" return self.relativni_v_rocniku(1) def predchozi(self): "Vrací None, pokud je toto první" return self.relativni_v_rocniku(-1) def relativni_v_rocniku(self, rel_index): "Číslo o `index` dále v ročníku. None pokud neexistuje." cs = self.rocnik.cisla.order_by('cislo').all() i = list(cs).index(self) + rel_index if (i < 0) or (i >= len(cs)): return None return cs[i] def vygeneruj_nahled(self): VYSKA = 594 sirka = int(VYSKA*210/297) if not self.pdf: return # Pokud obrázek neexistuje nebo není aktuální, vytvoř jej if not self.titulka_nahled or os.path.getmtime(self.titulka_nahled.path) < os.path.getmtime(self.pdf.path): png_filename = pathlib.Path(tempfile.mkdtemp(), 'nahled.png') subprocess.call([ "convert", "-density", "300x300", "-geometry", "{}x{}".format(VYSKA, sirka), "-background", "white", "-flatten", "{}[0]".format(self.pdf.path), # titulní strana png_filename ]) with open(png_filename,'rb') as f: self.titulka_nahled.save('',f,True) png_filename.unlink() png_filename.parent.rmdir() @classmethod def get(cls, rocnik, cislo): try: r = Rocnik.objects.get(rocnik=rocnik) c = r.cisla.get(poradi=cislo) except ObjectDoesNotExist: return None return c def save(self, *args, **kwargs): super().save(*args, **kwargs) self.vygeneruj_nahled() # *Node.save() aktualizuje název *Nodu. try: self.cislonode.save() except ObjectDoesNotExist: # Neexistující *Node nemá smysl aktualizovat. pass @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=True, blank=False, on_delete=models.SET_NULL) #FIXME opravit po migraci 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) class Soustredeni(SeminarModelBase): class Meta: db_table = 'seminar_soustredeni' verbose_name = 'Soustředění' verbose_name_plural = 'Soustředění' ordering = ['-rocnik__rocnik', '-datum_zacatku'] # Interní ID id = models.AutoField(primary_key = True) rocnik = models.ForeignKey(Rocnik, verbose_name='ročník', related_name='soustredeni', on_delete=models.PROTECT) datum_zacatku = models.DateField('datum začátku', blank=True, null=True, help_text='První den soustředění') datum_konce = models.DateField('datum konce', blank=True, null=True, help_text='Poslední den soustředění') verejne_db = models.BooleanField('soustředění zveřejněno', db_column='verejne', default=False) misto = models.CharField('místo soustředění', max_length=256, blank=True, default='', help_text='Místo (název obce, volitelně též objektu') ucastnici = models.ManyToManyField(Resitel, verbose_name='účastníci soustředění', help_text='Seznam účastníků soustředění', through='Soustredeni_Ucastnici') organizatori = models.ManyToManyField(Organizator, verbose_name='Organizátoři soustředění', help_text='Seznam organizátorů soustředění', through='Soustredeni_Organizatori') text = models.TextField('text k soustředění (HTML)', blank=True, default='') TYP_JARNI = 'jarni' TYP_PODZIMNI = 'podzimni' TYP_VIKEND = 'vikend' TYP_CHOICES = [ (TYP_JARNI, 'Jarní soustředění'), (TYP_PODZIMNI, 'Podzimní soustředění'), (TYP_VIKEND, 'Víkendový sraz'), ] typ = models.CharField('typ akce', max_length=16, choices=TYP_CHOICES, blank=False, default=TYP_PODZIMNI) exportovat = models.BooleanField('export do AESOPa', db_column='exportovat', default=False, help_text='Exportuje se jen podle tohoto flagu (ne veřejnosti)') def __str__(self): return '{} ({})'.format(self.misto, self.datum_zacatku) def verejne(self): return self.verejne_db verejne.boolean = True def verejne_url(self): #return reverse('seminar_soustredeni', kwargs={'pk': self.id}) return reverse('seminar_seznam_soustredeni') @reversion.register(ignore_duplicates=True) # Pozor na následující řádek. *Nekrmit, asi kouše!* class Problem(SeminarModelBase,PolymorphicModel): class Meta: # Není abstraktní, protože se na něj jinak nedají dělat ForeignKeys. # TODO: Udělat to polymorfní (pomocí django-polymorphic), abychom dostali # po těch vazbách přímo tu úlohu/témátko vč. fieldů, které nejsou součástí # modelu Problem? #abstract = True db_table = 'seminar_problemy' verbose_name = 'Problém' verbose_name_plural = 'Problémy' ordering = ['nazev'] # Interní ID id = models.AutoField(primary_key = True) # Název nazev = models.CharField('název', max_length=256) # Zveřejnitelný na stránky # Problém má podproblémy nadproblem = models.ForeignKey('self', verbose_name='nadřazený problém', related_name='podproblem', null=True, blank=True, on_delete=models.SET_NULL) STAV_NAVRH = 'navrh' STAV_ZADANY = 'zadany' STAV_VYRESENY = 'vyreseny' STAV_SMAZANY = 'smazany' STAV_CHOICES = [ (STAV_NAVRH, 'Návrh'), (STAV_ZADANY, 'Zadaný'), (STAV_VYRESENY, 'Vyřešený'), (STAV_SMAZANY, 'Smazaný'), ] stav = models.CharField('stav problému', max_length=32, choices=STAV_CHOICES, blank=False, default=STAV_NAVRH) # Téma je taky Problém, takže má stavy, "zadané" témátko je aktuálně otevřené a dá se k němu něco poslat (řešení nebo článek) zamereni = TaggableManager(verbose_name='zaměření', help_text='Zaměření M/F/I/O problému, příp. další tagy', blank=True) poznamka = models.TextField('org poznámky (HTML)', blank=True, help_text='Neveřejný návrh úlohy, návrh řešení, text zadání, poznámky ...') autor = models.ForeignKey(Organizator, verbose_name='autor problému', related_name='autor_problemu_%(class)s', null=True, blank=True, on_delete=models.SET_NULL) garant = models.ForeignKey(Organizator, verbose_name='garant zadaného problému', related_name='garant_problemu_%(class)s', null=True, blank=True, on_delete=models.SET_NULL) opravovatele = models.ManyToManyField(Organizator, verbose_name='opravovatelé', blank=True, related_name='opravovatele_%(class)s') kod = models.CharField('lokální kód', max_length=32, blank=True, default='', help_text='Číslo/kód úlohy v čísle nebo kód tématu/článku/seriálu v ročníku') vytvoreno = models.DateTimeField('vytvořeno', default=timezone.now, blank=True, editable=False) def __str__(self): return self.nazev # Implicitini implementace, jednotlivé dědící třídy si přepíšou def kod_v_rocniku(self): if self.stav == 'zadany': if self.nadproblem: return self.nadproblem.kod_v_rocniku()+".{}".format(self.kod) return str(self.kod) return '' def verejne(self): # aktuálně podle stavu problému # FIXME pro některé problémy možná chceme override stav_verejny = False if self.stav == 'zadany' or self.stav == 'vyreseny': stav_verejny = True cislo_verejne = False if (self.cislo_zadani and self.cislo_zadani.verejne()): cislo_verejne = True return (stav_verejny and cislo_verejne) verejne.boolean = True def verejne_url(self): return reverse('seminar_problem', kwargs={'pk': self.id}) def admin_url(self): return reverse('admin:seminar_problem_change', args=(self.id, )) # FIXME - k úloze def body_v_zavorce(self): """Vrať string s body v závorce jsou-li u problému vyplněné, jinak '' Je-li desetinná část nulová, nezobrazuj ji. """ pocet_bodu = None if self.body: b = self.body pocet_bodu = int(b) if int(b) == b else b return "({}\u2009b)".format(pocet_bodu) if self.body else "" class Tema(Problem): class Meta: db_table = 'seminar_temata' verbose_name = 'Téma' verbose_name_plural = 'Témata' TEMA_TEMA = 'tema' TEMA_SERIAL = 'serial' TEMA_CHOICES = [ (TEMA_TEMA, 'Téma'), (TEMA_SERIAL, 'Seriál'), ] tema_typ = models.CharField('Typ tématu', max_length=16, choices=TEMA_CHOICES, blank=False, default=TEMA_TEMA) rocnik = models.ForeignKey(Rocnik, verbose_name='ročník',blank=True, null=True, on_delete=models.PROTECT) abstrakt = models.TextField('Abstrakt na rozcestník', blank=True) obrazek = models.ImageField('Obrázek na rozcestník', null=True) def kod_v_rocniku(self): if self.stav == 'zadany': if self.nadproblem: return self.nadproblem.kod_v_rocniku()+".t{}".format(self.kod) return "t{}".format(self.kod) return '' def save(self, *args, **kwargs): super().save(*args, **kwargs) # *Node.save() aktualizuje název *Nodu. for tvcn in self.temavcislenode_set.all(): tvcn.save() class Clanek(Problem): class Meta: db_table = 'seminar_clanky' verbose_name = 'Článek' verbose_name_plural = 'Články' cislo = models.ForeignKey(Cislo, blank=True, null=True, on_delete=models.PROTECT, verbose_name='číslo vydání', related_name='vydane_clanky') def kod_v_rocniku(self): if self.stav == 'zadany': # Nemělo by být potřeba # if self.nadproblem: # return self.nadproblem.kod_v_rocniku()+".c{}".format(self.kod) return "c{}".format(self.kod) return '' class Text(SeminarModelBase): class Meta: db_table = 'seminar_texty' verbose_name = 'text' verbose_name_plural = 'texty' na_web = models.TextField('text na web', blank=True, help_text='Text ke zveřejnění na webu') do_cisla = models.TextField('text do čísla', blank=True, help_text='Text ke zveřejnění v čísle') # má OneToOneField s: # Reseni (je u něj jako reseni_cele) # obrázky mají návaznost opačným směrem (vazba z druhé strany) def save(self, *args, **kwargs): super().save(*args, **kwargs) # *Node.save() aktualizuje název *Nodu. for tn in self.textnode_set.all(): tn.save() def __str__(self): return str(self.na_web)[:20] class Uloha(Problem): class Meta: db_table = 'seminar_ulohy' verbose_name = 'Úloha' verbose_name_plural = 'Úlohy' cislo_zadani = models.ForeignKey(Cislo, verbose_name='číslo zadání', blank=True, null=True, related_name='zadane_ulohy', on_delete=models.PROTECT) cislo_deadline = models.ForeignKey(Cislo, verbose_name='číslo deadlinu', blank=True, null=True, related_name='deadlinove_ulohy', on_delete=models.PROTECT) cislo_reseni = models.ForeignKey(Cislo, verbose_name='číslo řešení', blank=True, null=True, related_name='resene_ulohy', help_text='Číslo s řešením úlohy, jen pro úlohy', on_delete=models.PROTECT) max_body = models.DecimalField(max_digits=8, decimal_places=1, verbose_name='maximum bodů', blank=True, null=True) # má OneToOneField s: # UlohaZadaniNode # UlohaVzorakNode def kod_v_rocniku(self): if self.stav == 'zadany': name="{}.u{}".format(self.cislo_zadani.poradi,self.kod) if self.nadproblem: return self.nadproblem.kod_v_rocniku()+name return name return '' def save(self, *args, **kwargs): super().save(*args, **kwargs) # *Node.save() aktualizuje název *Nodu. try: self.ulohazadaninode.save() except ObjectDoesNotExist: # Neexistující *Node nemá smysl aktualizovat. pass try: self.ulohavzoraknode.save() except ObjectDoesNotExist: # Neexistující *Node nemá smysl aktualizovat. pass @reversion.register(ignore_duplicates=True) class Reseni(SeminarModelBase): class Meta: db_table = 'seminar_reseni' verbose_name = 'Řešení' verbose_name_plural = 'Řešení' #ordering = ['-problem', 'resitele'] # FIXME: Takhle to chceme, ale nefunguje to. ordering = ['-cas_doruceni'] # Interní ID id = models.AutoField(primary_key = True) # Ke každé dvojici řešní a problém existuje nanejvýš jedno hodnocení, doplnění vazby. problem = models.ManyToManyField(Problem, verbose_name='problém', help_text='Problém', through='Hodnoceni') resitele = models.ManyToManyField(Resitel, verbose_name='autoři řešení', help_text='Seznam autorů řešení', through='Reseni_Resitele') cas_doruceni = models.DateTimeField('čas_doručení', default=timezone.now, blank=True) FORMA_PAPIR = 'papir' FORMA_EMAIL = 'email' FORMA_UPLOAD = 'upload' FORMA_CHOICES = [ (FORMA_PAPIR, 'Papírové řešení'), (FORMA_EMAIL, 'Emailem'), (FORMA_UPLOAD, 'Upload přes web'), ] forma = models.CharField('forma řešení', max_length=16, choices=FORMA_CHOICES, blank=False, default=FORMA_EMAIL) text_cely = models.OneToOneField('ReseniNode', verbose_name='Plná verze textu řešení', blank=True, null=True, related_name="reseni_cely_set", on_delete=models.PROTECT) poznamka = models.TextField('neveřejná poznámka', blank=True, help_text='Neveřejná poznámka k řešení (plain text)') zverejneno = models.BooleanField('řešení zveřejněno', default=False, help_text='Udává, zda je řešení zveřejněno') # má OneToOneField s: # Konfera # má ForeignKey s: # Hodnoceni def __str__(self): return "{}({}): {}({})".format(self.resitele.first(),len(self.resitele.all()), self.problem.first() ,len(self.problem.all())) # NOTE: Potenciální DB HOG (bez select_related) ## Pravdepodobne uz nebude potreba: # def save(self, *args, **kwargs): # if ((self.cislo_body is None) and (self.problem.cislo_reseni) and # (self.problem.typ == Problem.TYP_ULOHA)): # self.cislo_body = self.problem.cislo_reseni # super(Reseni, self).save(*args, **kwargs) class Hodnoceni(SeminarModelBase): class Meta: db_table = 'seminar_hodnoceni' verbose_name = 'Hodnocení' verbose_name_plural = 'Hodnocení' # Interní ID id = models.AutoField(primary_key = True) body = models.DecimalField(max_digits=8, decimal_places=1, verbose_name='body', blank=False, null=True) cislo_body = models.ForeignKey(Cislo, verbose_name='číslo pro body', related_name='hodnoceni', blank=False, null=True, on_delete=models.PROTECT) reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE) problem = models.ForeignKey(Problem, verbose_name='problém', related_name='hodnoceni', on_delete=models.PROTECT) def __str__(self): return "{}, {}, {}".format(self.problem, self.reseni, self.body) def aux_generate_filename(self, filename): """Pomocná funkce generující ošetřený název souboru v adresáři s datem""" clean = get_valid_filename( unidecode(filename.replace('/', '-').replace('\0', '')) ) datedir = timezone.now().strftime('%Y-%m') fname = "{}_{}".format( timezone.now().strftime('%Y-%m-%d-%H:%M'), clean) return os.path.join(datedir, fname) # Django neumí jednoduše serializovat partial nebo třídu s __call__ # (https://docs.djangoproject.com/en/1.8/topics/migrations/), # neprojdou pak migrace. Takže rozlišení funkcí generujících názvy souboru # podle adresáře řešíme takto. ## def generate_filename_konfera(self, filename): return os.path.join( settings.SEMINAR_KONFERY_DIR, aux_generate_filename(self, filename) ) ## def generate_filename(self, filename): return os.path.join( settings.SEMINAR_RESENI_DIR, aux_generate_filename(self, filename) ) @reversion.register(ignore_duplicates=True) class PrilohaReseni(SeminarModelBase): class Meta: db_table = 'seminar_priloha_reseni' verbose_name = 'Příloha řešení' verbose_name_plural = 'Přílohy řešení' ordering = ['reseni', 'vytvoreno'] # Interní ID id = models.AutoField(primary_key = True) reseni = models.ForeignKey(Reseni, verbose_name='řešení', related_name='prilohy', on_delete=models.CASCADE) vytvoreno = models.DateTimeField('vytvořeno', default=timezone.now, blank=True, editable=False) soubor = models.FileField('soubor', upload_to = generate_filename) poznamka = models.TextField('neveřejná poznámka', blank=True, help_text='Neveřejná poznámka k příloze řešení (plain text), např. o původu') res_poznamka = models.TextField('poznámka řešitele', blank=True, help_text='Poznámka k příloze řešení, např. co daný soubor obsahuje') def __str__(self): return str(self.soubor) class Pohadka(SeminarModelBase): """Kus pohádky před/za úlohou v čísle""" class Meta: db_table = 'seminar_pohadky' verbose_name = 'Pohádka' verbose_name_plural = 'Pohádky' ordering = ['vytvoreno'] # Interní ID id = models.AutoField(primary_key=True) autor = models.ForeignKey( Organizator, verbose_name="Autor pohádky", # Při nahrávání z TeXu není vyplnění vyžadováno, v adminu je null=True, blank=False, on_delete=models.SET_NULL ) vytvoreno = models.DateTimeField( 'Vytvořeno', default=timezone.now, blank=True, editable=False ) # má OneToOneField s: # PohadkaNode def __str__(self): uryvek = self.text if len(self.text) < 50 else self.text[:(50-3)]+"..." return uryvek def save(self, *args, **kwargs): super().save(*args, **kwargs) # *Node.save() aktualizuje název *Nodu. try: self.pohadkanode.save() except ObjectDoesNotExist: # Neexistující *Node nemá smysl aktualizovat. pass @reversion.register(ignore_duplicates=True) class Soustredeni_Ucastnici(SeminarModelBase): # zmena dedicnosti z models.Model na SeminarModelBase, potencialni vznik bugu class Meta: db_table = 'seminar_soustredeni_ucastnici' verbose_name = 'Účast na soustředění' verbose_name_plural = 'Účasti na soustředění' ordering = ['soustredeni', 'resitel'] # Interní ID id = models.AutoField(primary_key = True) resitel = models.ForeignKey(Resitel, verbose_name='řešitel', on_delete=models.PROTECT) soustredeni = models.ForeignKey(Soustredeni, verbose_name='soustředění', on_delete=models.PROTECT) poznamka = models.TextField('neveřejná poznámka', blank=True, help_text='Neveřejná poznámka k účasti (plain text)') def __str__(self): return '{} na {}'.format(self.resitel, self.soustredeni) # NOTE: Poteciální DB HOG bez select_related @reversion.register(ignore_duplicates=True) class Soustredeni_Organizatori(SeminarModelBase): # zmena dedicnosti z models.Model na SeminarModelBase, potencialni vznik bugu class Meta: db_table = 'seminar_soustredeni_organizatori' verbose_name = 'Účast organizátorů na soustředění' verbose_name_plural = 'Účasti organizátorů na soustředění' ordering = ['soustredeni', 'organizator'] # Interní ID id = models.AutoField(primary_key = True) organizator = models.ForeignKey(Organizator, verbose_name='organizátor', on_delete=models.PROTECT) soustredeni = models.ForeignKey(Soustredeni, verbose_name='soustředění', on_delete=models.PROTECT) poznamka = models.TextField('neveřejná poznámka', blank=True, help_text='Neveřejná poznámka k účasti organizátora (plain text)') def __str__(self): return '{} na {}'.format(self.organizator, self.soustredeni) # NOTE: Poteciální DB HOG bez select_related @reversion.register(ignore_duplicates=True) class Konfera(Problem): class Meta: db_table = 'seminar_konfera' verbose_name = 'Konfera' verbose_name_plural = 'Konfery' anotace = models.TextField('anotace', blank=True, help_text='Popis, o čem bude konfera.') abstrakt = models.TextField('abstrakt', blank=True, help_text='Abstrakt konfery tak, jak byl uveden ve sborníku') # FIXME: Umíme omezit jen na účastníky daného soustřeďka? ucastnici = models.ManyToManyField(Resitel, verbose_name='účastníci konfery', help_text='Seznam účastníků konfery', through='Konfery_Ucastnici') soustredeni = models.ForeignKey(Soustredeni, verbose_name='soustředění', related_name='konfery', on_delete = models.SET_NULL, null=True) TYP_VELETRH = 'veletrh' TYP_PREZENTACE = 'prezentace' TYP_CHOICES = [ (TYP_VELETRH, 'Veletrh (postery)'), (TYP_PREZENTACE, 'Prezentace (přednáška)'), ] typ_prezentace = models.CharField('typ prezentace', max_length=16, choices=TYP_CHOICES, blank=False, default=TYP_VELETRH) prezentace = models.FileField('prezentace',help_text = 'Prezentace nebo fotka posteru', upload_to = generate_filename_konfera, blank=True) materialy = models.FileField('materialy', help_text = 'Další materiály ke konfeře zabalené do jednoho souboru', upload_to = generate_filename_konfera, blank=True) def __str__(self): return "{}: ({})".format(self.nazev, self.soustredeni) # Vazebna tabulka. Mozna se generuje automaticky. @reversion.register(ignore_duplicates=True) class Reseni_Resitele(models.Model): class Meta: db_table = 'seminar_reseni_resitele' verbose_name = 'Řešení řešitelů' verbose_name_plural = 'Řešení řešitelů' ordering = ['reseni', 'resitele'] # Interní ID id = models.AutoField(primary_key = True) resitele = models.ForeignKey(Resitel, verbose_name='řešitel', on_delete=models.PROTECT) reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE) # podil - jakou merou se ktery resitel podilel na danem reseni # - pouziti v budoucnu, pokud by resitele nemeli dostat vsichni stejne bodu za spolecne reseni def __str__(self): return '{} od {}'.format(self.reseni, self.resitel) # NOTE: Poteciální DB HOG bez select_related @reversion.register(ignore_duplicates=True) class Konfery_Ucastnici(models.Model): class Meta: db_table = 'seminar_konfery_ucastnici' verbose_name = 'Účast na konfeře' verbose_name_plural = 'Účasti na konfeře' ordering = ['konfera', 'resitel'] # Interní ID id = models.AutoField(primary_key = True) resitel = models.ForeignKey(Resitel, verbose_name='řešitel', on_delete=models.PROTECT) konfera = models.ForeignKey(Konfera, verbose_name='konfera', on_delete=models.CASCADE) poznamka = models.TextField('neveřejná poznámka', blank=True, help_text='Neveřejná poznámka k účasti (plain text)') def __str__(self): return '{} na {}'.format(self.resitel, self.konfera) # NOTE: Poteciální DB HOG bez select_related class Obrazek(SeminarModelBase): class Meta: db_table = 'seminar_obrazky' verbose_name = 'obrázek' verbose_name_plural = 'obrázky' # Interní ID id = models.AutoField(primary_key = True) na_web = models.ImageField('obrázek na web', upload_to='obrazky/%Y/%m/%d/', null=True, blank=True) text = models.ForeignKey(Text, verbose_name='text', help_text='text, ve kterém se obrázek vyskytuje', null=False, blank=False, on_delete=models.CASCADE) do_cisla_barevny = models.FileField('barevný obrázek do čísla', help_text = 'Barevná verze obrázku do čísla', upload_to = 'obrazky/%Y/%m/%d/', blank=True, null=True) do_cisla_cernobily = models.FileField('černobílý obrázek do čísla', help_text = 'Černobílá verze obrázku do čísla', upload_to = 'obrazky/%Y/%m/%d/', blank=True, null=True) # TODO placement hint - chci ho tady / pred textem / za textem class TreeNode(PolymorphicModel): class Meta: db_table = "seminar_nodes_treenode" verbose_name = "TreeNode" verbose_name_plural = "TreeNody" # TODO: Nechceme radši jako root vyžadovat přímo RocnikNode? root = models.ForeignKey('TreeNode', related_name="potomci_set", null = True, blank = False, on_delete = models.SET_NULL, # Vrcholy s null kořenem jsou sirotci bez ročníku verbose_name="kořen stromu") first_child = models.OneToOneField('TreeNode', related_name='father_of_first', null = True, blank = True, on_delete=models.SET_NULL, verbose_name="první potomek") succ = models.OneToOneField('TreeNode', related_name="prev", null = True, blank = True, on_delete=models.SET_NULL, verbose_name="další element na stejné úrovni") nazev = models.TextField("název tohoto node", help_text = "Tento název se zobrazuje v nabídkách pro výběr vhodného TreeNode", blank=False, null=True) # Nezveřejnitelný název na stránky - pouze do adminu zajimave = models.BooleanField(default = False, verbose_name = "Zajímavé", help_text = "Zobrazí se daná věc na rozcestníku témátek") srolovatelne = models.BooleanField(null = True, blank = True, verbose_name = "Srolovatelné", help_text = "Bude na stránce témátka možnost tuto položku skrýt") def getOdkazStr(self): # String na rozcestník return self.first_child.getOdkazStr() def getOdkaz(self): # ID HTML tagu, na který se bude scrollovat #{{self.getOdkaz}} # Jsem si vědom, že tu potenciálně vznikají kolize. # Přijdou mi natolik nepravděpodobné, že je neřeším # Chtěl jsem ale hezké odkazy string = unidecode(self.getOdkazStr()) returnVal = "" i = 0 while len(returnVal) < 16: # Max 15 znaků if i == len(string): break if string[i] == " ": returnVal += "-" if string[i].isalnum(): returnVal += string[i].lower() i += 1 return returnVal def __str__(self): if self.nazev: return self.nazev else: #TODO: logování return "Nepojmenovaný Treenode" def save(self, *args, **kwargs): self.aktualizuj_nazev() super().save(*args, **kwargs) def aktualizuj_nazev(self): raise NotImplementedError("Pokus o aktualizaci názvu obecného TreeNode místo konkrétní instance") def get_admin_url(self): content_type = ContentType.objects.get_for_model(self.__class__) return reverse("admin:%s_%s_change" % (content_type.app_label, content_type.model), args=(self.id,)) class RocnikNode(TreeNode): class Meta: db_table = 'seminar_nodes_rocnik' verbose_name = 'Ročník (Node)' verbose_name_plural = 'Ročníky (Node)' rocnik = models.OneToOneField(Rocnik, on_delete = models.PROTECT, # Pokud chci mazat ročník, musím si Node pořešit ručně verbose_name = "ročník") def aktualizuj_nazev(self): self.nazev = "RocnikNode: "+str(self.rocnik) class CisloNode(TreeNode): class Meta: db_table = 'seminar_nodes_cislo' verbose_name = 'Číslo (Node)' verbose_name_plural = 'Čísla (Node)' cislo = models.OneToOneField(Cislo, on_delete = models.PROTECT, # Pokud chci mazat číslo, musím si Node pořešit ručně verbose_name = "číslo") def aktualizuj_nazev(self): self.nazev = "CisloNode: "+str(self.cislo) def getOdkazStr(self): return "Číslo " + str(self.cislo) class MezicisloNode(TreeNode): class Meta: db_table = 'seminar_nodes_mezicislo' verbose_name = 'Mezičíslo (Node)' verbose_name_plural = 'Mezičísla (Node)' # TODO: Využít TreeLib def aktualizuj_nazev(self): from seminar.treelib import safe_pred if safe_pred(self) is not None: if (self.prev.get_real_instance_class() != CisloNode and self.prev.get_real_instance_class() != MezicisloNode): raise ValueError("Předchůdce není číslo!") posledni = self.prev.cislo self.nazev = "MezicisloNode: Mezičíslo po čísle"+str(posledni) elif self.root: if self.root.get_real_instance_class() != RocnikNode: raise ValueError("Kořen stromu není ročník!") rocnik = self.root.rocnik self.nazev = "MezicisloNode: První mezičíslo ročníku "+str(rocnik) else: print("!!!!! Nějaké neidentifikované mezičíslo !!!!!") self.nazev = "MezicisloNode: Neidentifikovatelné mezičíslo!" def getOdkazStr(self): return "Obsah dostupný pouze na webu" class TemaVCisleNode(TreeNode): """ Obsahuje příspěvky k tématu v daném čísle """ class Meta: db_table = 'seminar_nodes_temavcisle' verbose_name = 'Téma v čísle (Node)' verbose_name_plural = 'Témata v čísle (Node)' tema = models.ForeignKey(Tema, on_delete=models.PROTECT, # Pokud chci mazat téma, musím si Node pořešit ručně verbose_name = "téma v čísle") def aktualizuj_nazev(self): self.nazev = "TemaVCisleNode: "+str(self.tema) def getOdkazStr(self): return str(self.tema) class OrgTextNode(TreeNode): class Meta: db_table = 'seminar_nodes_orgtextnode' verbose_name = 'Organizátorský článek (Node)' verbose_name_plural = 'Organizátorské články (Node)' organizator = models.ForeignKey(Organizator, null=False, blank=False, on_delete=models.DO_NOTHING, verbose_name="Organizátor", ) org_verejny = models.BooleanField(default = True, verbose_name = "Org je veřejný?", help_text = "Pokud ano, bude org pod článkem podepsaný", null=False, ) def aktualizuj_nazev(self): return f"OrgTextNode začínající následujícim: {self.first_child.nazev}" # FIXME!!! #def getOdkazStr(self): # return str(self.clanek) class UlohaZadaniNode(TreeNode): class Meta: db_table = 'seminar_nodes_uloha_zadani' verbose_name = 'Zadání úlohy (Node)' verbose_name_plural = 'Zadání úloh (Node)' uloha = models.OneToOneField(Uloha, on_delete=models.PROTECT, # Pokud chci mazat téma, musím si Node pořešit ručně verbose_name = "úloha", null=True, blank=False) def aktualizuj_nazev(self): self.nazev = "UlohaZadaniNode: "+str(self.uloha) def getOdkazStr(self): return str(self.uloha) class PohadkaNode(TreeNode): class Meta: db_table = 'seminar_nodes_pohadka' verbose_name = 'Pohádka (Node)' verbose_name_plural = 'Pohádky (Node)' pohadka = models.OneToOneField(Pohadka, on_delete=models.PROTECT, # Pokud chci mazat pohádku, musím si Node pořešit ručně verbose_name = "pohádka", ) def aktualizuj_nazev(self): self.nazev = "PohadkaNode: "+str(self.pohadka) class UlohaVzorakNode(TreeNode): class Meta: db_table = 'seminar_nodes_uloha_vzorak' verbose_name = 'Vzorák úlohy (Node)' verbose_name_plural = 'Vzoráky úloh (Node)' uloha = models.OneToOneField(Uloha, on_delete=models.PROTECT, # Pokud chci mazat téma, musím si Node pořešit ručně verbose_name = "úloha", null=True, blank=False) def aktualizuj_nazev(self): self.nazev = "UlohaVzorakNode: "+str(self.uloha) def getOdkazStr(self): return str(self.uloha) class TextNode(TreeNode): class Meta: db_table = 'seminar_nodes_obsah' verbose_name = 'Text (Node)' verbose_name_plural = 'Text (Node)' text = models.ForeignKey(Text, on_delete=models.PROTECT, verbose_name = 'text') def aktualizuj_nazev(self): self.nazev = "TextNode: "+str(self.text) def getOdkazStr(self): return str(self.text) class CastNode(TreeNode): class Meta: db_table = 'seminar_nodes_cast' verbose_name = 'Část (Node)' verbose_name_plural = 'Části (Node)' nadpis = models.CharField('Nadpis', max_length=100, help_text = 'Nadpis podvěšené části obsahu') def aktualizuj_nazev(self): self.nazev = "CastNode: "+str(self.nadpis) def getOdkazStr(self): return str(self.nadpis) class ReseniNode(TreeNode): class Meta: db_table = 'seminar_nodes_otistene_reseni' verbose_name = 'Otištěné řešení (Node)' verbose_name_plural = 'Otištěná řešení (Node)' reseni = models.ForeignKey(Reseni, on_delete=models.PROTECT, verbose_name = 'reseni') def aktualizuj_nazev(self): self.nazev = "OtisteneReseniNode: "+str(self.reseni) def getOdkazStr(self): return str(self.reseni) ## FIXME: Logiku přesunout do views. #class VysledkyBase(SeminarModelBase): # # class Meta: # verbose_name = 'Řádek výsledkovky' # verbose_name_plural = 'Řádky výsledkovky' # ordering = ['body'] # abstract = True # managed = False # # dummy_id = models.CharField('dummy ID pro view', max_length=32, primary_key=True, # db_column='id') # # cislo = models.ForeignKey(Cislo, verbose_name='číslo pro body', db_column='cislo_id', # on_delete=models.DO_NOTHING) # # resitel = models.ForeignKey(Resitel, verbose_name='řešitel', db_column='resitel_id', # on_delete=models.DO_NOTHING) # # body = models.DecimalField(max_digits=8, decimal_places=1, db_column='body', # verbose_name='body za číslo') # # def __str__(self): # return "%s: %sb (%s)".format(self.resitel.plne_jmeno(), self.body, # str(self.poradi)) # # NOTE: DB zatez pri vypisu (ale nepouzivany) ## FIXME: Logiku přesunout do views. #class VysledkyZaCislo(VysledkyBase): # # class Meta: # db_table = 'seminar_body_za_cislo' # abstract = False # managed = False # # ## FIXME: Logiku přesunout do views. #class VysledkyKCisluZaRocnik(VysledkyBase): # # class Meta: # db_table = 'seminar_body_k_cislu_rocnik' # abstract = False # managed = False # ## body = models.DecimalField(max_digits=8, decimal_places=1, db_column='body', ## verbose_name='body do čísla (za ročník)') # # ## FIXME: Logiku přesunout do views. #class VysledkyKCisluOdjakziva(VysledkyBase): # # class Meta: # db_table = 'seminar_body_k_cislu_odjakziva' # abstract = False # managed = False # ## body = models.DecimalField(max_digits=8, decimal_places=1, db_column='body', ## verbose_name='body do čísla (i minulé ročníky)') # # ## FIXME: Logiku přesunout do views. #class VysledkyCelkemKCislu(VysledkyBase): # # class Meta: # db_table = 'seminar_body_celkem_k_cislu' # abstract = False # managed = False # # body_celkem = models.DecimalField(max_digits=8, decimal_places=1, db_column='body_celkem', # verbose_name='body celkem do čísla včetně minulých ročníků') # # def __str__(self): # # NOTE: DB HOG (ale nepouzivany) # return "%s: %sb / %sb (do %s)" % (self.resitel.plne_jmeno(), self.body, self.body_celkem, str(self.poradi)) ##mozna potreba upravit @reversion.register(ignore_duplicates=True) class Nastaveni(SingletonModel): class Meta: db_table = 'seminar_nastaveni' verbose_name = 'Nastavení semináře' # aktualni_rocnik = models.ForeignKey(Rocnik, verbose_name='aktuální ročník', # null=False, on_delete=models.PROTECT) aktualni_cislo = models.ForeignKey(Cislo, verbose_name='poslední vydané číslo', null=False, on_delete=models.PROTECT) @property def aktualni_rocnik(self): return self.aktualni_cislo.rocnik def __str__(self): return 'Nastavení semináře' def admin_url(self): return reverse('admin:seminar_nastaveni_change', args=(self.id, )) def verejne(self): return False @reversion.register(ignore_duplicates=True) class Novinky(models.Model): class Meta: verbose_name = 'Novinka' verbose_name_plural = 'Novinky' ordering = ['-datum'] datum = models.DateField(auto_now_add=True) text = models.TextField('Text novinky', blank=True, null=True) obrazek = models.ImageField('Obrázek', upload_to='image_novinky/%Y/%m/%d/', null=True, blank=True) obrazek_maly = ImageSpecField(source='obrazek', processors=[ ResizeToFit(350, 200, upscale=False) ], options={'quality': 95}) autor = models.ForeignKey(Organizator, verbose_name='Autor novinky', null=True, on_delete=models.SET_NULL) zverejneno = models.BooleanField('Zveřejněno', default=False) def __str__(self): if self.text: return '[' + str(self.datum) + '] ' + self.text[0:50] else: return '[' + str(self.datum) + '] ' # FIXME: Tohle nepatří do aplikace 'seminar' # Nefunkční alternativa vestavěného Usera, který má jméno a mail v přidružené Osobě # from django.contrib.auth.models import User as Django_User # # class Uzivatel(Django_User): # class Meta: # proxy = True # # @property # def first_name(self): # osoby = Osoba.objects.filter(user=self) # if len(osoby) == 0: # return None # return osoby.first().krestni_jmeno # # @property # def last_name(self): # osoby = Osoba.objects.filter(user=self) # if len(osoby) == 0: # return None # return osoby.first().prijmeni # # @property # def email(self): # osoby = Osoba.objects.filter(user=self) # if len(osoby) == 0: # return None # return osoby.first().email