Browse Source

odstřel tvorby: relink – post

pull/66/head
Pavel 'LEdoian' Turinsky 2 weeks ago
parent
commit
062f70e947
  1. 62
      deploy_v2/admin_org_prava.json
  2. 14
      odevzdavatko/migrations/0006_tvorba_post.py
  3. 14
      personalni/migrations/0015_tvorba_post.py
  4. 150
      seminar/migrations/0138_tvorba_delete.py
  5. 14
      seminar/migrations/0139_tvorba_post.py
  6. 6
      seminar/models/__init__.py
  7. 2
      seminar/models/treenode.py
  8. 731
      seminar/models/tvorba.py
  9. 13
      soustredeni/migrations/0005_tvorba_relink.py
  10. 17
      soustredeni/migrations/0006_tvorba_relink2.py
  11. 15
      soustredeni/migrations/0007_tvorba_relink3.py
  12. 34
      soustredeni/migrations/0008_tvorba_relink4.py
  13. 17
      soustredeni/migrations/0009_tvorba_relink5.py
  14. 14
      soustredeni/migrations/0010_tvorba_post.py
  15. 54
      tvorba/migrations/0002_tvorba_manage.py
  16. 13
      tvorba/migrations/0003_tvorba_post.py
  17. 13
      tvorba/models.py
  18. 14
      various/migrations/0006_tvorba_post.py

62
deploy_v2/admin_org_prava.json

@ -216,57 +216,57 @@
},
{
"codename": "add_cislo",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "cislo"
},
{
"codename": "change_cislo",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "cislo"
},
{
"codename": "delete_cislo",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "cislo"
},
{
"codename": "view_cislo",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "cislo"
},
{
"codename": "add_clanek",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "clanek"
},
{
"codename": "change_clanek",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "clanek"
},
{
"codename": "delete_clanek",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "clanek"
},
{
"codename": "view_clanek",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "clanek"
},
{
"codename": "add_deadline",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "deadline"
},
{
"codename": "change_deadline",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "deadline"
},
{
"codename": "view_deadline",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "deadline"
},
{
@ -371,22 +371,22 @@
},
{
"codename": "add_pohadka",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "pohadka"
},
{
"codename": "change_pohadka",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "pohadka"
},
{
"codename": "delete_pohadka",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "pohadka"
},
{
"codename": "view_pohadka",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "pohadka"
},
{
@ -411,22 +411,22 @@
},
{
"codename": "add_problem",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "problem"
},
{
"codename": "change_problem",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "problem"
},
{
"codename": "delete_problem",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "problem"
},
{
"codename": "view_problem",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "problem"
},
{
@ -441,22 +441,22 @@
},
{
"codename": "add_rocnik",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "rocnik"
},
{
"codename": "change_rocnik",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "rocnik"
},
{
"codename": "delete_rocnik",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "rocnik"
},
{
"codename": "view_rocnik",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "rocnik"
},
{
@ -541,42 +541,42 @@
},
{
"codename": "add_tema",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "tema"
},
{
"codename": "change_tema",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "tema"
},
{
"codename": "delete_tema",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "tema"
},
{
"codename": "view_tema",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "tema"
},
{
"codename": "add_uloha",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "uloha"
},
{
"codename": "change_uloha",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "uloha"
},
{
"codename": "delete_uloha",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "uloha"
},
{
"codename": "view_uloha",
"ct_app_label": "seminar",
"ct_app_label": "tvorba",
"ct_model": "uloha"
},
{

14
odevzdavatko/migrations/0006_tvorba_post.py

@ -0,0 +1,14 @@
# Generated by Django 4.2.16 on 2024-10-30 21:34
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('odevzdavatko', '0005_tvorba_relink'),
('tvorba', '0003_tvorba_post'),
]
operations = [
]

14
personalni/migrations/0015_tvorba_post.py

@ -0,0 +1,14 @@
# Generated by Django 4.2.16 on 2024-10-30 21:35
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('personalni', '0014_tvorba_pre'),
('tvorba', '0003_tvorba_post'),
]
operations = [
]

150
seminar/migrations/0138_tvorba_delete.py

@ -0,0 +1,150 @@
# Generated by Django 4.2.16 on 2024-10-30 14:03
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('tvorba', '0001_tvorba_create'),
('seminar', '0137_tvorba_unmanage'),
('odevzdavatko', '0005_tvorba_relink'),
('soustredeni', '0009_tvorba_relink5'),
('various', '0005_tvorba_relink'),
]
operations = [
migrations.RemoveField(
model_name='cislo',
name='rocnik',
),
migrations.RemoveField(
model_name='clanek',
name='cislo',
),
migrations.RemoveField(
model_name='clanek',
name='problem_ptr',
),
migrations.RemoveField(
model_name='deadline',
name='cislo',
),
migrations.RemoveField(
model_name='pohadka',
name='autor',
),
migrations.RemoveField(
model_name='problem',
name='autor',
),
migrations.RemoveField(
model_name='problem',
name='garant',
),
migrations.RemoveField(
model_name='problem',
name='nadproblem',
),
migrations.RemoveField(
model_name='problem',
name='opravovatele',
),
migrations.RemoveField(
model_name='problem',
name='polymorphic_ctype',
),
migrations.RemoveField(
model_name='problem',
name='zamereni',
),
migrations.DeleteModel(
name='Problemy_Opravovatele',
),
migrations.RemoveField(
model_name='tema',
name='problem_ptr',
),
migrations.RemoveField(
model_name='tema',
name='rocnik',
),
migrations.RemoveField(
model_name='uloha',
name='cislo_deadline',
),
migrations.RemoveField(
model_name='uloha',
name='cislo_reseni',
),
migrations.RemoveField(
model_name='uloha',
name='cislo_zadani',
),
migrations.RemoveField(
model_name='uloha',
name='problem_ptr',
),
migrations.RemoveField(
model_name='zmrazenavysledkovka',
name='deadline',
),
migrations.AlterField(
model_name='cislonode',
name='cislo',
field=models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to='tvorba.cislo', verbose_name='číslo'),
),
migrations.AlterField(
model_name='pohadkanode',
name='pohadka',
field=models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to='tvorba.pohadka', verbose_name='pohádka'),
),
migrations.AlterField(
model_name='rocniknode',
name='rocnik',
field=models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to='tvorba.rocnik', verbose_name='ročník'),
),
migrations.AlterField(
model_name='temavcislenode',
name='tema',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='tvorba.tema', verbose_name='téma v čísle'),
),
migrations.AlterField(
model_name='ulohavzoraknode',
name='uloha',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.PROTECT, to='tvorba.uloha', verbose_name='úloha'),
),
migrations.AlterField(
model_name='ulohazadaninode',
name='uloha',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.PROTECT, to='tvorba.uloha', verbose_name='úloha'),
),
migrations.DeleteModel(
name='Cislo',
),
migrations.DeleteModel(
name='Clanek',
),
migrations.DeleteModel(
name='Deadline',
),
migrations.DeleteModel(
name='Pohadka',
),
migrations.DeleteModel(
name='Problem',
),
migrations.DeleteModel(
name='Rocnik',
),
migrations.DeleteModel(
name='Tema',
),
migrations.DeleteModel(
name='Uloha',
),
migrations.DeleteModel(
name='ZmrazenaVysledkovka',
),
]

14
seminar/migrations/0139_tvorba_post.py

@ -0,0 +1,14 @@
# Generated by Django 4.2.16 on 2024-10-30 21:35
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('seminar', '0138_tvorba_delete'),
('tvorba', '0003_tvorba_post'),
]
operations = [
]

6
seminar/models/__init__.py

@ -15,3 +15,9 @@ from tvorba.models import ZmrazenaVysledkovka, Deadline, Cislo, Rocnik, Pohadka,
from soustredeni.models import generate_filename_konfera
# migr. 0001
from odevzdavatko.models import generate_filename
# migr. 0031, 0032, 0081
from tvorba.models import cislo_pdf_filename
# migr. 0082
from tvorba.models import cislo_png_filename
# migr 0100 (hack)
import tvorba.models as tvorba

2
seminar/models/treenode.py

@ -14,7 +14,7 @@ from .pomocne import Text
logger = logging.getLogger(__name__)
from seminar.models import tvorba as am
import tvorba.models as am
class TreeNode(PolymorphicModel):
class Meta:

731
seminar/models/tvorba.py

@ -1,40 +1,7 @@
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 tvorba.utils import roman, aktivniResitele
from treenode import treelib
from unidecode import unidecode # Používám pro získání ID odkazu (ještě je to někde po někom zakomentované)
from polymorphic.models import PolymorphicModel
from django.core.mail import EmailMessage
from personalni.models import Prijemce, Organizator
from .base import SeminarModelBase
logger = logging.getLogger(__name__)
@ -45,701 +12,3 @@ class OverwriteStorage(FileSystemStorage):
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']
managed = False
# 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']
managed = False
# Interní ID
id = models.AutoField(primary_key = True)
rocnik = models.ForeignKey(Rocnik, verbose_name='ročník', related_name='cisla_old',
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=%stderr",
"-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 = "---\nK odběru těchto e-mailů jste se přihlásili na stránkách https://mam.matfyz.cz. Z odběru se lze odhlásit na https://mam.matfyz.cz/resitel/osobni-udaje/"
posli(predmet_resitel, text_mailu_resitel + paticka, resitele_vsichni.filter(zasilat_cislo_papirove=False))
posli(predmet_resitel, text_mailu_resitel + 'P. S. Brzy budeme též rozesílat papírovou verzi čísla. Připomínáme, že pokud papírovou verzi čísla nevyužijete, můžete v https://mam.mff.cuni.cz/resitel/osobni-udaje/ zaškrtnout, abychom vám ji neposílali. Čísla vždy můžete nalézt v našem archivu a dál vám budou chodit e-mailem. Děkujeme.\n' + paticka,
resitele_vsichni.filter(zasilat_cislo_papirove=True))
paticka_prijemce = "---\nPokud tyto e-maily nechcete nadále dostávat, prosíme, ozvěte se nám na mam@matfyz.cz."
posli(predmet, text_mailu + paticka_prijemce, Prijemce.objects.filter(zasilat_cislo_emailem=True))
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
self.vygeneruj_nahled()
# Při zveřejnění pošle mail
if self.verejne_db and not self.__original_verejne:
self.posli_cislo_mailem()
# *Node.save() aktualizuje název *Nodu.
try:
self.cislonode.save()
except ObjectDoesNotExist:
# Neexistující *Node nemá smysl aktualizovat, ale je potřeba ho naopak vyrobit
logger.warning(f'Číslo {self} nemělo ČísloNode, vyrábím…')
from 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']
managed = False
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_old', 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'
managed = False
deadline = models.OneToOneField(
Deadline,
on_delete=models.CASCADE,
primary_key=True,
related_name="vysledkovka_v_deadlinu_old"
)
html = models.TextField(null=False, blank=False)
class Problemy_Opravovatele(SeminarModelBase):
"""Jen vazebná tabulka pro opravovatele.
Ona stejně existovala, při přesunu mezi aplikacemi jen potřebujeme zajistit nepřejmenování DB tabulky.
Proto taky nepotřebuje žádná specifika, ze :py:class:SeminarModelBase: dědí ze zvyku než že by to k něčemu kdy měo být.
"""
class Meta:
db_table = 'seminar_problemy_opravovatele'
managed = False
id = models.AutoField(primary_key = True)
problem = models.ForeignKey('Problem', on_delete=models.CASCADE, related_name='awawa1_old')
organizator = models.ForeignKey(Organizator, on_delete=models.CASCADE, related_name='awawa2_old')
@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']
managed = False
# 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_old', 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í', related_name='zamereni_old',
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_old', 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_old', null=True, blank=True,
on_delete=models.SET_NULL)
opravovatele = models.ManyToManyField(Organizator, verbose_name='opravovatelé',
blank=True, related_name='opravovatele_%(class)s_old', through=Problemy_Opravovatele)
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 "({}\u2009b)".format(pocet_bodu) if self.body else ""
class Tema(Problem):
class Meta:
db_table = 'seminar_temata'
verbose_name = 'Téma'
verbose_name_plural = 'Témata'
managed = False
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_old',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'
managed = False
cislo = models.ForeignKey(Cislo, blank=True, null=True, on_delete=models.PROTECT,
verbose_name='číslo vydání', related_name='vydane_clanky_old')
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'
managed = False
cislo_zadani = models.ForeignKey(Cislo, verbose_name='číslo zadání', blank=True,
null=True, related_name='zadane_ulohy_old', on_delete=models.PROTECT)
cislo_deadline = models.ForeignKey(Cislo, verbose_name='číslo deadlinu', blank=True,
null=True, related_name='deadlinove_ulohy_old', on_delete=models.PROTECT)
cislo_reseni = models.ForeignKey(Cislo, verbose_name='číslo řešení', blank=True,
null=True, related_name='resene_ulohy_old',
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']
managed = False
# 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,
related_name='awawa3_old',
)
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

13
soustredeni/migrations/0005_tvorba_relink.py

@ -12,11 +12,14 @@ class Migration(migrations.Migration):
]
operations = [
migrations.AlterField(
model_name='konfera',
name='problem_ptr',
field=models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tvorba.problem'),
),
## Konferu zmigrujeme jinak, kvůli <https://code.djangoproject.com/ticket/23521> jí nejde přepsat někde ve stavu `bases`.
## Proto si ji unmanagujeme a vyrobíme celou znovu, to by nemělo vadit (zvlášť když t.č. v DB žádná instance Konfery není).
## (Šlo by `SeparateStateAndData`, což v principu děláme taky ale ty migrace jsou lehce čitelnější a o poznání konzistentnější.)
#migrations.AlterField(
# model_name='konfera',
# name='problem_ptr',
# field=models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tvorba.problem'),
#),
migrations.AlterField(
model_name='soustredeni',
name='rocnik',

17
soustredeni/migrations/0006_tvorba_relink2.py

@ -0,0 +1,17 @@
# Generated by Django 4.2.16 on 2024-10-30 19:37
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('soustredeni', '0005_tvorba_relink'),
]
operations = [
migrations.AlterModelOptions(
name='konfera',
options={'managed': False, 'verbose_name': 'Konfera', 'verbose_name_plural': 'Konfery'},
),
]

15
soustredeni/migrations/0007_tvorba_relink3.py

@ -0,0 +1,15 @@
# Generated by Django 4.2.16 on 2024-10-30 19:38
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('soustredeni', '0006_tvorba_relink2'),
]
operations = [
migrations.DeleteModel(
name='Konfera',
),
]

34
soustredeni/migrations/0008_tvorba_relink4.py

@ -0,0 +1,34 @@
# Generated by Django 4.2.16 on 2024-10-30 19:45
from django.db import migrations,models
import django.db.models.deletion
import soustredeni.models
class Migration(migrations.Migration):
dependencies = [
('soustredeni', '0007_tvorba_relink3'),
]
operations = [
migrations.CreateModel(
name='Konfera',
fields=[
('problem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tvorba.problem')),
('anotace', models.TextField(blank=True, help_text='Popis, o čem bude konfera.', verbose_name='anotace')),
('abstrakt', models.TextField(blank=True, help_text='Abstrakt konfery tak, jak byl uveden ve sborníku', verbose_name='abstrakt')),
('typ_prezentace', models.CharField(choices=[('veletrh', 'Veletrh (postery)'), ('prezentace', 'Prezentace (přednáška)')], default='veletrh', max_length=16, verbose_name='typ prezentace')),
('prezentace', models.FileField(blank=True, help_text='Prezentace nebo fotka posteru', upload_to=soustredeni.models.generate_filename_konfera, verbose_name='prezentace')),
('materialy', models.FileField(blank=True, help_text='Další materiály ke konfeře zabalené do jednoho souboru', upload_to=soustredeni.models.generate_filename_konfera, verbose_name='materialy')),
('soustredeni', models.ForeignKey(to='soustredeni.soustredeni', verbose_name='soustředění', on_delete=models.SET_NULL, null=True, related_name='konfery')),
('ucastnici', models.ManyToManyField(help_text='Seznam účastníků konfery', through='soustredeni.Konfery_Ucastnici', to='personalni.resitel', verbose_name='účastníci konfery')),
],
options={
'verbose_name': 'Konfera',
'verbose_name_plural': 'Konfery',
'db_table': 'seminar_konfera',
'managed': False,
},
bases=('tvorba.problem',),
),
]

17
soustredeni/migrations/0009_tvorba_relink5.py

@ -0,0 +1,17 @@
# Generated by Django 4.2.16 on 2024-10-30 20:03
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('soustredeni', '0008_tvorba_relink4'),
]
operations = [
migrations.AlterModelOptions(
name='konfera',
options={'verbose_name': 'Konfera', 'verbose_name_plural': 'Konfery'},
),
]

14
soustredeni/migrations/0010_tvorba_post.py

@ -0,0 +1,14 @@
# Generated by Django 4.2.16 on 2024-10-30 21:35
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('soustredeni', '0009_tvorba_relink5'),
('tvorba', '0003_tvorba_post'),
]
operations = [
]

54
tvorba/migrations/0002_tvorba_manage.py

@ -0,0 +1,54 @@
# Generated by Django 4.2.16 on 2024-10-30 21:29
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('tvorba', '0001_tvorba_create'),
('seminar', '0138_tvorba_delete'),
]
operations = [
migrations.AlterModelOptions(
name='cislo',
options={'ordering': ['-rocnik__rocnik', '-poradi'], 'verbose_name': 'Číslo', 'verbose_name_plural': 'Čísla'},
),
migrations.AlterModelOptions(
name='clanek',
options={'verbose_name': 'Článek', 'verbose_name_plural': 'Články'},
),
migrations.AlterModelOptions(
name='deadline',
options={'ordering': ['deadline'], 'verbose_name': 'Deadline', 'verbose_name_plural': 'Deadliny'},
),
migrations.AlterModelOptions(
name='pohadka',
options={'ordering': ['vytvoreno'], 'verbose_name': 'Pohádka', 'verbose_name_plural': 'Pohádky'},
),
migrations.AlterModelOptions(
name='problem',
options={'ordering': ['nazev'], 'verbose_name': 'Problém', 'verbose_name_plural': 'Problémy'},
),
migrations.AlterModelOptions(
name='problemy_opravovatele',
options={},
),
migrations.AlterModelOptions(
name='rocnik',
options={'ordering': ['-rocnik'], 'verbose_name': 'Ročník', 'verbose_name_plural': 'Ročníky'},
),
migrations.AlterModelOptions(
name='tema',
options={'verbose_name': 'Téma', 'verbose_name_plural': 'Témata'},
),
migrations.AlterModelOptions(
name='uloha',
options={'verbose_name': 'Úloha', 'verbose_name_plural': 'Úlohy'},
),
migrations.AlterModelOptions(
name='zmrazenavysledkovka',
options={'verbose_name': 'Zmražená výsledkovka', 'verbose_name_plural': 'Zmražené výsledkovky'},
),
]

13
tvorba/migrations/0003_tvorba_post.py

@ -0,0 +1,13 @@
# Generated by Django 4.2.16 on 2024-10-30 21:34
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('tvorba', '0002_tvorba_manage'),
]
operations = [
]

13
tvorba/models.py

@ -31,7 +31,8 @@ from polymorphic.models import PolymorphicModel
from django.core.mail import EmailMessage
from seminar.models import SeminarModelBase, OverwriteStorage
from seminar.models.base import SeminarModelBase
from seminar.models.tvorba import OverwriteStorage
from personalni.models import Prijemce, Organizator
logger = logging.getLogger(__name__)
@ -44,7 +45,6 @@ class Rocnik(SeminarModelBase):
verbose_name = 'Ročník'
verbose_name_plural = 'Ročníky'
ordering = ['-rocnik']
managed = False
# Interní ID
id = models.AutoField(primary_key = True)
@ -132,7 +132,6 @@ class Cislo(SeminarModelBase):
verbose_name = 'Číslo'
verbose_name_plural = 'Čísla'
ordering = ['-rocnik__rocnik', '-poradi']
managed = False
# Interní ID
id = models.AutoField(primary_key = True)
@ -321,7 +320,6 @@ class Deadline(SeminarModelBase):
verbose_name = 'Deadline'
verbose_name_plural = 'Deadliny'
ordering = ['deadline']
managed = False
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@ -384,7 +382,6 @@ class ZmrazenaVysledkovka(SeminarModelBase):
db_table = 'seminar_vysledkovky'
verbose_name = 'Zmražená výsledkovka'
verbose_name_plural = 'Zmražené výsledkovky'
managed = False
deadline = models.OneToOneField(
Deadline,
@ -403,7 +400,6 @@ class Problemy_Opravovatele(SeminarModelBase):
"""
class Meta:
db_table = 'seminar_problemy_opravovatele'
managed = False
id = models.AutoField(primary_key = True)
@ -425,7 +421,6 @@ class Problem(SeminarModelBase,PolymorphicModel):
verbose_name = 'Problém'
verbose_name_plural = 'Problémy'
ordering = ['nazev']
managed = False
# Interní ID
id = models.AutoField(primary_key = True)
@ -543,7 +538,6 @@ class Tema(Problem):
db_table = 'seminar_temata'
verbose_name = 'Téma'
verbose_name_plural = 'Témata'
managed = False
TEMA_TEMA = 'tema'
TEMA_SERIAL = 'serial'
@ -591,7 +585,6 @@ class Clanek(Problem):
db_table = 'seminar_clanky'
verbose_name = 'Článek'
verbose_name_plural = 'Články'
managed = False
cislo = models.ForeignKey(Cislo, blank=True, null=True, on_delete=models.PROTECT,
verbose_name='číslo vydání', related_name='vydane_clanky')
@ -617,7 +610,6 @@ class Uloha(Problem):
db_table = 'seminar_ulohy'
verbose_name = 'Úloha'
verbose_name_plural = 'Úlohy'
managed = False
cislo_zadani = models.ForeignKey(Cislo, verbose_name='číslo zadání', blank=True,
null=True, related_name='zadane_ulohy', on_delete=models.PROTECT)
@ -680,7 +672,6 @@ class Pohadka(SeminarModelBase):
verbose_name = 'Pohádka'
verbose_name_plural = 'Pohádky'
ordering = ['vytvoreno']
managed = False
# Interní ID
id = models.AutoField(primary_key=True)

14
various/migrations/0006_tvorba_post.py

@ -0,0 +1,14 @@
# Generated by Django 4.2.16 on 2024-10-30 21:35
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('various', '0005_tvorba_relink'),
('tvorba', '0003_tvorba_post'),
]
operations = [
]
Loading…
Cancel
Save