mamweb/seminar/models/models_all.py

927 lines
28 KiB
Python
Raw Normal View History

2014-11-17 12:05:46 +01:00
# -*- coding: utf-8 -*-
2015-03-13 20:08:18 +01:00
import os
import subprocess
import pathlib
import tempfile
2021-09-05 16:01:05 +02:00
import logging
2015-03-13 20:08:18 +01:00
from django.contrib.sites.shortcuts import get_current_site
2014-11-17 12:05:46 +01:00
from django.db import models
from django.utils import timezone
from django.conf import settings
2021-10-08 18:06:02 +02:00
from django.urls import reverse
from django.core.cache import cache
from django.core.exceptions import ObjectDoesNotExist, ValidationError
2020-05-06 23:13:52 +02:00
from django.contrib.contenttypes.models import ContentType
from django.utils.text import get_valid_filename
2021-10-08 18:06:02 +02:00
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFit
from django.utils.functional import cached_property
from solo.models import SingletonModel
2015-05-15 15:28:00 +02:00
from taggit.managers import TaggableManager
from reversion import revisions as reversion
2019-12-05 00:50:04 +01:00
from seminar.utils import roman, FirstTagParser # Pro získání úryvku z TextNode
from seminar.utils import hlavni_problem
from seminar import treelib
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é)
from polymorphic.models import PolymorphicModel
2020-07-02 11:07:45 +02:00
from django.core.mail import EmailMessage
from seminar.utils import aktivniResitele
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
2021-10-08 19:05:11 +02:00
from .pomocne import Text
2021-10-08 18:06:02 +02:00
logger = logging.getLogger(__name__)
2014-11-17 12:05:46 +01:00
2018-08-20 22:30:16 +02:00
@reversion.register(ignore_duplicates=True)
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,
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):
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):
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)'
2019-03-26 21:14:10 +01:00
def verejna_cisla(self):
vc = [c for c in self.cisla.all() if c.verejne()]
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):
vc = list(self.cisla.filter(verejna_vysledkovka=True))
vc.sort(key=lambda c: c.poradi)
2019-03-26 21:14:10 +01:00
return vc
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
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})
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
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
# *Node.save() aktualizuje název *Nodu.
try:
self.rocniknode.save()
2019-11-20 20:51:17 +01:00
except ObjectDoesNotExist:
# Neexistující *Node nemá smysl aktualizovat.
pass
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)
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))
2018-08-20 22:30:16 +02:00
@reversion.register(ignore_duplicates=True)
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'
ordering = ['-rocnik__rocnik', '-poradi']
2019-03-26 21:14:10 +01:00
# 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)
2019-03-26 21:14:10 +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
datum_deadline_soustredeni = models.DateField(
2019-04-16 21:13:36 +02:00
'datum deadline soustředění',
2019-03-26 21:14:10 +01:00
blank=True, null=True,
2019-04-16 21:13:36 +02:00
help_text='Datum pro příjem řešení pro účast na soustředění')
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 = models.DateField('datum deadline', blank=True, null=True,
help_text='Datum pro příjem řešení úloh zadaných v tomto čísle')
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
verejna_vysledkovka = models.BooleanField(
2019-04-16 21:13:36 +02:00
'zveřejněna výsledkovka',
2019-03-26 21:14:10 +01:00
default=False,
help_text='Je-li false u veřejného čísla, '
'není výsledkovka zatím veřejná.')
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,
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')
2019-03-26 21:14:10 +01:00
# má OneToOneField s:
# CisloNode
2019-03-26 21:14:10 +01:00
def kod(self):
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)
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):
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):
"Vrací None, pokud je toto poslední"
2019-03-26 21:14:10 +01:00
return self.relativni_v_rocniku(1)
def predchozi(self):
"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):
"Číslo o `index` dále v ročníku. None pokud neexistuje."
2019-03-26 21:14:10 +01:00
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.run([
2021-04-06 23:11:22 +02:00
"gs",
"-sstdout=%stderr",
"-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
],
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()
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
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'
predmet = 'Vyšlo číslo {}'.format(self.kod())
text_mailu = 'Ahoj,\n' \
'na adrese {} najdete nejnovější číslo.\n' \
'Vaše M&M\n'.format(odkaz)
# Prijemci e-mailu
emaily = map(lambda r: r.osoba.email, filter(lambda r: r.zasilat_cislo_emailem, aktivniResitele(self)))
2020-07-02 11:07:45 +02:00
if not settings.POSLI_MAILOVOU_NOTIFIKACI:
print("Poslal bych upozornění na tyto adresy: ", " ".join(emaily))
return
email = EmailMessage(
subject=predmet,
body=text_mailu,
from_email=poslat_z_mailu,
bcc=list(emaily)
#bcc = příjemci skryté kopie
)
email.send()
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
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()
# *Node.save() aktualizuje název *Nodu.
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…')
CisloNode.objects.create(cislo=self)
def clean(self):
# Finální deadline má být až poslední a je povinný, pokud nějaký deadline existuje.
# Existence:
if self.datum_deadline is None and (self.datum_preddeadline is not None or self.datum_deadline_soustredeni is not None):
raise ValidationError({'datum_deadline': "Číslo musí mít finální deadline, pokud má nějaké deadliny"})
if self.datum_deadline is not None:
if self.datum_preddeadline is not None and self.datum_preddeadline > self.datum_deadline:
raise ValidationError({'datum_preddeadline': "Předdeadline musí předcházet finálnímu deadlinu"})
if self.datum_deadline_soustredeni is not None and self.datum_deadline_soustredeni > self.datum_deadline:
raise ValidationError({'datum_deadline_soustredeni': "Soustřeďkový deadline musí předcházet finálnímu deadlinu"})
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:
# 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
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
nadproblem = models.ForeignKey('self', verbose_name='nadřazený problém',
related_name='podproblem', null=True, blank=True,
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)
# 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',
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',
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):
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
@cached_property
2019-03-26 21:14:10 +01:00
def kod_v_rocniku(self):
if self.stav == 'zadany':
2019-03-27 01:01:57 +01:00
if self.nadproblem:
return self.nadproblem.kod_v_rocniku+".{}".format(self.kod)
2019-06-20 22:49:21 +02:00
return str(self.kod)
2019-04-16 21:13:36 +02:00
return '<Není zadaný>'
2019-03-26 21:14:10 +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):
return reverse('admin:seminar_problem_change', args=(self.id, ))
2019-03-26 21:14:10 +01:00
def hlavni_problem(self):
""" Pro daný problém vrátí jeho nejvyšší nadproblém."""
return hlavni_problem(self)
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
return "({}\u2009b)".format(pocet_bodu) if self.body else ""
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,
on_delete=models.PROTECT)
2019-03-27 01:01:57 +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)
@cached_property
2019-03-27 01:01:57 +01:00
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)
2019-04-16 21:13:36 +02:00
return '<Není zadaný>'
2019-03-27 01:01:57 +01:00
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
# *Node.save() aktualizuje název *Nodu.
2019-11-20 21:26:33 +01:00
for tvcn in self.temavcislenode_set.all():
tvcn.save()
def cislo_node(self):
tema_node_set = self.temavcislenode_set.all()
tema_cisla_vyskyt = []
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')
@cached_property
2019-03-27 01:01:57 +01:00
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)
2019-04-16 21:13:36 +02:00
return '<Není zadaný>'
def node(self):
return None
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,
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,
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',
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
# má OneToOneField s:
# UlohaZadaniNode
# UlohaVzorakNode
@cached_property
2019-03-27 01:01:57 +01:00
def kod_v_rocniku(self):
if self.stav == 'zadany':
name="{}.u{}".format(self.cislo_zadani.poradi,self.kod)
2019-03-27 01:01:57 +01:00
if self.nadproblem:
return self.nadproblem.kod_v_rocniku+name
return name
2019-04-16 21:13:36 +02:00
return '<Není zadaný>'
2019-03-27 01:01:57 +01:00
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
# *Node.save() aktualizuje název *Nodu.
try:
self.ulohazadaninode.save()
2019-11-20 20:51:17 +01:00
except ObjectDoesNotExist:
# Neexistující *Node nemá smysl aktualizovat.
pass
try:
self.ulohavzoraknode.save()
2019-11-20 20:51:17 +01:00
except ObjectDoesNotExist:
# Neexistující *Node nemá smysl aktualizovat.
pass
def cislo_node(self):
zadani_node = self.ulohazadaninode
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-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,
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
# 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)]+"..."
return uryvek
2016-02-16 00:04:26 +01:00
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
# *Node.save() aktualizuje název *Nodu.
try:
self.pohadkanode.save()
2019-11-20 20:51:17 +01:00
except ObjectDoesNotExist:
# Neexistující *Node nemá smysl aktualizovat.
pass
2016-02-16 00:04:26 +01:00
class TreeNode(PolymorphicModel):
2019-04-23 21:32:39 +02:00
class Meta:
2019-05-24 00:44:45 +02:00
db_table = "seminar_nodes_treenode"
verbose_name = "TreeNode"
verbose_name_plural = "TreeNody"
2019-10-22 21:39:34 +02:00
# TODO: Nechceme radši jako root vyžadovat přímo RocnikNode?
2019-05-24 00:44:45 +02:00
root = models.ForeignKey('TreeNode',
2019-05-01 00:46:59 +02:00
related_name="potomci_set",
2019-04-23 21:32:39 +02:00
null = True,
blank = False,
on_delete = models.SET_NULL, # Vrcholy s null kořenem jsou sirotci bez ročníku
2019-04-23 22:25:18 +02:00
verbose_name="kořen stromu")
first_child = models.OneToOneField('TreeNode',
related_name='father_of_first',
2019-04-23 21:32:39 +02:00
null = True,
blank = True,
on_delete=models.SET_NULL,
verbose_name="první potomek")
2019-05-24 00:44:45 +02:00
succ = models.OneToOneField('TreeNode',
2019-05-01 00:46:59 +02:00
related_name="prev",
2019-04-23 21:32:39 +02:00
null = True,
blank = True,
on_delete=models.SET_NULL,
verbose_name="další element na stejné úrovni")
nazev = models.TextField("název tohoto node",
2019-12-05 00:50:04 +01:00
help_text = "Tento název se zobrazuje v nabídkách pro výběr vhodného TreeNode",
blank=False,
2019-12-11 23:35:27 +01:00
null=True) # Nezveřejnitelný název na stránky - pouze do adminu
2019-12-05 00:50:04 +01:00
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")
2019-12-11 23:35:27 +01:00
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):
2019-10-22 21:39:34 +02:00
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)
2019-04-23 21:32:39 +02:00
def aktualizuj_nazev(self):
raise NotImplementedError("Pokus o aktualizaci názvu obecného TreeNode místo konkrétní instance")
2020-05-06 23:13:52 +02:00
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,))
2019-04-23 21:32:39 +02:00
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,
2019-04-23 21:32:39 +02:00
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)
2019-04-23 21:32:39 +02:00
class CisloNode(TreeNode):
class Meta:
db_table = 'seminar_nodes_cislo'
verbose_name = 'Číslo (Node)'
verbose_name_plural = 'Čísla (Node)'
cislo = models.OneToOneField(Cislo,
2019-04-23 21:32:39 +02:00
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)
2019-04-23 21:32:39 +02:00
2019-12-11 23:35:27 +01:00
def getOdkazStr(self):
2019-12-05 00:50:04 +01:00
return "Číslo " + str(self.cislo)
2019-04-23 21:32:39 +02:00
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!"
2019-12-11 23:35:27 +01:00
def getOdkazStr(self):
2019-12-05 00:50:04 +01:00
return "Obsah dostupný pouze na webu"
2019-04-23 21:32:39 +02:00
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)
2019-04-23 21:32:39 +02:00
2019-12-11 23:35:27 +01:00
def getOdkazStr(self):
2019-12-05 00:50:04 +01:00
return str(self.tema)
class OrgTextNode(TreeNode):
2019-04-23 21:32:39 +02:00
class Meta:
db_table = 'seminar_nodes_orgtextnode'
verbose_name = 'Organizátorský článek (Node)'
verbose_name_plural = 'Organizátorské články (Node)'
2021-10-08 18:06:02 +02:00
organizator = models.ForeignKey(pm.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,
)
2019-04-23 21:32:39 +02:00
def aktualizuj_nazev(self):
return f"OrgTextNode začínající následujícim: {self.first_child.nazev}"
2019-04-23 21:32:39 +02:00
# FIXME!!!
#def getOdkazStr(self):
# return str(self.clanek)
2019-12-05 00:50:04 +01:00
2019-05-24 00:13:35 +02:00
class UlohaZadaniNode(TreeNode):
2019-04-23 21:32:39 +02:00
class Meta:
2019-05-24 00:13:35 +02:00
db_table = 'seminar_nodes_uloha_zadani'
verbose_name = 'Zadání úlohy (Node)'
verbose_name_plural = 'Zadání úloh (Node)'
uloha = models.OneToOneField(Uloha,
2019-05-24 00:13:35 +02:00
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)
2019-04-23 21:32:39 +02:00
def aktualizuj_nazev(self):
self.nazev = "UlohaZadaniNode: "+str(self.uloha)
2019-04-23 21:32:39 +02:00
2019-12-11 23:35:27 +01:00
def getOdkazStr(self):
2019-12-05 00:50:04 +01:00
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)'
2019-05-30 01:41:11 +02:00
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",
2019-05-30 01:41:11 +02:00
)
def aktualizuj_nazev(self):
self.nazev = "PohadkaNode: "+str(self.pohadka)
2019-05-24 00:13:35 +02:00
class UlohaVzorakNode(TreeNode):
2019-04-23 21:32:39 +02:00
class Meta:
2019-05-24 00:13:35 +02:00
db_table = 'seminar_nodes_uloha_vzorak'
verbose_name = 'Vzorák úlohy (Node)'
verbose_name_plural = 'Vzoráky úloh (Node)'
uloha = models.OneToOneField(Uloha,
2019-05-24 00:13:35 +02:00
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)
2019-04-23 21:32:39 +02:00
def aktualizuj_nazev(self):
self.nazev = "UlohaVzorakNode: "+str(self.uloha)
2019-04-23 21:32:39 +02:00
2019-12-11 23:35:27 +01:00
def getOdkazStr(self):
2019-12-05 00:50:04 +01:00
return str(self.uloha)
2019-04-23 21:32:39 +02:00
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.CASCADE,
2019-04-23 21:32:39 +02:00
verbose_name = 'text')
def aktualizuj_nazev(self):
self.nazev = "TextNode: "+str(self.text)
2019-12-11 23:35:27 +01:00
def getOdkazStr(self):
2019-12-05 00:50:04 +01:00
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)
2018-08-20 22:30:16 +02:00
@reversion.register(ignore_duplicates=True)
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'
# aktualni_rocnik = models.ForeignKey(Rocnik, verbose_name='aktuální ročník',
# null=False, on_delete=models.PROTECT)
2019-04-16 21:13:36 +02:00
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
2019-03-26 21:14:10 +01:00
def __str__(self):
2019-04-16 21:13:36 +02:00
return 'Nastavení semináře'
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
2015-06-23 10:50:58 +02:00
2018-08-20 22:30:16 +02:00
@reversion.register(ignore_duplicates=True)
2015-06-23 10:50:58 +02:00
class Novinky(models.Model):
class Meta:
verbose_name = 'Novinka'
verbose_name_plural = 'Novinky'
ordering = ['-datum']
2019-03-26 21:14:10 +01:00
datum = models.DateField(auto_now_add=True)
2019-03-27 01:01:57 +01:00
2019-03-26 21:14:10 +01:00
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)
2019-03-27 01:01:57 +01:00
2019-03-26 21:14:10 +01:00
obrazek_maly = ImageSpecField(source='obrazek',
processors=[
ResizeToFit(350, 200, upscale=False)
],
options={'quality': 95})
2021-10-08 18:06:02 +02:00
autor = models.ForeignKey(pm.Organizator, verbose_name='Autor novinky', null=True,
on_delete=models.SET_NULL)
2019-03-27 01:01:57 +01:00
zverejneno = models.BooleanField('Zveřejněno', default=False)
2019-03-26 21:14:10 +01:00
def __str__(self):
2019-07-25 23:09:31 +02:00
if self.text:
return '[' + str(self.datum) + '] ' + self.text[0:50]
else:
return '[' + str(self.datum) + '] '