2014-11-17 12:05:46 +01:00
# -*- coding: utf-8 -*-
2022-10-01 11:25:53 +02:00
import datetime
2015-03-13 20:08:18 +01:00
import os
2020-04-08 23:09:25 +02:00
import subprocess
import pathlib
import tempfile
2021-09-05 16:01:05 +02:00
import logging
2015-03-13 20:08:18 +01:00
2021-09-05 10:10:55 +02:00
from django . contrib . sites . shortcuts import get_current_site
2014-11-17 12:05:46 +01:00
from django . db import models
2022-10-01 21:47:15 +02:00
from django . db . models import Q
2022-10-10 09:28:12 +02:00
from django . template . loader import render_to_string
2014-11-17 12:05:46 +01:00
from django . utils import timezone
from django . conf import settings
2021-10-08 18:06:02 +02:00
from django . urls import reverse
2015-05-21 22:58:37 +02:00
from django . core . cache import cache
2020-06-25 13:08:27 +02:00
from django . core . exceptions import ObjectDoesNotExist , ValidationError
2021-11-29 23:55:06 +01:00
from django . core . files . storage import FileSystemStorage
2016-09-03 21:52:08 +02:00
from django . utils . text import get_valid_filename
2021-02-24 00:54:39 +01:00
from django . utils . functional import cached_property
2015-05-21 22:58:37 +02:00
2015-03-14 15:41:29 +01:00
from solo . models import SingletonModel
2015-05-15 15:28:00 +02:00
from taggit . managers import TaggableManager
2015-03-14 15:41:29 +01:00
2016-01-09 17:03:40 +01:00
from reversion import revisions as reversion
2015-05-14 16:02:00 +02:00
2021-11-07 10:47:39 +01:00
from seminar . utils import roman
2021-11-07 10:25:34 +01:00
from treenode import treelib
2015-05-11 12:56:39 +02:00
2019-12-11 23:35:27 +01:00
from unidecode import unidecode # Používám pro získání ID odkazu (ještě je to někde po někom zakomentované)
2016-09-03 21:52:08 +02:00
2019-08-13 20:57:33 +02:00
from polymorphic . models import PolymorphicModel
2016-06-05 21:17:28 +02:00
2020-07-02 11:07:45 +02:00
from django . core . mail import EmailMessage
from seminar . utils import aktivniResitele
2020-04-08 23:09:25 +02:00
2021-10-08 18:06:02 +02:00
from . import personalni as pm
2014-11-17 12:05:46 +01:00
2021-10-08 18:06:02 +02:00
from . base import SeminarModelBase
2015-07-09 11:46:46 +02:00
2021-10-08 18:06:02 +02:00
logger = logging . getLogger ( __name__ )
2015-07-09 03:07:29 +02:00
2021-11-29 23:14:13 +01:00
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 )
2014-11-17 12:05:46 +01:00
2018-08-20 22:30:16 +02:00
@reversion.register ( ignore_duplicates = True )
2015-05-11 12:56:39 +02:00
class Rocnik ( SeminarModelBase ) :
2014-11-17 12:05:46 +01:00
2019-03-26 21:14:10 +01:00
class Meta :
db_table = ' seminar_rocniky '
2019-04-16 21:13:36 +02:00
verbose_name = ' Ročník '
verbose_name_plural = ' Ročníky '
2019-03-26 21:14:10 +01:00
ordering = [ ' -rocnik ' ]
2014-11-17 12:05:46 +01:00
2019-03-26 21:14:10 +01:00
# Interní ID
id = models . AutoField ( primary_key = True )
2014-11-17 12:05:46 +01:00
2019-04-16 21:13:36 +02:00
prvni_rok = models . IntegerField ( ' první rok ' , db_index = True , unique = True )
2015-03-13 20:08:18 +01:00
2019-04-16 21:13:36 +02:00
rocnik = models . IntegerField ( ' číslo ročníku ' , db_index = True , unique = True )
2015-03-13 20:08:18 +01:00
2019-04-16 21:13:36 +02:00
exportovat = models . BooleanField ( ' export do AESOPa ' , db_column = ' exportovat ' , default = False ,
2019-06-10 22:51:37 +02:00
help_text = ' Exportuje se jen podle tohoto flagu (ne veřejnosti), '
' a to jen čísla s veřejnou výsledkovkou ' )
# má OneToOneField s:
# RocnikNode
2015-07-23 20:42:50 +02:00
2019-03-26 21:14:10 +01:00
def __str__ ( self ) :
2019-05-18 10:57:09 +02:00
return ' {} ( {} / {} ) ' . format ( self . rocnik , self . prvni_rok , self . prvni_rok + 1 )
2014-11-17 12:05:46 +01:00
2019-03-27 01:01:57 +01:00
# Ročník v římských číslech
2019-03-26 21:14:10 +01:00
def roman ( self ) :
2019-05-18 10:57:09 +02:00
return roman ( int ( self . rocnik ) )
2014-11-17 12:05:46 +01:00
2019-03-26 21:14:10 +01:00
def verejne ( self ) :
return len ( self . verejna_cisla ( ) ) > 0
verejne . boolean = True
2019-04-16 21:13:36 +02:00
verejne . short_description = ' Veřejný (jen dle čísel) '
2021-11-22 20:11:50 +01:00
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
2019-03-26 21:14:10 +01:00
def verejna_cisla ( self ) :
vc = [ c for c in self . cisla . all ( ) if c . verejne ( ) ]
2019-10-30 22:56:45 +01:00
vc . sort ( key = lambda c : c . poradi )
2019-03-26 21:14:10 +01:00
return vc
2015-05-06 17:07:32 +02:00
2019-03-26 21:14:10 +01:00
def posledni_verejne_cislo ( self ) :
vc = self . verejna_cisla ( )
return vc [ - 1 ] if vc else None
2015-04-01 14:01:13 +02:00
2019-03-26 21:14:10 +01:00
def verejne_vysledkovky_cisla ( self ) :
2022-10-08 12:43:51 +02:00
vc = list ( self . cisla . filter ( deadline_v_cisle__verejna_vysledkovka = True ) . distinct ( ) )
2019-10-30 22:56:45 +01:00
vc . sort ( key = lambda c : c . poradi )
2019-03-26 21:14:10 +01:00
return vc
2015-07-23 20:52:56 +02:00
2019-03-26 21:14:10 +01:00
def posledni_zverejnena_vysledkovka_cislo ( self ) :
vc = self . verejne_vysledkovky_cisla ( )
return vc [ - 1 ] if vc else None
2015-07-23 20:52:56 +02:00
2019-03-26 21:14:10 +01:00
def druhy_rok ( self ) :
return self . prvni_rok + 1
2015-04-01 14:01:13 +02:00
2019-03-26 21:14:10 +01:00
def verejne_url ( self ) :
return reverse ( ' seminar_rocnik ' , kwargs = { ' rocnik ' : self . rocnik } )
2015-05-11 12:56:39 +02:00
2019-03-26 21:14:10 +01:00
@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
2019-11-14 04:00:27 +01:00
def save ( self , * args , * * kwargs ) :
super ( ) . save ( * args , * * kwargs )
2019-11-08 00:31:19 +01:00
# *Node.save() aktualizuje název *Nodu.
2019-11-20 20:45:32 +01:00
try :
self . rocniknode . save ( )
2019-11-20 20:51:17 +01:00
except ObjectDoesNotExist :
2019-11-20 20:45:32 +01:00
# Neexistující *Node nemá smysl aktualizovat.
pass
2015-05-21 22:58:37 +02:00
2015-09-12 12:35:30 +02:00
def cislo_pdf_filename ( self , filename ) :
2019-03-26 21:14:10 +01:00
rocnik = str ( self . rocnik . rocnik )
2020-04-08 23:09:25 +02:00
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 ) )
2015-05-11 12:56:39 +02:00
2018-08-20 22:30:16 +02:00
@reversion.register ( ignore_duplicates = True )
2015-05-11 12:56:39 +02:00
class Cislo ( SeminarModelBase ) :
2014-11-17 12:05:46 +01:00
2019-03-26 21:14:10 +01:00
class Meta :
db_table = ' seminar_cisla '
2019-04-16 21:13:36 +02:00
verbose_name = ' Číslo '
verbose_name_plural = ' Čísla '
2019-10-30 22:56:45 +01:00
ordering = [ ' -rocnik__rocnik ' , ' -poradi ' ]
2019-03-26 21:14:10 +01:00
# Interní ID
id = models . AutoField ( primary_key = True )
2019-06-10 23:55:45 +02:00
rocnik = models . ForeignKey ( Rocnik , verbose_name = ' ročník ' , related_name = ' cisla ' ,
db_index = True , on_delete = models . PROTECT )
2019-03-26 21:14:10 +01:00
2019-10-30 22:56:45 +01:00
poradi = models . CharField ( ' název čísla ' , max_length = 32 , db_index = True ,
2019-04-16 21:13:36 +02:00
help_text = ' Většinou jen " 1 " , vyjímečně " 7-8 " , lexikograficky určuje pořadí v ročníku! ' )
2019-03-26 21:14:10 +01:00
2019-04-16 21:13:36 +02:00
datum_vydani = models . DateField ( ' datum vydání ' , blank = True , null = True ,
help_text = ' Datum vydání finální verze ' )
2019-03-26 21:14:10 +01:00
2019-04-16 21:13:36 +02:00
verejne_db = models . BooleanField ( ' číslo zveřejněno ' ,
2019-03-27 01:01:57 +01:00
db_column = ' verejne ' , default = False )
2019-03-26 21:14:10 +01:00
2019-04-16 21:13:36 +02:00
poznamka = models . TextField ( ' neveřejná poznámka ' , blank = True ,
help_text = ' Neveřejná poznámka k číslu (plain text) ' )
2019-03-26 21:14:10 +01:00
2019-04-16 21:13:36 +02:00
pdf = models . FileField ( ' pdf ' , upload_to = cislo_pdf_filename , null = True , blank = True ,
2021-11-29 23:14:13 +01:00
help_text = ' PDF čísla, které si mohou řešitelé stáhnout ' , storage = OverwriteStorage ( ) )
2020-04-08 23:09:25 +02:00
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 ' )
2019-03-26 21:14:10 +01:00
2019-06-10 22:51:37 +02:00
# má OneToOneField s:
# CisloNode
2019-03-26 21:14:10 +01:00
def kod ( self ) :
2019-10-30 22:56:45 +01:00
return ' %s . %s ' % ( self . rocnik . rocnik , self . poradi )
2019-04-16 21:13:36 +02:00
kod . short_description = ' Kód čísla '
2019-03-26 21:14:10 +01:00
def __str__ ( self ) :
# Potenciální DB HOG, pokud by se ročník necachoval
r = Rocnik . cached_rocnik ( self . rocnik_id )
2019-10-30 22:56:45 +01:00
return ' {} . {} ' . format ( r . rocnik , self . poradi )
2019-03-26 21:14:10 +01:00
def verejne ( self ) :
return self . verejne_db
verejne . boolean = True
def verejne_url ( self ) :
2019-10-30 22:56:45 +01:00
return reverse ( ' seminar_cislo ' , kwargs = { ' rocnik ' : self . rocnik . rocnik , ' cislo ' : self . poradi } )
2019-03-26 21:14:10 +01:00
2021-09-05 10:55:48 +02:00
def absolute_url ( self ) :
return " https:// " + str ( get_current_site ( None ) ) + self . verejne_url ( )
2019-03-26 21:14:10 +01:00
def nasledujici ( self ) :
2019-05-18 10:57:09 +02:00
" Vrací None, pokud je toto poslední "
2019-03-26 21:14:10 +01:00
return self . relativni_v_rocniku ( 1 )
def predchozi ( self ) :
2019-05-18 10:57:09 +02:00
" Vrací None, pokud je toto první "
2019-03-26 21:14:10 +01:00
return self . relativni_v_rocniku ( - 1 )
def relativni_v_rocniku ( self , rel_index ) :
2019-05-18 10:57:09 +02:00
" Číslo o `index` dále v ročníku. None pokud neexistuje. "
2022-10-03 16:56:38 +02:00
cs = self . rocnik . cisla . order_by ( ' poradi ' ) . all ( )
2019-03-26 21:14:10 +01:00
i = list ( cs ) . index ( self ) + rel_index
if ( i < 0 ) or ( i > = len ( cs ) ) :
return None
return cs [ i ]
2020-04-08 23:09:25 +02:00
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 ' )
2021-03-23 19:42:25 +01:00
subprocess . run ( [
2021-04-06 23:11:22 +02:00
" gs " ,
" -sstdout= %s tderr " ,
" -dSAFER " ,
" -dNOPAUSE " ,
" -dBATCH " ,
" -dNOPROMPT " ,
2021-09-02 19:58:36 +02:00
" -sDEVICE=png16m " ,
2021-09-02 22:46:04 +02:00
" -r300x300 " ,
2021-04-06 23:11:22 +02:00
" -dFirstPage=1d " ,
" -dLastPage=1d " ,
" -sOutputFile= " + str ( png_filename ) ,
" -f %s " % self . pdf . path
2021-03-23 19:42:25 +01:00
] ,
check = True ,
capture_output = True
)
2020-04-08 23:09:25 +02:00
with open ( png_filename , ' rb ' ) as f :
self . titulka_nahled . save ( ' ' , f , True )
png_filename . unlink ( )
png_filename . parent . rmdir ( )
2019-03-26 21:14:10 +01:00
@classmethod
def get ( cls , rocnik , cislo ) :
try :
r = Rocnik . objects . get ( rocnik = rocnik )
2019-11-20 21:17:15 +01:00
c = r . cisla . get ( poradi = cislo )
2019-03-26 21:14:10 +01:00
except ObjectDoesNotExist :
return None
return c
2016-06-05 21:17:28 +02:00
2020-07-02 11:07:45 +02:00
def __init__ ( self , * args , * * kwargs ) :
super ( ) . __init__ ( * args , * * kwargs )
self . __original_verejne = self . verejne_db
def posli_cislo_mailem ( self ) :
# parametry e-mailu
2021-09-05 10:55:48 +02:00
odkaz = self . absolute_url ( )
2020-07-02 11:07:45 +02:00
poslat_z_mailu = ' zadani@mam.mff.cuni.cz '
2023-06-01 14:47:03 +02:00
predmet = ' Vyšlo číslo {} ' . format ( self . kod ( ) )
2023-04-17 20:41:01 +02:00
# TODO Možná nechceme všem psát „Ahoj“, např. příjemcům…
2020-07-02 11:07:45 +02:00
text_mailu = ' Ahoj, \n ' \
' na adrese {} najdete nejnovější číslo. \n ' \
' Vaše M&M \n ' . format ( odkaz )
2023-06-01 14:47:03 +02:00
predmet_prvni = ' Právě vyšlo 1. číslo M&M, pomoz nám ho poslat dál! '
2023-06-01 14:24:14 +02:00
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 )
2023-06-01 14:47:03 +02:00
predmet_resitel = predmet_prvni if self . poradi == " 1 " else predmet
2023-06-01 14:24:14 +02:00
text_mailu_resitel = text_mailu_prvni if self . poradi == " 1 " else text_mailu
2020-07-02 11:07:45 +02:00
# Prijemci e-mailu
2023-01-30 20:58:37 +01:00
resitele_vsichni = aktivniResitele ( self ) . filter ( zasilat_cislo_emailem = True )
2023-06-01 14:47:03 +02:00
def posli ( subject , text , resitele ) :
2023-01-30 20:58:37 +01:00
emaily = map ( lambda resitel : resitel . osoba . email , resitele )
if not settings . POSLI_MAILOVOU_NOTIFIKACI :
print ( " Poslal bych upozornění na tyto adresy: " , " " . join ( emaily ) )
return
email = EmailMessage (
2023-06-01 14:47:03 +02:00
subject = subject ,
2023-01-30 20:58:37 +01:00
body = text ,
from_email = poslat_z_mailu ,
bcc = list ( emaily )
#bcc = příjemci skryté kopie
)
2020-07-02 11:07:45 +02:00
2023-01-30 20:58:37 +01:00
email . send ( )
2020-07-02 11:07:45 +02:00
2023-03-06 22:11:58 +01:00
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/ "
2023-06-01 15:08:55 +02:00
posli ( predmet_resitel , text_mailu_resitel + paticka , resitele_vsichni . filter ( zasilat_cislo_papirove = False ) )
2023-06-01 14:47:03 +02:00
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 ,
2023-06-01 15:08:55 +02:00
resitele_vsichni . filter ( zasilat_cislo_papirove = True ) )
2020-07-02 11:07:45 +02:00
2023-04-17 20:41:01 +02:00
paticka_prijemce = " --- \n Pokud tyto e-maily nechcete nadále dostávat, prosíme, ozvěte se nám na mam@matfyz.cz. "
2023-06-01 14:47:03 +02:00
posli ( predmet , text_mailu + paticka_prijemce , pm . Prijemce . objects . filter ( zasilat_cislo_emailem = True ) )
2023-04-17 20:41:01 +02:00
2019-11-14 04:00:27 +01:00
def save ( self , * args , * * kwargs ) :
super ( ) . save ( * args , * * kwargs )
2020-04-08 23:09:25 +02:00
self . vygeneruj_nahled ( )
2020-07-02 11:07:45 +02:00
# Při zveřejnění pošle mail
if self . verejne_db and not self . __original_verejne :
self . posli_cislo_mailem ( )
2019-11-08 00:31:19 +01:00
# *Node.save() aktualizuje název *Nodu.
2019-11-20 20:45:32 +01:00
try :
self . cislonode . save ( )
2019-11-20 20:51:17 +01:00
except ObjectDoesNotExist :
2021-09-05 15:59:10 +02:00
# Neexistující *Node nemá smysl aktualizovat, ale je potřeba ho naopak vyrobit
2021-09-05 16:00:09 +02:00
logger . warning ( f ' Číslo { self } nemělo ČísloNode, vyrábím… ' )
2021-11-07 10:47:39 +01:00
from seminar . models . treenode import CisloNode
2021-09-05 16:00:09 +02:00
CisloNode . objects . create ( cislo = self )
2019-11-08 00:31:19 +01:00
2022-10-01 21:47:15 +02:00
def zlomovy_deadline_pro_papirove_cislo ( self ) :
2022-10-06 11:58:23 +02:00
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 :
2022-10-06 12:05:07 +02:00
posledni_deadline = self . posledni_deadline
if posledni_deadline is None :
# TODO promyslet, co se má stát tady
2022-10-06 12:26:28 +02:00
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 ( )
2022-10-06 12:05:07 +02:00
return posledni_deadline
2022-10-06 11:58:23 +02:00
return prvni_deadline
2022-10-01 21:47:15 +02:00
2022-10-06 10:35:14 +02:00
@property
def posledni_deadline ( self ) :
2022-10-06 11:17:56 +02:00
return self . deadline_v_cisle . all ( ) . order_by ( " deadline " ) . last ( )
2019-04-16 21:45:43 +02:00
2022-10-01 11:25:53 +02:00
class Deadline ( SeminarModelBase ) :
class Meta :
db_table = ' seminar_deadliny '
verbose_name = ' Deadline '
verbose_name_plural = ' Deadliny '
ordering = [ ' deadline ' ]
2022-10-10 09:28:12 +02:00
def __init__ ( self , * args , * * kwargs ) :
super ( ) . __init__ ( * args , * * kwargs )
self . __original_verejna_vysledkovka = self . verejna_vysledkovka
2022-10-01 11:25:53 +02:00
id = models . AutoField ( primary_key = True )
2022-10-11 11:00:53 +02:00
# V ročníku < 26 nastaveno na datetime.datetime.combine(datetime.date(1994 + cislo.rocnik.rocnik, 6, int(cislo.poradi[0])), datetime.time.min)
2022-10-01 11:25:53 +02:00
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 )
2022-10-10 09:28:12 +02:00
def save ( self , * args , * * kwargs ) :
super ( ) . save ( * args , * * kwargs )
if self . verejna_vysledkovka and not self . __original_verejna_vysledkovka :
self . vygeneruj_vysledkovku ( )
2022-10-10 09:30:45 +02:00
if not self . verejna_vysledkovka and hasattr ( self , " vysledkovka_v_deadlinu " ) :
self . vysledkovka_v_deadlinu . delete ( )
2022-10-10 09:28:12 +02:00
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 )
2022-10-01 11:25:53 +02:00
2018-08-20 22:30:16 +02:00
@reversion.register ( ignore_duplicates = True )
2019-08-13 22:03:31 +02:00
# Pozor na následující řádek. *Nekrmit, asi kouše!*
class Problem ( SeminarModelBase , PolymorphicModel ) :
2014-11-17 12:05:46 +01:00
2019-03-26 21:14:10 +01:00
class Meta :
2019-04-30 21:19:52 +02:00
# Není abstraktní, protože se na něj jinak nedají dělat ForeignKeys.
2019-06-10 22:51:37 +02:00
# 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?
2019-04-30 21:19:52 +02:00
#abstract = True
2019-05-16 23:06:06 +02:00
db_table = ' seminar_problemy '
2019-04-16 21:13:36 +02:00
verbose_name = ' Problém '
verbose_name_plural = ' Problémy '
2019-03-26 21:14:10 +01:00
ordering = [ ' nazev ' ]
# Interní ID
id = models . AutoField ( primary_key = True )
# Název
2019-12-11 23:35:27 +01:00
nazev = models . CharField ( ' název ' , max_length = 256 ) # Zveřejnitelný na stránky
2019-03-26 21:14:10 +01:00
2019-03-27 01:01:57 +01:00
# Problém má podproblémy
2019-04-16 21:45:43 +02:00
nadproblem = models . ForeignKey ( ' self ' , verbose_name = ' nadřazený problém ' ,
2019-11-07 21:09:00 +01:00
related_name = ' podproblem ' , null = True , blank = True ,
2019-06-10 23:55:45 +02:00
on_delete = models . SET_NULL )
2019-03-26 21:14:10 +01:00
STAV_NAVRH = ' navrh '
STAV_ZADANY = ' zadany '
2019-03-27 01:01:57 +01:00
STAV_VYRESENY = ' vyreseny '
2019-03-26 21:14:10 +01:00
STAV_SMAZANY = ' smazany '
STAV_CHOICES = [
2019-04-16 21:13:36 +02:00
( STAV_NAVRH , ' Návrh ' ) ,
( STAV_ZADANY , ' Zadaný ' ) ,
( STAV_VYRESENY , ' Vyřešený ' ) ,
( STAV_SMAZANY , ' Smazaný ' ) ,
2019-03-26 21:14:10 +01:00
]
2019-04-16 21:13:36 +02:00
stav = models . CharField ( ' stav problému ' , max_length = 32 , choices = STAV_CHOICES , blank = False , default = STAV_NAVRH )
2020-07-15 11:21:16 +02:00
# 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)
2019-03-26 21:14:10 +01:00
2019-05-24 02:33:04 +02:00
zamereni = TaggableManager ( verbose_name = ' zaměření ' ,
help_text = ' Zaměření M/F/I/O problému, příp. další tagy ' , blank = True )
2019-03-26 21:14:10 +01:00
2019-04-16 21:13:36 +02:00
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 ... ' )
2019-03-26 21:14:10 +01:00
2021-10-08 18:06:02 +02:00
autor = models . ForeignKey ( pm . Organizator , verbose_name = ' autor problému ' ,
2019-06-10 23:55:45 +02:00
related_name = ' autor_problemu_ %(class)s ' , null = True , blank = True ,
on_delete = models . SET_NULL )
2019-03-26 21:14:10 +01:00
2021-10-08 18:06:02 +02:00
garant = models . ForeignKey ( pm . Organizator , verbose_name = ' garant zadaného problému ' ,
2019-06-10 23:55:45 +02:00
related_name = ' garant_problemu_ %(class)s ' , null = True , blank = True ,
on_delete = models . SET_NULL )
2019-03-26 21:14:10 +01:00
2021-10-08 18:06:02 +02:00
opravovatele = models . ManyToManyField ( pm . Organizator , verbose_name = ' opravovatelé ' ,
2019-04-30 21:26:25 +02:00
blank = True , related_name = ' opravovatele_ %(class)s ' )
2019-03-26 21:14:10 +01:00
2019-04-16 21:13:36 +02:00
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 ' )
2019-03-26 21:14:10 +01:00
2019-04-16 21:13:36 +02:00
vytvoreno = models . DateTimeField ( ' vytvořeno ' , default = timezone . now , blank = True , editable = False )
2019-03-26 21:14:10 +01:00
def __str__ ( self ) :
2019-05-18 10:57:09 +02:00
return self . nazev
2019-03-26 21:14:10 +01:00
2019-03-27 01:01:57 +01:00
# Implicitini implementace, jednotlivé dědící třídy si přepíšou
2021-02-24 00:54:39 +01:00
@cached_property
2019-03-26 21:14:10 +01:00
def kod_v_rocniku ( self ) :
2021-11-29 22:07:00 +01:00
if self . stav == Problem . STAV_ZADANY or self . stav == Problem . STAV_VYRESENY :
2019-03-27 01:01:57 +01:00
if self . nadproblem :
2021-02-24 00:54:39 +01:00
return self . nadproblem . kod_v_rocniku + " . {} " . format ( self . kod )
2019-06-20 22:49:21 +02:00
return str ( self . kod )
2021-11-29 22:07:00 +01:00
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ý. " )
2019-04-16 21:13:36 +02:00
return ' <Není zadaný> '
2019-03-26 21:14:10 +01:00
2021-03-02 23:45:58 +01:00
# 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
2019-03-26 21:14:10 +01:00
def verejne_url ( self ) :
return reverse ( ' seminar_problem ' , kwargs = { ' pk ' : self . id } )
def admin_url ( self ) :
2020-04-22 22:21:23 +02:00
return reverse ( ' admin:seminar_problem_change ' , args = ( self . id , ) )
2019-03-26 21:14:10 +01:00
2022-10-05 21:08:43 +02:00
@cached_property
2021-09-19 19:16:37 +02:00
def hlavni_problem ( self ) :
""" Pro daný problém vrátí jeho nejvyšší nadproblém. """
2022-10-05 21:43:15 +02:00
problem = self
while not ( problem . nadproblem is None ) :
problem = problem . nadproblem
return problem
2021-09-19 19:16:37 +02:00
2019-03-27 01:01:57 +01:00
# FIXME - k úloze
2019-03-26 21:14:10 +01:00
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
2019-05-18 10:57:09 +02:00
return " ( {} \u2009 b) " . format ( pocet_bodu ) if self . body else " "
2015-09-10 15:45:27 +02:00
2019-03-27 01:01:57 +01:00
class Tema ( Problem ) :
class Meta :
db_table = ' seminar_temata '
2019-04-16 21:13:36 +02:00
verbose_name = ' Téma '
verbose_name_plural = ' Témata '
2019-03-27 01:01:57 +01:00
TEMA_TEMA = ' tema '
TEMA_SERIAL = ' serial '
TEMA_CHOICES = [
2019-04-16 21:13:36 +02:00
( TEMA_TEMA , ' Téma ' ) ,
( TEMA_SERIAL , ' Seriál ' ) ,
2019-03-27 01:01:57 +01:00
]
2019-05-24 02:33:04 +02:00
tema_typ = models . CharField ( ' Typ tématu ' , max_length = 16 , choices = TEMA_CHOICES ,
blank = False , default = TEMA_TEMA )
2019-03-27 01:01:57 +01:00
2021-02-23 21:58:10 +01:00
rocnik = models . ForeignKey ( Rocnik , verbose_name = ' ročník ' , related_name = ' temata ' , blank = True , null = True ,
2019-06-10 23:55:45 +02:00
on_delete = models . PROTECT )
2019-03-27 01:01:57 +01:00
2019-11-21 00:01:03 +01:00
abstrakt = models . TextField ( ' Abstrakt na rozcestník ' , blank = True )
2021-09-03 01:12:28 +02:00
obrazek = models . ImageField ( ' Obrázek na rozcestník ' , null = True , blank = True )
2019-11-21 00:01:03 +01:00
2021-02-24 00:54:39 +01:00
@cached_property
2019-03-27 01:01:57 +01:00
def kod_v_rocniku ( self ) :
2021-11-29 22:07:00 +01:00
if self . stav == Problem . STAV_ZADANY or self . stav == Problem . STAV_VYRESENY :
2019-03-27 01:01:57 +01:00
if self . nadproblem :
2021-02-24 00:54:39 +01:00
return self . nadproblem . kod_v_rocniku + " .t {} " . format ( self . kod )
2019-05-18 10:57:09 +02:00
return " t {} " . format ( self . kod )
2021-11-29 22:07:00 +01:00
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ý. " )
2019-04-16 21:13:36 +02:00
return ' <Není zadaný> '
2019-03-27 01:01:57 +01:00
2019-11-14 04:00:27 +01:00
def save ( self , * args , * * kwargs ) :
super ( ) . save ( * args , * * kwargs )
2019-11-08 00:31:19 +01:00
# *Node.save() aktualizuje název *Nodu.
2019-11-20 21:26:33 +01:00
for tvcn in self . temavcislenode_set . all ( ) :
2019-11-08 00:31:19 +01:00
tvcn . save ( )
2021-02-16 23:26:52 +01:00
def cislo_node ( self ) :
tema_node_set = self . temavcislenode_set . all ( )
tema_cisla_vyskyt = [ ]
2021-11-07 10:47:39 +01:00
from seminar . models . treenode import CisloNode
2021-02-16 23:26:52 +01:00
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
2019-03-27 01:01:57 +01:00
class Clanek ( Problem ) :
class Meta :
db_table = ' seminar_clanky '
2019-04-16 21:13:36 +02:00
verbose_name = ' Článek '
verbose_name_plural = ' Články '
2019-03-27 01:01:57 +01:00
2020-06-17 21:56:00 +02:00
cislo = models . ForeignKey ( Cislo , blank = True , null = True , on_delete = models . PROTECT ,
verbose_name = ' číslo vydání ' , related_name = ' vydane_clanky ' )
2021-03-09 20:28:27 +01:00
@cached_property
2019-03-27 01:01:57 +01:00
def kod_v_rocniku ( self ) :
2021-11-29 22:07:00 +01:00
if self . stav == Problem . STAV_ZADANY or self . stav == Problem . STAV_VYRESENY :
2019-03-27 01:01:57 +01:00
# Nemělo by být potřeba
# if self.nadproblem:
2021-02-24 00:54:39 +01:00
# return self.nadproblem.kod_v_rocniku+".c{}".format(self.kod)
2019-05-18 10:57:09 +02:00
return " c {} " . format ( self . kod )
2021-11-29 22:07:00 +01:00
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ý. " )
2019-04-16 21:13:36 +02:00
return ' <Není zadaný> '
2021-02-16 23:26:52 +01:00
def node ( self ) :
return None
2019-04-16 21:45:43 +02:00
2019-03-27 01:01:57 +01:00
class Uloha ( Problem ) :
class Meta :
db_table = ' seminar_ulohy '
2019-04-16 21:13:36 +02:00
verbose_name = ' Úloha '
verbose_name_plural = ' Úlohy '
2019-03-27 01:01:57 +01:00
2019-05-24 02:33:04 +02:00
cislo_zadani = models . ForeignKey ( Cislo , verbose_name = ' číslo zadání ' , blank = True ,
2019-06-10 23:55:45 +02:00
null = True , related_name = ' zadane_ulohy ' , on_delete = models . PROTECT )
2019-03-27 01:01:57 +01:00
2019-05-24 02:33:04 +02:00
cislo_deadline = models . ForeignKey ( Cislo , verbose_name = ' číslo deadlinu ' , blank = True ,
2019-06-10 23:55:45 +02:00
null = True , related_name = ' deadlinove_ulohy ' , on_delete = models . PROTECT )
2019-03-27 01:01:57 +01:00
2019-05-24 02:33:04 +02:00
cislo_reseni = models . ForeignKey ( Cislo , verbose_name = ' číslo řešení ' , blank = True ,
null = True , related_name = ' resene_ulohy ' ,
2019-06-10 23:55:45 +02:00
help_text = ' Číslo s řešením úlohy, jen pro úlohy ' ,
on_delete = models . PROTECT )
2019-03-27 01:01:57 +01:00
2019-05-24 02:33:04 +02:00
max_body = models . DecimalField ( max_digits = 8 , decimal_places = 1 , verbose_name = ' maximum bodů ' ,
blank = True , null = True )
2019-03-27 01:01:57 +01:00
2019-06-10 22:51:37 +02:00
# má OneToOneField s:
# UlohaZadaniNode
# UlohaVzorakNode
2021-02-24 00:54:39 +01:00
@cached_property
2019-03-27 01:01:57 +01:00
def kod_v_rocniku ( self ) :
2021-11-29 22:07:00 +01:00
if self . stav == Problem . STAV_ZADANY or self . stav == Problem . STAV_VYRESENY :
2019-10-30 22:56:45 +01:00
name = " {} .u {} " . format ( self . cislo_zadani . poradi , self . kod )
2019-03-27 01:01:57 +01:00
if self . nadproblem :
2021-02-24 00:54:39 +01:00
return self . nadproblem . kod_v_rocniku + name
2019-05-18 10:57:09 +02:00
return name
2021-11-29 22:07:00 +01:00
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ý. " )
2019-04-16 21:13:36 +02:00
return ' <Není zadaný> '
2019-03-27 01:01:57 +01:00
2019-11-14 04:00:27 +01:00
def save ( self , * args , * * kwargs ) :
super ( ) . save ( * args , * * kwargs )
2019-11-08 00:31:19 +01:00
# *Node.save() aktualizuje název *Nodu.
2019-11-20 20:45:32 +01:00
try :
self . ulohazadaninode . save ( )
2019-11-20 20:51:17 +01:00
except ObjectDoesNotExist :
2019-11-20 20:45:32 +01:00
# Neexistující *Node nemá smysl aktualizovat.
pass
try :
self . ulohavzoraknode . save ( )
2019-11-20 20:51:17 +01:00
except ObjectDoesNotExist :
2019-11-20 20:45:32 +01:00
# Neexistující *Node nemá smysl aktualizovat.
pass
2019-11-08 00:31:19 +01:00
2021-02-16 23:26:52 +01:00
def cislo_node ( self ) :
2021-11-07 10:47:39 +01:00
zadani_node = self . ulohazadaninode
from seminar . models . treenode import CisloNode
2021-02-16 23:26:52 +01:00
return treelib . get_upper_node_of_type ( zadani_node , CisloNode )
2019-03-27 01:01:57 +01:00
2020-02-28 21:34:53 +01:00
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 ' )
2020-12-02 00:56:15 +01:00
fname = " {} / {} " . format (
2020-02-28 21:34:53 +01:00
timezone . now ( ) . strftime ( ' % Y- % m- %d - % H: % M ' ) ,
clean )
return os . path . join ( datedir , fname )
2016-09-03 21:52:08 +02:00
2016-02-16 00:04:26 +01:00
class Pohadka ( SeminarModelBase ) :
2019-06-20 22:49:21 +02:00
""" Kus pohádky před/za úlohou v čísle """
2016-02-16 00:04:26 +01:00
2019-03-26 21:14:10 +01:00
class Meta :
db_table = ' seminar_pohadky '
2019-04-16 21:13:36 +02:00
verbose_name = ' Pohádka '
verbose_name_plural = ' Pohádky '
2019-05-30 03:18:22 +02:00
ordering = [ ' vytvoreno ' ]
2016-02-16 00:04:26 +01:00
2019-03-26 21:14:10 +01:00
# Interní ID
id = models . AutoField ( primary_key = True )
2016-02-16 00:04:26 +01:00
2019-03-26 21:14:10 +01:00
autor = models . ForeignKey (
2021-10-08 18:06:02 +02:00
pm . Organizator ,
2019-03-26 21:14:10 +01:00
verbose_name = " Autor pohádky " ,
2016-02-17 16:13:11 +01:00
2019-03-26 21:14:10 +01:00
# Při nahrávání z TeXu není vyplnění vyžadováno, v adminu je
null = True ,
2019-06-10 23:55:45 +02:00
blank = False ,
on_delete = models . SET_NULL
2019-03-26 21:14:10 +01:00
)
2016-02-16 00:04:26 +01:00
2019-03-27 01:01:57 +01:00
vytvoreno = models . DateTimeField (
2019-04-16 21:13:36 +02:00
' Vytvořeno ' ,
2019-03-26 21:14:10 +01:00
default = timezone . now ,
blank = True ,
editable = False
)
2016-02-16 00:04:26 +01:00
2019-06-10 22:51:37 +02:00
# má OneToOneField s:
# PohadkaNode
2019-03-26 21:14:10 +01:00
def __str__ ( self ) :
2019-03-27 01:01:57 +01:00
uryvek = self . text if len ( self . text ) < 50 else self . text [ : ( 50 - 3 ) ] + " ... "
2019-05-18 10:57:09 +02:00
return uryvek
2016-02-16 00:04:26 +01:00
2019-11-14 04:00:27 +01:00
def save ( self , * args , * * kwargs ) :
super ( ) . save ( * args , * * kwargs )
2019-11-08 00:31:19 +01:00
# *Node.save() aktualizuje název *Nodu.
2019-11-20 20:45:32 +01:00
try :
self . pohadkanode . save ( )
2019-11-20 20:51:17 +01:00
except ObjectDoesNotExist :
2019-11-20 20:45:32 +01:00
# Neexistující *Node nemá smysl aktualizovat.
pass
2016-02-16 00:04:26 +01:00
2016-01-08 13:01:03 +01:00
2018-08-20 22:30:16 +02:00
@reversion.register ( ignore_duplicates = True )
2015-03-14 15:41:29 +01:00
class Nastaveni ( SingletonModel ) :
2019-03-26 21:14:10 +01:00
class Meta :
db_table = ' seminar_nastaveni '
2019-04-16 21:13:36 +02:00
verbose_name = ' Nastavení semináře '
2015-03-14 15:41:29 +01:00
2019-11-21 18:36:51 +01:00
# aktualni_rocnik = models.ForeignKey(Rocnik, verbose_name='aktuální ročník',
# null=False, on_delete=models.PROTECT)
2015-03-14 15:41:29 +01:00
2021-12-13 22:52:44 +01:00
aktualni_cislo = models . ForeignKey ( Cislo , verbose_name = ' Aktuální číslo ' ,
2019-06-10 23:55:45 +02:00
null = False , on_delete = models . PROTECT )
2015-03-14 15:41:29 +01:00
2022-11-14 23:08:02 +01:00
cena_sous = models . IntegerField ( null = False ,
verbose_name = " Účastnický poplatek za soustředění " ,
default = 1000 )
2019-12-19 01:29:53 +01:00
@property
2019-11-21 18:36:51 +01:00
def aktualni_rocnik ( self ) :
return self . aktualni_cislo . rocnik
2019-03-26 21:14:10 +01:00
def __str__ ( self ) :
2019-04-16 21:13:36 +02:00
return ' Nastavení semináře '
2015-03-14 15:41:29 +01:00
2019-03-26 21:14:10 +01:00
def admin_url ( self ) :
return reverse ( ' admin:seminar_nastaveni_change ' , args = ( self . id , ) )
def verejne ( self ) :
return False