You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

730 lines
24 KiB

import datetime
import os
import subprocess
import pathlib
import tempfile
3 years ago
import logging
from django.contrib.sites.shortcuts import get_current_site
from django.db import models
from django.db.models import Q
from django.template.loader import render_to_string
from django.utils import timezone
from django.conf import settings
from django.urls import reverse
from django.core.cache import cache
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.files.storage import FileSystemStorage
from django.utils.text import get_valid_filename
from django.utils.functional import cached_property
from solo.models import SingletonModel
from taggit.managers import TaggableManager
from reversion import revisions as reversion
from treenode import treelib
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
from django.core.mail import EmailMessage
from personalni.models import Prijemce, Organizator
from seminar.models.base import SeminarModelBase
from various.utils import roman
logger = logging.getLogger(__name__)
__all__ = [
"Rocnik", "Cislo", "Deadline", "Problem", "Uloha", "Tema", "Clanek",
"ZmrazenaVysledkovka", "Pohadka",
]
class OverwriteStorage(FileSystemStorage):
""" Varianta FileSystemStorage, která v případě, že soubor cílového
jména již existuje, ho smaže a místo něj uloží soubor nový"""
def get_available_name(self,name, max_length=None):
if self.exists(name):
os.remove(os.path.join(self.location,name))
return super().get_available_name(name,max_length)
@reversion.register(ignore_duplicates=True)
class Rocnik(SeminarModelBase):
6 years ago
class Meta:
db_table = 'mam_rocniky'
verbose_name = 'Ročník'
verbose_name_plural = 'Ročníky'
6 years ago
ordering = ['-rocnik']
6 years ago
# 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
6 years ago
def __str__(self):
return '{} ({}/{})'.format(self.rocnik, self.prvni_rok, self.prvni_rok+1)
# Ročník v římských číslech
6 years ago
def roman(self):
return roman(int(self.rocnik))
6 years ago
def verejne(self):
return len(self.verejna_cisla()) > 0
verejne.boolean = True
verejne.short_description = 'Veřejný (jen dle čísel)'
3 years ago
def neverejna_cisla(self):
vc = [c for c in self.cisla.all() if not c.verejne()]
vc.sort(key=lambda c: c.poradi)
return vc
6 years ago
def verejna_cisla(self):
vc = [c for c in self.cisla.all() if c.verejne()]
vc.sort(key=lambda c: c.poradi)
6 years ago
return vc
6 years ago
def posledni_verejne_cislo(self):
vc = self.verejna_cisla()
return vc[-1] if vc else None
6 years ago
def verejne_vysledkovky_cisla(self):
vc = list(self.cisla.filter(deadline_v_cisle__verejna_vysledkovka=True).distinct())
vc.sort(key=lambda c: c.poradi)
6 years ago
return vc
6 years ago
def posledni_zverejnena_vysledkovka_cislo(self):
vc = self.verejne_vysledkovky_cisla()
return vc[-1] if vc else None
6 years ago
def druhy_rok(self):
return self.prvni_rok + 1
6 years ago
def verejne_url(self):
return reverse('seminar_rocnik', kwargs={'rocnik': self.rocnik})
6 years ago
@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):
6 years ago
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):
6 years ago
class Meta:
db_table = 'mam_cisla'
verbose_name = 'Číslo'
verbose_name_plural = 'Čísla'
ordering = ['-rocnik__rocnik', '-poradi']
6 years ago
# 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)
6 years ago
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!')
6 years ago
datum_vydani = models.DateField('datum vydání', blank=True, null=True,
help_text='Datum vydání finální verze')
6 years ago
verejne_db = models.BooleanField('číslo zveřejněno',
db_column='verejne', default=False)
6 years ago
poznamka = models.TextField('neveřejná poznámka', blank=True,
help_text='Neveřejná poznámka k číslu (plain text)')
6 years ago
pdf = models.FileField('pdf', upload_to=cislo_pdf_filename, null=True, blank=True,
help_text='PDF čísla, které si mohou řešitelé stáhnout', storage=OverwriteStorage())
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')
6 years ago
# má OneToOneField s:
# CisloNode
6 years ago
def kod(self):
return '%s.%s' % (self.rocnik.rocnik, self.poradi)
kod.short_description = 'Kód čísla'
6 years ago
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)
6 years ago
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})
6 years ago
def absolute_url(self):
return "https://" + str(get_current_site(None)) + self.verejne_url()
6 years ago
def nasledujici(self):
"Vrací None, pokud je toto poslední"
6 years ago
return self.relativni_v_rocniku(1)
def predchozi(self):
"Vrací None, pokud je toto první"
6 years ago
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('poradi').all()
6 years ago
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.run([
"gs",
"-sstdout=%stderr",
"-dSAFER",
"-dNOPAUSE",
"-dBATCH",
"-dNOPROMPT",
3 years ago
"-sDEVICE=png16m",
"-r300x300",
"-dFirstPage=1d",
"-dLastPage=1d",
"-sOutputFile=" + str(png_filename),
"-f%s" % self.pdf.path
],
check=True,
capture_output=True
)
with open(png_filename,'rb') as f:
self.titulka_nahled.save('',f,True)
png_filename.unlink()
png_filename.parent.rmdir()
6 years ago
@classmethod
def get(cls, rocnik, cislo):
try:
r = Rocnik.objects.get(rocnik=rocnik)
c = r.cisla.get(poradi=cislo)
6 years ago
except ObjectDoesNotExist:
return None
return c
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__original_verejne = self.verejne_db
def posli_cislo_mailem(self):
# parametry e-mailu
odkaz = self.absolute_url()
poslat_z_mailu = 'zadani@mam.mff.cuni.cz'
predmet = 'Vyšlo číslo {}'.format(self.kod())
# TODO Možná nechceme všem psát „Ahoj“, např. příjemcům…
text_mailu = 'Ahoj,\n' \
'na adrese {} najdete nejnovější číslo.\n' \
'Vaše M&M\n'.format(odkaz)
predmet_prvni = 'Právě vyšlo 1. číslo M&M, pomoz nám ho poslat dál!'
text_mailu_prvni = 'Milý řešiteli,\n'\
'právě jsme na našem webu zveřejnili první číslo {}. ročníku, najdeš ho na tomto odkazu: {}.\n\n'\
'Doufáme, že tě M&M baví, a byli bychom rádi, kdyby mohlo dělat radost i dalším středoškolákům. Máme na tebe proto jednu prosbu. Sdílej prosím odkaz alespoň s jedním svým kamarádem, který by mohl mít o řešení M&M zájem. Je to pro nás moc důležité a velmi nám tím pomůžeš. Díky!\n\n'\
'Organizátoři M&M\n'.format(self.rocnik.rocnik, odkaz)
predmet_resitel = predmet_prvni if self.poradi == "1" else predmet
text_mailu_resitel = text_mailu_prvni if self.poradi == "1" else text_mailu
# Prijemci e-mailu
from personalni.utils import aktivniResitele
resitele_vsichni = aktivniResitele(self).filter(zasilat_cislo_emailem=True)
def posli(subject, text, resitele):
emaily = map(lambda resitel: resitel.osoba.email, resitele)
email = EmailMessage(
subject=subject,
body=text,
from_email=poslat_z_mailu,
bcc=list(emaily)
#bcc = příjemci skryté kopie
)
email.send()
paticka = "---\nK odběru těchto e-mailů jste se přihlásili na stránkách https://mam.matfyz.cz. Z odběru se lze odhlásit na https://mam.matfyz.cz/resitel/osobni-udaje/"
posli(predmet_resitel, text_mailu_resitel + paticka, resitele_vsichni.filter(zasilat_cislo_papirove=False))
posli(predmet_resitel, text_mailu_resitel + 'P. S. Brzy budeme též rozesílat papírovou verzi čísla. Připomínáme, že pokud papírovou verzi čísla nevyužijete, můžete v https://mam.mff.cuni.cz/resitel/osobni-udaje/ zaškrtnout, abychom vám ji neposílali. Čísla vždy můžete nalézt v našem archivu a dál vám budou chodit e-mailem. Děkujeme.\n' + paticka,
resitele_vsichni.filter(zasilat_cislo_papirove=True))
paticka_prijemce = "---\nPokud tyto e-maily nechcete nadále dostávat, prosíme, ozvěte se nám na mam@matfyz.cz."
posli(predmet, text_mailu + paticka_prijemce, Prijemce.objects.filter(zasilat_cislo_emailem=True))
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
self.vygeneruj_nahled()
# Při zveřejnění pošle mail
if self.verejne_db and not self.__original_verejne:
self.posli_cislo_mailem()
# *Node.save() aktualizuje název *Nodu.
try:
self.cislonode.save()
except ObjectDoesNotExist:
# Neexistující *Node nemá smysl aktualizovat, ale je potřeba ho naopak vyrobit
logger.warning(f'Číslo {self} nemělo ČísloNode, vyrábím…')
from treenode.models import CisloNode
CisloNode.objects.create(cislo=self)
def zlomovy_deadline_pro_papirove_cislo(self):
prvni_deadline = Deadline.objects.filter(Q(typ=Deadline.TYP_PRVNI) | Q(typ=Deadline.TYP_PRVNI_A_SOUS), cislo=self).first()
if prvni_deadline is None:
posledni_deadline = self.posledni_deadline
if posledni_deadline is None:
# TODO promyslet, co se má stát tady
return Deadline.objects.filter(Q(cislo__poradi__lt=self.poradi, cislo__rocnik=self.rocnik) | Q(cislo__rocnik__rocnik__lt=self.rocnik.rocnik)).order_by("deadline").last()
return posledni_deadline
return prvni_deadline
2 years ago
@property
def posledni_deadline(self):
return self.deadline_v_cisle.all().order_by("deadline").last()
class Deadline(SeminarModelBase):
class Meta:
db_table = 'mam_deadliny'
verbose_name = 'Deadline'
verbose_name_plural = 'Deadliny'
ordering = ['deadline']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__original_verejna_vysledkovka = self.verejna_vysledkovka
id = models.AutoField(primary_key=True)
# V ročníku < 26 nastaveno na datetime.datetime.combine(datetime.date(1994 + cislo.rocnik.rocnik, 6, int(cislo.poradi[0])), datetime.time.min)
deadline = models.DateTimeField(blank=False, default=timezone.make_aware(datetime.datetime.combine(timezone.now(), datetime.time.max)))
cislo = models.ForeignKey(Cislo, verbose_name='deadline v čísle',
related_name='deadline_v_cisle', blank=False,
on_delete=models.CASCADE)
TYP_CISLA = 'cisla'
TYP_PRVNI_A_SOUS = 'prvniasous'
TYP_PRVNI = 'prvni'
TYP_SOUS = 'sous'
TYP_CHOICES = [
(TYP_CISLA, 'Deadline celého čísla'),
(TYP_PRVNI, 'První deadline'),
(TYP_PRVNI_A_SOUS, 'Sousový a první deadline'),
(TYP_SOUS, 'Sousový deadline'),
]
CHOICES_MAP = dict(TYP_CHOICES)
typ = models.CharField('typ deadlinu', max_length=32,
choices=TYP_CHOICES, blank=False)
verejna_vysledkovka = models.BooleanField('veřejná výsledkovka',
db_column='verejna_vysledkovka',
default=False)
def __str__(self):
return self.CHOICES_MAP[self.typ] + " " + str(self.cislo)
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
if self.verejna_vysledkovka and not self.__original_verejna_vysledkovka:
self.vygeneruj_vysledkovku()
if not self.verejna_vysledkovka and hasattr(self, "vysledkovka_v_deadlinu"):
self.vysledkovka_v_deadlinu.delete()
def vygeneruj_vysledkovku(self):
from vysledkovky.utils import VysledkovkaCisla
if hasattr(self, "vysledkovka_v_deadlinu"):
self.vysledkovka_v_deadlinu.delete()
vysledkovka = VysledkovkaCisla(self.cislo, jen_verejne=True, do_deadlinu=self)
if len(vysledkovka.radky_vysledkovky) != 0:
ZmrazenaVysledkovka.objects.create(
deadline=self,
html=render_to_string(
"vysledkovky/vysledkovka_cisla.html",
context={"vysledkovka": vysledkovka, "oznaceni_vysledkovky": self.id}
)
)
class ZmrazenaVysledkovka(SeminarModelBase):
class Meta:
db_table = 'mam_vysledkovky'
verbose_name = 'Zmražená výsledkovka'
verbose_name_plural = 'Zmražené výsledkovky'
deadline = models.OneToOneField(
Deadline,
on_delete=models.CASCADE,
primary_key=True,
related_name="vysledkovka_v_deadlinu"
)
html = models.TextField(null=False, blank=False)
@reversion.register(ignore_duplicates=True)
# Pozor na následující řádek. *Nekrmit, asi kouše!*
class Problem(SeminarModelBase,PolymorphicModel):
6 years ago
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 = 'mam_problemy'
verbose_name = 'Problém'
verbose_name_plural = 'Problémy'
6 years ago
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
6 years ago
# 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)
6 years ago
STAV_NAVRH = 'navrh'
STAV_ZADANY = 'zadany'
STAV_VYRESENY = 'vyreseny'
6 years ago
STAV_SMAZANY = 'smazany'
STAV_CHOICES = [
(STAV_NAVRH, 'Návrh'),
(STAV_ZADANY, 'Zadaný'),
(STAV_VYRESENY, 'Vyřešený'),
(STAV_SMAZANY, 'Smazaný'),
6 years ago
]
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)
6 years ago
zamereni = TaggableManager(verbose_name='zaměření',
help_text='Zaměření M/F/I/O problému, příp. další tagy', blank=True)
6 years ago
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 ...')
6 years ago
autor = models.ForeignKey(Organizator, verbose_name='autor problému',
related_name='autor_problemu_%(class)s', null=True, blank=True,
on_delete=models.SET_NULL)
6 years ago
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)
6 years ago
opravovatele = models.ManyToManyField(Organizator, verbose_name='opravovatelé',
blank=True, related_name='opravovatele_%(class)s')
6 years ago
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')
6 years ago
vytvoreno = models.DateTimeField('vytvořeno', default=timezone.now, blank=True, editable=False)
6 years ago
def __str__(self):
return self.nazev
6 years ago
# Implicitini implementace, jednotlivé dědící třídy si přepíšou
@cached_property
6 years ago
def kod_v_rocniku(self):
if self.stav == Problem.STAV_ZADANY or self.stav == Problem.STAV_VYRESENY:
if self.nadproblem:
return self.nadproblem.kod_v_rocniku+".{}".format(self.kod)
6 years ago
return str(self.kod)
logger.warning(f"K problému {self} byl vyžadován kód v ročníku, i když není zadaný ani vyřešený.")
return '<Není zadaný>'
6 years ago
# def verejne(self):
# # aktuálně podle stavu problému
# # FIXME pro některé problémy možná chceme override
# # FIXME vrací veřejnost čistě problému, nezávisle na čísle, ve kterém je.
# # Je to tak správně? Podle aktuální představy ano.
# stav_verejny = False
# if self.stav == 'zadany' or self.stav == 'vyreseny':
# stav_verejny = True
# print("stav_verejny: {}".format(stav_verejny))
#
# cislo_verejne = False
# cislonode = self.cislo_node()
# if cislonode is None:
# # problém nemá vlastní node, veřejnost posuzujeme jen podle stavu
# print("empty node")
# return stav_verejny
# else:
# cislo_zadani = cislonode.cislo
# if (cislo_zadani and cislo_zadani.verejne()):
# print("cislo: {}".format(cislo_zadani))
# cislo_verejne = True
# print("stav_verejny: {}".format(stav_verejny))
# print("cislo_verejne: {}".format(cislo_verejne))
# return (stav_verejny and cislo_verejne)
# verejne.boolean = True
6 years ago
def verejne_url(self):
return reverse('seminar_problem', kwargs={'pk': self.id})
def admin_url(self):
return reverse('admin:tvorba_problem_change', args=(self.id, ))
6 years ago
@cached_property
def hlavni_problem(self):
""" Pro daný problém vrátí jeho nejvyšší nadproblém."""
problem = self
while not (problem.nadproblem is None):
problem = problem.nadproblem
return problem
# FIXME - k úloze
6 years ago
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 = 'mam_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',related_name='temata',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, blank=True)
@cached_property
def kod_v_rocniku(self):
if self.stav == Problem.STAV_ZADANY or self.stav == Problem.STAV_VYRESENY:
if self.nadproblem:
return self.nadproblem.kod_v_rocniku+".t{}".format(self.kod)
return "t{}".format(self.kod)
logger.warning(f"K problému {self} byl vyžadován kód v ročníku, i když není zadaný ani vyřešený.")
return '<Není zadaný>'
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
# *Node.save() aktualizuje název *Nodu.
5 years ago
for tvcn in self.temavcislenode_set.all():
tvcn.save()
def cislo_node(self):
tema_node_set = self.temavcislenode_set.all()
tema_cisla_vyskyt = []
from treenode.models import CisloNode
for tn in tema_node_set:
tema_cisla_vyskyt.append(
treelib.get_upper_node_of_type(tn, CisloNode).cislo)
tema_cisla_vyskyt.sort(key=lambda x:x.datum_vydani)
prvni_zadani = tema_cisla_vyskyt[0]
return prvni_zadani.cislonode
class Clanek(Problem):
class Meta:
db_table = 'mam_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')
@cached_property
def kod_v_rocniku(self):
if self.stav == Problem.STAV_ZADANY or self.stav == Problem.STAV_VYRESENY:
# 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)
logger.warning(f"K problému {self} byl vyžadován kód v ročníku, i když není zadaný ani vyřešený.")
return '<Není zadaný>'
def node(self):
return None
class Uloha(Problem):
class Meta:
db_table = 'mam_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
@cached_property
def kod_v_rocniku(self):
if self.stav == Problem.STAV_ZADANY or self.stav == Problem.STAV_VYRESENY:
name="{}.u{}".format(self.cislo_zadani.poradi,self.kod)
if self.nadproblem:
return self.nadproblem.kod_v_rocniku+name
return name
logger.warning(f"K problému {self} byl vyžadován kód v ročníku, i když není zadaný ani vyřešený.")
return '<Není zadaný>'
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
def cislo_node(self):
zadani_node = self.ulohazadaninode
from treenode.models import CisloNode
return treelib.get_upper_node_of_type(zadani_node, CisloNode)
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)
class Pohadka(SeminarModelBase):
6 years ago
"""Kus pohádky před/za úlohou v čísle"""
6 years ago
class Meta:
db_table = 'mam_pohadky'
verbose_name = 'Pohádka'
verbose_name_plural = 'Pohádky'
ordering = ['vytvoreno']
6 years ago
# Interní ID
id = models.AutoField(primary_key=True)
6 years ago
autor = models.ForeignKey(
Organizator,
6 years ago
verbose_name="Autor pohádky",
6 years ago
# 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
6 years ago
)
vytvoreno = models.DateTimeField(
'Vytvořeno',
6 years ago
default=timezone.now,
blank=True,
editable=False
)
# má OneToOneField s:
# PohadkaNode
6 years ago
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