import datetime
import os
import subprocess
import pathlib
import tempfile
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 seminar . utils import roman
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 seminar . utils import aktivniResitele
from personalni . models import Prijemce , Organizator
from . base import SeminarModelBase
logger = logging . getLogger ( __name__ )
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 ) :
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 neverejna_cisla ( self ) :
vc = [ c for c in self . cisla . all ( ) if not c . verejne ( ) ]
vc . sort ( key = lambda c : c . poradi )
return vc
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 ( deadline_v_cisle__verejna_vysledkovka = True ) . distinct ( ) )
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 ' )
verejne_db = models . BooleanField ( ' číslo zveřejněno ' ,
db_column = ' verejne ' , default = False )
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 ' , 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 ' )
# 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 absolute_url ( self ) :
return " https:// " + str ( get_current_site ( None ) ) + self . verejne_url ( )
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 ( ' poradi ' ) . 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 . run ( [
" gs " ,
" -sstdout= %s tderr " ,
" -dSAFER " ,
" -dNOPAUSE " ,
" -dBATCH " ,
" -dNOPROMPT " ,
" -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 ( )
@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 __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
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 = " --- \n K 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 = " --- \n Pokud 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 seminar . models . treenode 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
@property
def posledni_deadline ( self ) :
return self . deadline_v_cisle . all ( ) . order_by ( " deadline " ) . last ( )
class Deadline ( SeminarModelBase ) :
class Meta :
db_table = ' seminar_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 = ' seminar_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 ) :
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
@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 + " . {} " . format ( self . kod )
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 f ' <Není zadaný: { self . kod } > '
# 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
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 , ) )
@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
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 " ( {} \u2009 b) " . 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 ' , 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 ' + 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 f ' <Není zadaný: { self . kod } > '
def save ( self , * args , * * kwargs ) :
super ( ) . save ( * args , * * kwargs )
# *Node.save() aktualizuje název *Nodu.
for tvcn in self . temavcislenode_set . all ( ) :
tvcn . save ( )
def cislo_node ( self ) :
tema_node_set = self . temavcislenode_set . all ( )
tema_cisla_vyskyt = [ ]
from seminar . models . treenode 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 = ' 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 ' )
strana = models . PositiveIntegerField ( verbose_name = " první strana " , blank = True , null = True )
@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 " + 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 f ' <Není zadaný: { self . kod } > '
def node ( self ) :
return None
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
@cached_property
def kod_v_rocniku ( self ) :
if self . stav == Problem . STAV_ZADANY or self . stav == Problem . STAV_VYRESENY :
return f " { self . cislo_zadani . poradi } . { 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 f ' <Není zadaný: { self . kod } > '
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 seminar . models . treenode 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 ) :
""" 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