diff --git a/deploy_v2/admin_org_prava.json b/deploy_v2/admin_org_prava.json
index 370aec4f..c7fc8c7d 100644
--- a/deploy_v2/admin_org_prava.json
+++ b/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"
},
{
diff --git a/odevzdavatko/migrations/0004_tvorba_pre.py b/odevzdavatko/migrations/0004_tvorba_pre.py
new file mode 100644
index 00000000..a571c07e
--- /dev/null
+++ b/odevzdavatko/migrations/0004_tvorba_pre.py
@@ -0,0 +1,13 @@
+# Generated by Django 4.2.16 on 2024-10-30 01:06
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('odevzdavatko', '0003_odstrel_odevzdavatka_post'),
+ ]
+
+ operations = [
+ ]
diff --git a/odevzdavatko/migrations/0005_tvorba_relink.py b/odevzdavatko/migrations/0005_tvorba_relink.py
new file mode 100644
index 00000000..2d235410
--- /dev/null
+++ b/odevzdavatko/migrations/0005_tvorba_relink.py
@@ -0,0 +1,35 @@
+# Generated by Django 4.2.16 on 2024-10-30 13:18
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tvorba', '0001_tvorba_create'),
+ ('odevzdavatko', '0004_tvorba_pre'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='hodnoceni',
+ name='cislo_body',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='hodnoceni', to='tvorba.cislo', verbose_name='číslo pro body'),
+ ),
+ migrations.AlterField(
+ model_name='hodnoceni',
+ name='deadline_body',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='hodnoceni', to='tvorba.deadline', verbose_name='deadline pro body'),
+ ),
+ migrations.AlterField(
+ model_name='hodnoceni',
+ name='problem',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='hodnoceni', to='tvorba.problem', verbose_name='problém'),
+ ),
+ migrations.AlterField(
+ model_name='reseni',
+ name='problem',
+ field=models.ManyToManyField(help_text='Problém', through='odevzdavatko.Hodnoceni', to='tvorba.problem', verbose_name='problém'),
+ ),
+ ]
diff --git a/odevzdavatko/migrations/0006_tvorba_post.py b/odevzdavatko/migrations/0006_tvorba_post.py
new file mode 100644
index 00000000..13ab895c
--- /dev/null
+++ b/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 = [
+ ]
diff --git a/odevzdavatko/models.py b/odevzdavatko/models.py
index a5b6a7e1..a52f370f 100644
--- a/odevzdavatko/models.py
+++ b/odevzdavatko/models.py
@@ -9,7 +9,7 @@ from django.urls import reverse_lazy
from django.utils import timezone
from django.conf import settings
-import seminar.models as am # tvorba
+import tvorba.models as am
from seminar.models import base as bm
from odevzdavatko.utils import vzorecek_na_prepocet, inverze_vzorecku_na_prepocet
diff --git a/personalni/migrations/0014_tvorba_pre.py b/personalni/migrations/0014_tvorba_pre.py
new file mode 100644
index 00000000..2d976333
--- /dev/null
+++ b/personalni/migrations/0014_tvorba_pre.py
@@ -0,0 +1,13 @@
+# Generated by Django 4.2.16 on 2024-10-30 01:07
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('personalni', '0013_odstrel_odevzdavatka_post'),
+ ]
+
+ operations = [
+ ]
diff --git a/personalni/migrations/0015_tvorba_post.py b/personalni/migrations/0015_tvorba_post.py
new file mode 100644
index 00000000..6580b1e8
--- /dev/null
+++ b/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 = [
+ ]
diff --git a/personalni/templates/personalni/profil/orgorozcestnik.html b/personalni/templates/personalni/profil/orgorozcestnik.html
index 90d5867d..9d0bbdba 100644
--- a/personalni/templates/personalni/profil/orgorozcestnik.html
+++ b/personalni/templates/personalni/profil/orgorozcestnik.html
@@ -20,7 +20,7 @@
Tvorba čísla
- - přidat téma
+ - přidat téma
- korektury
- korekturování
diff --git a/seminar/migrations/0136_tvorba_pre.py b/seminar/migrations/0136_tvorba_pre.py
new file mode 100644
index 00000000..07f7d234
--- /dev/null
+++ b/seminar/migrations/0136_tvorba_pre.py
@@ -0,0 +1,21 @@
+# Generated by Django 4.2.16 on 2024-10-30 01:06
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('seminar', '0135_odstrel_odevzdavatka_post'),
+ ('odevzdavatko', '0004_tvorba_pre'),
+ ('various', '0004_tvorba_pre'),
+ ('soustredeni', '0004_tvorba_pre'),
+ ('personalni', '0014_tvorba_pre'),
+ # Polymorphic:
+ ('contenttypes', '0002_remove_content_type_name'),
+ # Taggit
+ ('taggit', '0006_rename_taggeditem_content_type_object_id_taggit_tagg_content_8fc721_idx'),
+ ]
+
+ operations = [
+ ]
diff --git a/seminar/migrations/0137_tvorba_unmanage.py b/seminar/migrations/0137_tvorba_unmanage.py
new file mode 100644
index 00000000..132f979b
--- /dev/null
+++ b/seminar/migrations/0137_tvorba_unmanage.py
@@ -0,0 +1,59 @@
+# Generated by Django 4.2.16 on 2024-10-30 11:19
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('seminar', '0136_tvorba_pre'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Problemy_Opravovatele',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ],
+ options={
+ 'db_table': 'seminar_problemy_opravovatele',
+ 'managed': False,
+ },
+ ),
+ migrations.AlterModelOptions(
+ name='cislo',
+ options={'managed': False, 'ordering': ['-rocnik__rocnik', '-poradi'], 'verbose_name': 'Číslo', 'verbose_name_plural': 'Čísla'},
+ ),
+ migrations.AlterModelOptions(
+ name='clanek',
+ options={'managed': False, 'verbose_name': 'Článek', 'verbose_name_plural': 'Články'},
+ ),
+ migrations.AlterModelOptions(
+ name='deadline',
+ options={'managed': False, 'ordering': ['deadline'], 'verbose_name': 'Deadline', 'verbose_name_plural': 'Deadliny'},
+ ),
+ migrations.AlterModelOptions(
+ name='pohadka',
+ options={'managed': False, 'ordering': ['vytvoreno'], 'verbose_name': 'Pohádka', 'verbose_name_plural': 'Pohádky'},
+ ),
+ migrations.AlterModelOptions(
+ name='problem',
+ options={'managed': False, 'ordering': ['nazev'], 'verbose_name': 'Problém', 'verbose_name_plural': 'Problémy'},
+ ),
+ migrations.AlterModelOptions(
+ name='rocnik',
+ options={'managed': False, 'ordering': ['-rocnik'], 'verbose_name': 'Ročník', 'verbose_name_plural': 'Ročníky'},
+ ),
+ migrations.AlterModelOptions(
+ name='tema',
+ options={'managed': False, 'verbose_name': 'Téma', 'verbose_name_plural': 'Témata'},
+ ),
+ migrations.AlterModelOptions(
+ name='uloha',
+ options={'managed': False, 'verbose_name': 'Úloha', 'verbose_name_plural': 'Úlohy'},
+ ),
+ migrations.AlterModelOptions(
+ name='zmrazenavysledkovka',
+ options={'managed': False, 'verbose_name': 'Zmražená výsledkovka', 'verbose_name_plural': 'Zmražené výsledkovky'},
+ ),
+ ]
diff --git a/seminar/migrations/0138_tvorba_delete.py b/seminar/migrations/0138_tvorba_delete.py
new file mode 100644
index 00000000..0448a160
--- /dev/null
+++ b/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',
+ ),
+ ]
diff --git a/seminar/migrations/0139_tvorba_post.py b/seminar/migrations/0139_tvorba_post.py
new file mode 100644
index 00000000..560ddde4
--- /dev/null
+++ b/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 = [
+ ]
diff --git a/seminar/models/__init__.py b/seminar/models/__init__.py
index 4bc85266..95e449ab 100644
--- a/seminar/models/__init__.py
+++ b/seminar/models/__init__.py
@@ -9,8 +9,15 @@ from personalni.models import Organizator, Resitel, Skola, Prijemce, Osoba
from soustredeni.models import Soustredeni, Soustredeni_Ucastnici, Soustredeni_Organizatori, Konfera, Konfery_Ucastnici
from novinky.models import Novinky
from odevzdavatko.models import Reseni, PrilohaReseni, Reseni_Resitele, Hodnoceni
+from tvorba.models import ZmrazenaVysledkovka, Deadline, Cislo, Rocnik, Pohadka, Tema, Problem, Problemy_Opravovatele, Uloha, Clanek
# Kvůli migr. 0041
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
diff --git a/seminar/models/treenode.py b/seminar/models/treenode.py
index abc20eab..eee40281 100644
--- a/seminar/models/treenode.py
+++ b/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:
diff --git a/seminar/models/tvorba.py b/seminar/models/tvorba.py
index c11a3861..c1f91196 100644
--- a/seminar/models/tvorba.py
+++ b/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,677 +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']
-
- # Interní ID
- id = models.AutoField(primary_key = True)
-
- prvni_rok = models.IntegerField('první rok', db_index=True, unique=True)
-
- rocnik = models.IntegerField('číslo ročníku', db_index=True, unique=True)
-
- exportovat = models.BooleanField('export do AESOPa', db_column='exportovat', default=False,
- help_text='Exportuje se jen podle tohoto flagu (ne veřejnosti),'
- ' a to jen čísla s veřejnou výsledkovkou')
-
- # má OneToOneField s:
- # RocnikNode
-
- def __str__(self):
- return '{} ({}/{})'.format(self.rocnik, self.prvni_rok, self.prvni_rok+1)
-
- # Ročník v římských číslech
- def roman(self):
- return roman(int(self.rocnik))
-
- def verejne(self):
- return len(self.verejna_cisla()) > 0
- verejne.boolean = True
- verejne.short_description = 'Veřejný (jen dle čísel)'
-
- def neverejna_cisla(self):
- vc = [c for c in self.cisla.all() if not c.verejne()]
- vc.sort(key=lambda c: c.poradi)
- return vc
-
- def verejna_cisla(self):
- vc = [c for c in self.cisla.all() if c.verejne()]
- vc.sort(key=lambda c: c.poradi)
- return vc
-
- def posledni_verejne_cislo(self):
- vc = self.verejna_cisla()
- return vc[-1] if vc else None
-
- def verejne_vysledkovky_cisla(self):
- vc = list(self.cisla.filter(deadline_v_cisle__verejna_vysledkovka=True).distinct())
- vc.sort(key=lambda c: c.poradi)
- return vc
-
- def posledni_zverejnena_vysledkovka_cislo(self):
- vc = self.verejne_vysledkovky_cisla()
- return vc[-1] if vc else None
-
- def druhy_rok(self):
- return self.prvni_rok + 1
-
- def verejne_url(self):
- return reverse('seminar_rocnik', kwargs={'rocnik': self.rocnik})
-
- @classmethod
- def cached_rocnik(cls, r_id):
- name = 'rocnik_%s' % (r_id, )
- c = cache.get(name)
- if c is None:
- c = cls.objects.get(id=r_id)
- cache.set(name, c, 300)
- return c
-
- def save(self, *args, **kwargs):
- super().save(*args, **kwargs)
- # *Node.save() aktualizuje název *Nodu.
- try:
- self.rocniknode.save()
- except ObjectDoesNotExist:
- # Neexistující *Node nemá smysl aktualizovat.
- pass
-
-def cislo_pdf_filename(self, filename):
- rocnik = str(self.rocnik.rocnik)
- return pathlib.Path('cislo', 'pdf', rocnik, '{}-{}.pdf'.format(rocnik, self.poradi))
-
-def cislo_png_filename(self, filename):
- rocnik = str(self.rocnik.rocnik)
- return pathlib.Path('cislo', 'png', rocnik, '{}-{}.png'.format(rocnik, self.poradi))
-
-@reversion.register(ignore_duplicates=True)
-class Cislo(SeminarModelBase):
-
- class Meta:
- db_table = 'seminar_cisla'
- verbose_name = 'Číslo'
- verbose_name_plural = 'Čísla'
- ordering = ['-rocnik__rocnik', '-poradi']
-
- # Interní ID
- id = models.AutoField(primary_key = True)
-
- rocnik = models.ForeignKey(Rocnik, verbose_name='ročník', related_name='cisla',
- db_index=True,on_delete=models.PROTECT)
-
- poradi = models.CharField('název čísla', max_length=32, db_index=True,
- help_text='Většinou jen "1", vyjímečně "7-8", lexikograficky určuje pořadí v ročníku!')
-
- datum_vydani = models.DateField('datum vydání', blank=True, null=True,
- help_text='Datum vydání finální verze')
-
- verejne_db = models.BooleanField('číslo zveřejněno',
- db_column='verejne', default=False)
-
- poznamka = models.TextField('neveřejná poznámka', blank=True,
- help_text='Neveřejná poznámka k číslu (plain text)')
-
- pdf = models.FileField('pdf', upload_to=cislo_pdf_filename, null=True, blank=True,
- help_text='PDF čísla, které si mohou řešitelé stáhnout', storage=OverwriteStorage())
-
- titulka_nahled = models.ImageField('Obrázek titulní strany', upload_to=cislo_png_filename, null=True, blank=True,
- help_text='Obrázek titulní strany, generuje se automaticky')
-
- # má OneToOneField s:
- # CisloNode
-
- def kod(self):
- return '%s.%s' % (self.rocnik.rocnik, self.poradi)
- kod.short_description = 'Kód čísla'
-
- def __str__(self):
- # Potenciální DB HOG, pokud by se ročník necachoval
- r = Rocnik.cached_rocnik(self.rocnik_id)
- return '{}.{}'.format(r.rocnik, self.poradi)
-
- def verejne(self):
- return self.verejne_db
- verejne.boolean = True
-
- def verejne_url(self):
- return reverse('seminar_cislo', kwargs={'rocnik': self.rocnik.rocnik, 'cislo': self.poradi})
-
- def absolute_url(self):
- return "https://" + str(get_current_site(None)) + self.verejne_url()
-
- def nasledujici(self):
- "Vrací None, pokud je toto poslední"
- return self.relativni_v_rocniku(1)
-
- def predchozi(self):
- "Vrací None, pokud je toto první"
- return self.relativni_v_rocniku(-1)
-
- def relativni_v_rocniku(self, rel_index):
- "Číslo o `index` dále v ročníku. None pokud neexistuje."
- cs = self.rocnik.cisla.order_by('poradi').all()
- i = list(cs).index(self) + rel_index
- if (i < 0) or (i >= len(cs)):
- return None
- return cs[i]
-
- def vygeneruj_nahled(self):
- VYSKA = 594
- sirka = int(VYSKA*210/297)
- if not self.pdf:
- return
-
-
- # Pokud obrázek neexistuje nebo není aktuální, vytvoř jej
- if not self.titulka_nahled or os.path.getmtime(self.titulka_nahled.path) < os.path.getmtime(self.pdf.path):
- png_filename = pathlib.Path(tempfile.mkdtemp(), 'nahled.png')
-
- subprocess.run([
- "gs",
- "-sstdout=%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']
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.__original_verejna_vysledkovka = self.verejna_vysledkovka
-
- id = models.AutoField(primary_key=True)
-
- # V ročníku < 26 nastaveno na datetime.datetime.combine(datetime.date(1994 + cislo.rocnik.rocnik, 6, int(cislo.poradi[0])), datetime.time.min)
- deadline = models.DateTimeField(blank=False, default=timezone.make_aware(datetime.datetime.combine(timezone.now(), datetime.time.max)))
-
- cislo = models.ForeignKey(Cislo, verbose_name='deadline v čísle',
- related_name='deadline_v_cisle', blank=False,
- on_delete=models.CASCADE)
-
- TYP_CISLA = 'cisla'
- TYP_PRVNI_A_SOUS = 'prvniasous'
- TYP_PRVNI = 'prvni'
- TYP_SOUS = 'sous'
- TYP_CHOICES = [
- (TYP_CISLA, 'Deadline celého čísla'),
- (TYP_PRVNI, 'První deadline'),
- (TYP_PRVNI_A_SOUS, 'Sousový a první deadline'),
- (TYP_SOUS, 'Sousový deadline'),
- ]
- CHOICES_MAP = dict(TYP_CHOICES)
- typ = models.CharField('typ deadlinu', max_length=32,
- choices=TYP_CHOICES, blank=False)
-
- verejna_vysledkovka = models.BooleanField('veřejná výsledkovka',
- db_column='verejna_vysledkovka',
- default=False)
-
- def __str__(self):
- return self.CHOICES_MAP[self.typ] + " " + str(self.cislo)
-
- def save(self, *args, **kwargs):
- super().save(*args, **kwargs)
- if self.verejna_vysledkovka and not self.__original_verejna_vysledkovka:
- self.vygeneruj_vysledkovku()
- if not self.verejna_vysledkovka and hasattr(self, "vysledkovka_v_deadlinu"):
- self.vysledkovka_v_deadlinu.delete()
-
- def vygeneruj_vysledkovku(self):
- from vysledkovky.utils import VysledkovkaCisla
- if hasattr(self, "vysledkovka_v_deadlinu"):
- self.vysledkovka_v_deadlinu.delete()
- vysledkovka = VysledkovkaCisla(self.cislo, jen_verejne=True, do_deadlinu=self)
- if len(vysledkovka.radky_vysledkovky) != 0:
- ZmrazenaVysledkovka.objects.create(
- deadline=self,
- html=render_to_string(
- "vysledkovky/vysledkovka_cisla.html",
- context={"vysledkovka": vysledkovka, "oznaceni_vysledkovky": self.id}
- )
- )
-
-
-class ZmrazenaVysledkovka(SeminarModelBase):
- class Meta:
- db_table = 'seminar_vysledkovky'
- verbose_name = 'Zmražená výsledkovka'
- verbose_name_plural = 'Zmražené výsledkovky'
-
- deadline = models.OneToOneField(
- Deadline,
- on_delete=models.CASCADE,
- primary_key=True,
- related_name="vysledkovka_v_deadlinu"
- )
-
- html = models.TextField(null=False, blank=False)
-
-
-@reversion.register(ignore_duplicates=True)
-# Pozor na následující řádek. *Nekrmit, asi kouše!*
-class Problem(SeminarModelBase,PolymorphicModel):
-
- class Meta:
- # Není abstraktní, protože se na něj jinak nedají dělat ForeignKeys.
- # TODO: Udělat to polymorfní (pomocí django-polymorphic), abychom dostali
- # po těch vazbách přímo tu úlohu/témátko vč. fieldů, které nejsou součástí
- # modelu Problem?
-
- #abstract = True
- db_table = 'seminar_problemy'
- verbose_name = 'Problém'
- verbose_name_plural = 'Problémy'
- ordering = ['nazev']
-
- # Interní ID
- id = models.AutoField(primary_key = True)
-
- # Název
- nazev = models.CharField('název', max_length=256) # Zveřejnitelný na stránky
-
- # Problém má podproblémy
- nadproblem = models.ForeignKey('self', verbose_name='nadřazený problém',
- related_name='podproblem', null=True, blank=True,
- on_delete=models.SET_NULL)
-
- STAV_NAVRH = 'navrh'
- STAV_ZADANY = 'zadany'
- STAV_VYRESENY = 'vyreseny'
- STAV_SMAZANY = 'smazany'
- STAV_CHOICES = [
- (STAV_NAVRH, 'Návrh'),
- (STAV_ZADANY, 'Zadaný'),
- (STAV_VYRESENY, 'Vyřešený'),
- (STAV_SMAZANY, 'Smazaný'),
- ]
- stav = models.CharField('stav problému', max_length=32, choices=STAV_CHOICES, blank=False, default=STAV_NAVRH)
- # Téma je taky Problém, takže má stavy, "zadané" témátko je aktuálně otevřené a dá se k němu něco poslat (řešení nebo článek)
-
- zamereni = TaggableManager(verbose_name='zaměření',
- help_text='Zaměření M/F/I/O problému, příp. další tagy', blank=True)
-
- poznamka = models.TextField('org poznámky (HTML)', blank=True,
- help_text='Neveřejný návrh úlohy, návrh řešení, text zadání, poznámky ...')
-
- autor = models.ForeignKey(Organizator, verbose_name='autor problému',
- related_name='autor_problemu_%(class)s', null=True, blank=True,
- on_delete=models.SET_NULL)
-
- garant = models.ForeignKey(Organizator, verbose_name='garant zadaného problému',
- related_name='garant_problemu_%(class)s', null=True, blank=True,
- on_delete=models.SET_NULL)
-
- opravovatele = models.ManyToManyField(Organizator, verbose_name='opravovatelé',
- blank=True, related_name='opravovatele_%(class)s')
-
- kod = models.CharField('lokální kód', max_length=32, blank=True, default='',
- help_text='Číslo/kód úlohy v čísle nebo kód tématu/článku/seriálu v ročníku')
-
-
- vytvoreno = models.DateTimeField('vytvořeno', default=timezone.now, blank=True, editable=False)
-
-
- def __str__(self):
- return self.nazev
-
- # Implicitini implementace, jednotlivé dědící třídy si přepíšou
- @cached_property
- def kod_v_rocniku(self):
- if self.stav == Problem.STAV_ZADANY or self.stav == Problem.STAV_VYRESENY:
- if self.nadproblem:
- return self.nadproblem.kod_v_rocniku+".{}".format(self.kod)
- return str(self.kod)
- logger.warning(f"K problému {self} byl vyžadován kód v ročníku, i když není zadaný ani vyřešený.")
- return f''
-
-# 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'
-
- TEMA_TEMA = 'tema'
- TEMA_SERIAL = 'serial'
- TEMA_CHOICES = [
- (TEMA_TEMA, 'Téma'),
- (TEMA_SERIAL, 'Seriál'),
- ]
- tema_typ = models.CharField('Typ tématu', max_length=16, choices=TEMA_CHOICES,
- blank=False, default=TEMA_TEMA)
-
- rocnik = models.ForeignKey(Rocnik, verbose_name='ročník',related_name='temata',blank=True, null=True,
- on_delete=models.PROTECT)
-
- abstrakt = models.TextField('Abstrakt na rozcestník', blank=True)
- obrazek = models.ImageField('Obrázek na rozcestník', null=True, blank=True)
-
- @cached_property
- def kod_v_rocniku(self):
- if self.stav == Problem.STAV_ZADANY or self.stav == Problem.STAV_VYRESENY:
- if self.nadproblem:
- return self.nadproblem.kod_v_rocniku+".t{}".format(self.kod)
- return 't'+self.kod
- logger.warning(f"K problému {self} byl vyžadován kód v ročníku, i když není zadaný ani vyřešený.")
- return f''
-
- def save(self, *args, **kwargs):
- super().save(*args, **kwargs)
- # *Node.save() aktualizuje název *Nodu.
- for tvcn in self.temavcislenode_set.all():
- tvcn.save()
-
- def cislo_node(self):
- tema_node_set = self.temavcislenode_set.all()
- tema_cisla_vyskyt = []
- from seminar.models.treenode import CisloNode
- for tn in tema_node_set:
- tema_cisla_vyskyt.append(
- treelib.get_upper_node_of_type(tn, CisloNode).cislo)
- tema_cisla_vyskyt.sort(key=lambda x:x.datum_vydani)
- prvni_zadani = tema_cisla_vyskyt[0]
- return prvni_zadani.cislonode
-
-class Clanek(Problem):
- class Meta:
- db_table = 'seminar_clanky'
- verbose_name = 'Článek'
- verbose_name_plural = 'Články'
-
- cislo = models.ForeignKey(Cislo, blank=True, null=True, on_delete=models.PROTECT,
- verbose_name='číslo vydání', related_name='vydane_clanky')
-
- strana = models.PositiveIntegerField(verbose_name="první strana", blank=True, null=True)
-
- @cached_property
- def kod_v_rocniku(self):
- if self.stav == Problem.STAV_ZADANY or self.stav == Problem.STAV_VYRESENY:
-# Nemělo by být potřeba
-# if self.nadproblem:
-# return self.nadproblem.kod_v_rocniku+".c{}".format(self.kod)
- return "c" + self.kod
- logger.warning(f"K problému {self} byl vyžadován kód v ročníku, i když není zadaný ani vyřešený.")
- return f''
-
- def node(self):
- return None
-
-
-class Uloha(Problem):
- class Meta:
- db_table = 'seminar_ulohy'
- verbose_name = 'Úloha'
- verbose_name_plural = 'Úlohy'
-
- cislo_zadani = models.ForeignKey(Cislo, verbose_name='číslo zadání', blank=True,
- null=True, related_name='zadane_ulohy', on_delete=models.PROTECT)
-
- cislo_deadline = models.ForeignKey(Cislo, verbose_name='číslo deadlinu', blank=True,
- null=True, related_name='deadlinove_ulohy', on_delete=models.PROTECT)
-
- cislo_reseni = models.ForeignKey(Cislo, verbose_name='číslo řešení', blank=True,
- null=True, related_name='resene_ulohy',
- help_text='Číslo s řešením úlohy, jen pro úlohy',
- on_delete=models.PROTECT)
-
- max_body = models.DecimalField(max_digits=8, decimal_places=1, verbose_name='maximum bodů',
- blank=True, null=True)
-
- # má OneToOneField s:
- # UlohaZadaniNode
- # UlohaVzorakNode
-
- @cached_property
- def kod_v_rocniku(self):
- if self.stav == Problem.STAV_ZADANY or self.stav == Problem.STAV_VYRESENY:
- return f"{self.cislo_zadani.poradi}.{self.kod}"
- logger.warning(f"K problému {self} byl vyžadován kód v ročníku, i když není zadaný ani vyřešený.")
- return f''
-
- def save(self, *args, **kwargs):
- super().save(*args, **kwargs)
- # *Node.save() aktualizuje název *Nodu.
- try:
- self.ulohazadaninode.save()
- except ObjectDoesNotExist:
- # Neexistující *Node nemá smysl aktualizovat.
- pass
- try:
- self.ulohavzoraknode.save()
- except ObjectDoesNotExist:
- # Neexistující *Node nemá smysl aktualizovat.
- pass
-
- def cislo_node(self):
- zadani_node = self.ulohazadaninode
- from seminar.models.treenode import CisloNode
- return treelib.get_upper_node_of_type(zadani_node, CisloNode)
-
-
-def aux_generate_filename(self, filename):
- """Pomocná funkce generující ošetřený název souboru v adresáři s datem"""
- clean = get_valid_filename(
- unidecode(filename.replace('/', '-').replace('\0', ''))
- )
- datedir = timezone.now().strftime('%Y-%m')
- fname = "{}/{}".format(
- timezone.now().strftime('%Y-%m-%d-%H:%M'),
- clean)
- return os.path.join(datedir, fname)
-
-
-class Pohadka(SeminarModelBase):
- """Kus pohádky před/za úlohou v čísle"""
-
- class Meta:
- db_table = 'seminar_pohadky'
- verbose_name = 'Pohádka'
- verbose_name_plural = 'Pohádky'
- ordering = ['vytvoreno']
-
- # Interní ID
- id = models.AutoField(primary_key=True)
-
- autor = models.ForeignKey(
- Organizator,
- verbose_name="Autor pohádky",
-
- # Při nahrávání z TeXu není vyplnění vyžadováno, v adminu je
- null=True,
- blank=False,
- on_delete=models.SET_NULL
- )
-
- vytvoreno = models.DateTimeField(
- 'Vytvořeno',
- default=timezone.now,
- blank=True,
- editable=False
- )
-
- # má OneToOneField s:
- # PohadkaNode
-
- def __str__(self):
- uryvek = self.text if len(self.text) < 50 else self.text[:(50-3)]+"..."
- return uryvek
-
- def save(self, *args, **kwargs):
- super().save(*args, **kwargs)
- # *Node.save() aktualizuje název *Nodu.
- try:
- self.pohadkanode.save()
- except ObjectDoesNotExist:
- # Neexistující *Node nemá smysl aktualizovat.
- pass
diff --git a/soustredeni/migrations/0004_tvorba_pre.py b/soustredeni/migrations/0004_tvorba_pre.py
new file mode 100644
index 00000000..76461971
--- /dev/null
+++ b/soustredeni/migrations/0004_tvorba_pre.py
@@ -0,0 +1,13 @@
+# Generated by Django 4.2.16 on 2024-10-30 01:07
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('soustredeni', '0003_post_split_soustredeni'),
+ ]
+
+ operations = [
+ ]
diff --git a/soustredeni/migrations/0005_tvorba_relink.py b/soustredeni/migrations/0005_tvorba_relink.py
new file mode 100644
index 00000000..1a3a09bf
--- /dev/null
+++ b/soustredeni/migrations/0005_tvorba_relink.py
@@ -0,0 +1,28 @@
+# Generated by Django 4.2.16 on 2024-10-30 13:18
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tvorba', '0001_tvorba_create'),
+ ('soustredeni', '0004_tvorba_pre'),
+ ]
+
+ operations = [
+ ## Konferu zmigrujeme jinak, kvůli 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',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='soustredeni', to='tvorba.rocnik', verbose_name='ročník'),
+ ),
+ ]
diff --git a/soustredeni/migrations/0006_tvorba_relink2.py b/soustredeni/migrations/0006_tvorba_relink2.py
new file mode 100644
index 00000000..0fe70b8c
--- /dev/null
+++ b/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'},
+ ),
+ ]
diff --git a/soustredeni/migrations/0007_tvorba_relink3.py b/soustredeni/migrations/0007_tvorba_relink3.py
new file mode 100644
index 00000000..48a84686
--- /dev/null
+++ b/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',
+ ),
+ ]
diff --git a/soustredeni/migrations/0008_tvorba_relink4.py b/soustredeni/migrations/0008_tvorba_relink4.py
new file mode 100644
index 00000000..d2792c8e
--- /dev/null
+++ b/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',),
+ ),
+ ]
diff --git a/soustredeni/migrations/0009_tvorba_relink5.py b/soustredeni/migrations/0009_tvorba_relink5.py
new file mode 100644
index 00000000..cfe5c97b
--- /dev/null
+++ b/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'},
+ ),
+ ]
diff --git a/soustredeni/migrations/0010_tvorba_post.py b/soustredeni/migrations/0010_tvorba_post.py
new file mode 100644
index 00000000..1a700298
--- /dev/null
+++ b/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 = [
+ ]
diff --git a/soustredeni/models.py b/soustredeni/models.py
index fb5c5239..23553c34 100644
--- a/soustredeni/models.py
+++ b/soustredeni/models.py
@@ -10,7 +10,7 @@ from django.conf import settings
from personalni.models import Resitel, Organizator
from seminar.models.base import SeminarModelBase
-import seminar.models as am # tvorba
+import tvorba.models as am
logger = logging.getLogger(__name__)
diff --git a/split-apps-meta/polymorphic b/split-apps-meta/polymorphic
new file mode 100644
index 00000000..9c6ba514
--- /dev/null
+++ b/split-apps-meta/polymorphic
@@ -0,0 +1,3 @@
+django-polymorphic by *nemělo* být potřeba řešit, protože se odkazuje na id contenttype a tedy když přepisujeme ctype na správném místě rovnou, tak to bude fungovat. IN THEORY.
+
+Better safe than sorry: přidáme si v seminar.pre vazbu na model contenttypes. (technicky asi měl být všude?)
diff --git a/tvorba/admin.py b/tvorba/admin.py
index 01880d5b..0e873c29 100644
--- a/tvorba/admin.py
+++ b/tvorba/admin.py
@@ -1,6 +1,7 @@
from django.contrib import admin
from django.forms import ModelForm
from django.core.exceptions import ValidationError
+from django.urls import reverse
from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter
from django.utils.safestring import mark_safe
@@ -9,7 +10,7 @@ from django.utils.safestring import mark_safe
import soustredeni.models
-from seminar.models import Rocnik, ZmrazenaVysledkovka, Deadline, Uloha, Problem, Tema, Clanek, Cislo # tvorba
+from tvorba.models import Rocnik, ZmrazenaVysledkovka, Deadline, Uloha, Problem, Tema, Clanek, Cislo # tvorba
admin.site.register(Rocnik)
admin.site.register(ZmrazenaVysledkovka)
@@ -59,9 +60,6 @@ class CisloForm(ModelForm):
# if problem not in \
# (Problem.STAV_ZADANY, Problem.STAV_VYRESENY):
# errors.append(ValidationError('Problém %s není zadaný ani vyřešený', code=problem))
- # if errors:
- # errors.append(ValidationError(mark_safe('Pokud chceš učinit všechny problémy, co nejsou zadané ani vyřešené, zadanými a číslo zveřejnit, můžeš to udělat pomocí akce v seznamu čísel')))
- # raise ValidationError(errors)
errors = []
for ch in Uloha.objects.filter(cislo_zadani=self.instance):
@@ -70,7 +68,7 @@ class CisloForm(ModelForm):
ValidationError('Úloha %(uloha)s není zadaná ani vyřešená', params={'uloha': ch}))
if errors:
errors.append(ValidationError(mark_safe(
- 'Pokud chceš učinit všechny problémy, co nejsou zadané ani vyřešené, zadanými a číslo zveřejnit, můžeš to udělat pomocí akce v seznamu čísel')))
+ 'Pokud chceš učinit všechny problémy, co nejsou zadané ani vyřešené, zadanými a číslo zveřejnit, můžeš to udělat pomocí akce v seznamu čísel')))
if self.cleaned_data.get('datum_vydani') == None:
self.add_error('datum_vydani','Číslo určené ke zveřejnění nemá nastavené datum vydání')
diff --git a/tvorba/migrations/0001_tvorba_create.py b/tvorba/migrations/0001_tvorba_create.py
new file mode 100644
index 00000000..085cb5b4
--- /dev/null
+++ b/tvorba/migrations/0001_tvorba_create.py
@@ -0,0 +1,197 @@
+# Generated by Django 4.2.16 on 2024-10-30 11:37
+
+import datetime
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+import seminar.models.tvorba
+import tvorba.models
+import taggit.managers
+
+def nastav_nove_contenttypes(apps, schema_editor):
+ ContentType = apps.get_model('contenttypes', 'ContentType')
+ for m in ('zmrazenavysledkovka', 'deadline', 'cislo', 'rocnik', 'pohadka', 'tema', 'problem', 'problemy_opravovatele', 'uloha', 'clanek'):
+ ContentType.objects.filter(app_label='seminar', model=m).update(app_label='tvorba')
+
+def nastav_stare_contenttypes(apps, schema_editor):
+ ContentType = apps.get_model('contenttypes', 'ContentType')
+ for m in ('zmrazenavysledkovka', 'deadline', 'cislo', 'rocnik', 'pohadka', 'tema', 'problem', 'problemy_opravovatele', 'uloha', 'clanek'):
+ ContentType.objects.filter(app_label='tvorba', model=m).update(app_label='seminar')
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('seminar', '0137_tvorba_unmanage'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Cislo',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('poradi', models.CharField(db_index=True, help_text='Většinou jen "1", vyjímečně "7-8", lexikograficky určuje pořadí v ročníku!', max_length=32, verbose_name='název čísla')),
+ ('datum_vydani', models.DateField(blank=True, help_text='Datum vydání finální verze', null=True, verbose_name='datum vydání')),
+ ('verejne_db', models.BooleanField(db_column='verejne', default=False, verbose_name='číslo zveřejněno')),
+ ('poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka k číslu (plain text)', verbose_name='neveřejná poznámka')),
+ ('pdf', models.FileField(blank=True, help_text='PDF čísla, které si mohou řešitelé stáhnout', null=True, storage=seminar.models.tvorba.OverwriteStorage(), upload_to=tvorba.models.cislo_pdf_filename, verbose_name='pdf')),
+ ('titulka_nahled', models.ImageField(blank=True, help_text='Obrázek titulní strany, generuje se automaticky', null=True, upload_to=tvorba.models.cislo_png_filename, verbose_name='Obrázek titulní strany')),
+ ('rocnik', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='cisla', to='tvorba.rocnik', verbose_name='ročník')),
+ ],
+ options={
+ 'verbose_name': 'Číslo',
+ 'verbose_name_plural': 'Čísla',
+ 'db_table': 'seminar_cisla',
+ 'ordering': ['-rocnik__rocnik', '-poradi'],
+ 'managed': False,
+ },
+ ),
+ migrations.CreateModel(
+ name='Deadline',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('deadline', models.DateTimeField(default=datetime.datetime(2024, 10, 30, 22, 59, 59, 999999, tzinfo=datetime.timezone.utc))),
+ ('cislo', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='deadline_v_cisle', to='tvorba.cislo', verbose_name='deadline v čísle')),
+ ('typ', models.CharField(choices=[('cisla', 'Deadline celého čísla'), ('prvni', 'První deadline'), ('prvniasous', 'Sousový a první deadline'), ('sous', 'Sousový deadline')], max_length=32, verbose_name='typ deadlinu')),
+ ('verejna_vysledkovka', models.BooleanField(db_column='verejna_vysledkovka', default=False, verbose_name='veřejná výsledkovka')),
+ ],
+ options={
+ 'verbose_name': 'Deadline',
+ 'verbose_name_plural': 'Deadliny',
+ 'db_table': 'seminar_deadliny',
+ 'ordering': ['deadline'],
+ 'managed': False,
+ },
+ ),
+ migrations.CreateModel(
+ name='Pohadka',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('vytvoreno', models.DateTimeField(blank=True, default=django.utils.timezone.now, editable=False, verbose_name='Vytvořeno')),
+ ('autor', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='personalni.organizator', verbose_name='Autor pohádky')),
+ ],
+ options={
+ 'verbose_name': 'Pohádka',
+ 'verbose_name_plural': 'Pohádky',
+ 'db_table': 'seminar_pohadky',
+ 'ordering': ['vytvoreno'],
+ 'managed': False,
+ },
+ ),
+ migrations.CreateModel(
+ name='Problem',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('nazev', models.CharField(max_length=256, verbose_name='název')),
+ ('stav', models.CharField(choices=[('navrh', 'Návrh'), ('zadany', 'Zadaný'), ('vyreseny', 'Vyřešený'), ('smazany', 'Smazaný')], default='navrh', max_length=32, verbose_name='stav problému')),
+ ('autor', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='autor_problemu_%(class)s', to='personalni.organizator', verbose_name='autor problému')),
+ ('garant', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='garant_problemu_%(class)s', to='personalni.organizator', verbose_name='garant zadaného problému')),
+ ('nadproblem', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='podproblem', to='tvorba.problem', verbose_name='nadřazený problém')),
+ ('opravovatele', models.ManyToManyField(blank=True, related_name='opravovatele_%(class)s', through='tvorba.Problemy_Opravovatele', to='personalni.organizator', verbose_name='opravovatelé')),
+ ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),
+ ('zamereni', taggit.managers.TaggableManager(blank=True, help_text='Zaměření M/F/I/O problému, příp. další tagy', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='zaměření')),
+ ('poznamka', models.TextField(blank=True, help_text='Neveřejný návrh úlohy, návrh řešení, text zadání, poznámky ...', verbose_name='org poznámky (HTML)')),
+ ('kod', models.CharField(blank=True, default='', help_text='Číslo/kód úlohy v čísle nebo kód tématu/článku/seriálu v ročníku', max_length=32, verbose_name='lokální kód')),
+ ('vytvoreno', models.DateTimeField(blank=True, default=django.utils.timezone.now, editable=False, verbose_name='vytvořeno')),
+ ],
+ options={
+ 'verbose_name': 'Problém',
+ 'verbose_name_plural': 'Problémy',
+ 'db_table': 'seminar_problemy',
+ 'ordering': ['nazev'],
+ 'managed': False,
+ },
+ ),
+ migrations.CreateModel(
+ name='Problemy_Opravovatele',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('problem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tvorba.problem')),
+ ('organizator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='personalni.organizator')),
+ ],
+ options={
+ 'db_table': 'seminar_problemy_opravovatele',
+ 'managed': False,
+ },
+ ),
+ migrations.CreateModel(
+ name='Rocnik',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('prvni_rok', models.IntegerField(db_index=True, unique=True, verbose_name='první rok')),
+ ('rocnik', models.IntegerField(db_index=True, unique=True, verbose_name='číslo ročníku')),
+ ('exportovat', models.BooleanField(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', verbose_name='export do AESOPa')),
+ ],
+ options={
+ 'verbose_name': 'Ročník',
+ 'verbose_name_plural': 'Ročníky',
+ 'db_table': 'seminar_rocniky',
+ 'ordering': ['-rocnik'],
+ 'managed': False,
+ },
+ ),
+ migrations.CreateModel(
+ name='Clanek',
+ 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')),
+ ('strana', models.PositiveIntegerField(blank=True, null=True, verbose_name='první strana')),
+ ('cislo', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='vydane_clanky', to='tvorba.cislo', verbose_name='číslo vydání')),
+ ],
+ options={
+ 'verbose_name': 'Článek',
+ 'verbose_name_plural': 'Články',
+ 'db_table': 'seminar_clanky',
+ 'managed': False,
+ },
+ bases=('tvorba.problem',),
+ ),
+ migrations.CreateModel(
+ name='Tema',
+ 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')),
+ ('tema_typ', models.CharField(choices=[('tema', 'Téma'), ('serial', 'Seriál')], default='tema', max_length=16, verbose_name='Typ tématu')),
+ ('rocnik', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='temata', to='tvorba.rocnik', verbose_name='ročník')),
+ ('abstrakt', models.TextField(blank=True, verbose_name='Abstrakt na rozcestník')),
+ ('obrazek', models.ImageField(blank=True, null=True, upload_to='', verbose_name='Obrázek na rozcestník')),
+ ],
+ options={
+ 'verbose_name': 'Téma',
+ 'verbose_name_plural': 'Témata',
+ 'db_table': 'seminar_temata',
+ 'managed': False,
+ },
+ bases=('tvorba.problem',),
+ ),
+ migrations.CreateModel(
+ name='Uloha',
+ 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')),
+ ('max_body', models.DecimalField(blank=True, decimal_places=1, max_digits=8, null=True, verbose_name='maximum bodů')),
+ ('cislo_zadani', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='zadane_ulohy', to='tvorba.cislo', verbose_name='číslo zadání')),
+ ('cislo_reseni', models.ForeignKey(blank=True, help_text='Číslo s řešením úlohy, jen pro úlohy', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='resene_ulohy', to='tvorba.cislo', verbose_name='číslo řešení')),
+ ('cislo_deadline', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='deadlinove_ulohy', to='tvorba.cislo', verbose_name='číslo deadlinu')),
+ ],
+ options={
+ 'verbose_name': 'Úloha',
+ 'verbose_name_plural': 'Úlohy',
+ 'db_table': 'seminar_ulohy',
+ 'managed': False,
+ },
+ bases=('tvorba.problem',),
+ ),
+ migrations.CreateModel(
+ name='ZmrazenaVysledkovka',
+ fields=[
+ ('deadline', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='vysledkovka_v_deadlinu', serialize=False, to='tvorba.deadline')),
+ ('html', models.TextField()),
+ ],
+ options={
+ 'verbose_name': 'Zmražená výsledkovka',
+ 'verbose_name_plural': 'Zmražené výsledkovky',
+ 'db_table': 'seminar_vysledkovky',
+ 'managed': False,
+ },
+ ),
+ migrations.RunPython(nastav_nove_contenttypes, nastav_stare_contenttypes),
+ ]
diff --git a/tvorba/migrations/0002_tvorba_manage.py b/tvorba/migrations/0002_tvorba_manage.py
new file mode 100644
index 00000000..593d3263
--- /dev/null
+++ b/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'},
+ ),
+ ]
diff --git a/tvorba/migrations/0003_tvorba_post.py b/tvorba/migrations/0003_tvorba_post.py
new file mode 100644
index 00000000..16e6d203
--- /dev/null
+++ b/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 = [
+ ]
diff --git a/tvorba/models.py b/tvorba/models.py
new file mode 100644
index 00000000..d7290ae3
--- /dev/null
+++ b/tvorba/models.py
@@ -0,0 +1,708 @@
+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.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 seminar.models.base import SeminarModelBase
+from seminar.models.tvorba import OverwriteStorage
+from personalni.models import Prijemce, Organizator
+
+logger = logging.getLogger(__name__)
+
+@reversion.register(ignore_duplicates=True)
+class Rocnik(SeminarModelBase):
+
+ class Meta:
+ db_table = 'seminar_rocniky'
+ verbose_name = 'Ročník'
+ verbose_name_plural = 'Ročníky'
+ ordering = ['-rocnik']
+
+ # Interní ID
+ id = models.AutoField(primary_key = True)
+
+ prvni_rok = models.IntegerField('první rok', db_index=True, unique=True)
+
+ rocnik = models.IntegerField('číslo ročníku', db_index=True, unique=True)
+
+ exportovat = models.BooleanField('export do AESOPa', db_column='exportovat', default=False,
+ help_text='Exportuje se jen podle tohoto flagu (ne veřejnosti),'
+ ' a to jen čísla s veřejnou výsledkovkou')
+
+ def __str__(self):
+ return '{} ({}/{})'.format(self.rocnik, self.prvni_rok, self.prvni_rok+1)
+
+ # Ročník v římských číslech
+ def roman(self):
+ return roman(int(self.rocnik))
+
+ def verejne(self):
+ return len(self.verejna_cisla()) > 0
+ verejne.boolean = True
+ verejne.short_description = 'Veřejný (jen dle čísel)'
+
+ def neverejna_cisla(self):
+ vc = [c for c in self.cisla.all() if not c.verejne()]
+ vc.sort(key=lambda c: c.poradi)
+ return vc
+
+ def verejna_cisla(self):
+ vc = [c for c in self.cisla.all() if c.verejne()]
+ vc.sort(key=lambda c: c.poradi)
+ return vc
+
+ def posledni_verejne_cislo(self):
+ vc = self.verejna_cisla()
+ return vc[-1] if vc else None
+
+ def verejne_vysledkovky_cisla(self):
+ vc = list(self.cisla.filter(deadline_v_cisle__verejna_vysledkovka=True).distinct())
+ vc.sort(key=lambda c: c.poradi)
+ return vc
+
+ def posledni_zverejnena_vysledkovka_cislo(self):
+ vc = self.verejne_vysledkovky_cisla()
+ return vc[-1] if vc else None
+
+ def druhy_rok(self):
+ return self.prvni_rok + 1
+
+ def verejne_url(self):
+ return reverse('seminar_rocnik', kwargs={'rocnik': self.rocnik})
+
+ @classmethod
+ def cached_rocnik(cls, r_id):
+ name = 'rocnik_%s' % (r_id, )
+ c = cache.get(name)
+ if c is None:
+ c = cls.objects.get(id=r_id)
+ cache.set(name, c, 300)
+ return c
+
+ def save(self, *args, **kwargs):
+ super().save(*args, **kwargs)
+ # *Node.save() aktualizuje název *Nodu.
+ try:
+ self.rocniknode.save()
+ except ObjectDoesNotExist:
+ # Neexistující *Node nemá smysl aktualizovat.
+ pass
+
+def cislo_pdf_filename(self, filename):
+ rocnik = str(self.rocnik.rocnik)
+ return pathlib.Path('cislo', 'pdf', rocnik, '{}-{}.pdf'.format(rocnik, self.poradi))
+
+def cislo_png_filename(self, filename):
+ rocnik = str(self.rocnik.rocnik)
+ return pathlib.Path('cislo', 'png', rocnik, '{}-{}.png'.format(rocnik, self.poradi))
+
+@reversion.register(ignore_duplicates=True)
+class Cislo(SeminarModelBase):
+
+ class Meta:
+ db_table = 'seminar_cisla'
+ verbose_name = 'Číslo'
+ verbose_name_plural = 'Čísla'
+ ordering = ['-rocnik__rocnik', '-poradi']
+
+ # Interní ID
+ id = models.AutoField(primary_key = True)
+
+ rocnik = models.ForeignKey(Rocnik, verbose_name='ročník', related_name='cisla',
+ db_index=True,on_delete=models.PROTECT)
+
+ poradi = models.CharField('název čísla', max_length=32, db_index=True,
+ help_text='Většinou jen "1", vyjímečně "7-8", lexikograficky určuje pořadí v ročníku!')
+
+ datum_vydani = models.DateField('datum vydání', blank=True, null=True,
+ help_text='Datum vydání finální verze')
+
+ verejne_db = models.BooleanField('číslo zveřejněno',
+ db_column='verejne', default=False)
+
+ poznamka = models.TextField('neveřejná poznámka', blank=True,
+ help_text='Neveřejná poznámka k číslu (plain text)')
+
+ pdf = models.FileField('pdf', upload_to=cislo_pdf_filename, null=True, blank=True,
+ help_text='PDF čísla, které si mohou řešitelé stáhnout', storage=OverwriteStorage())
+
+ titulka_nahled = models.ImageField('Obrázek titulní strany', upload_to=cislo_png_filename, null=True, blank=True,
+ help_text='Obrázek titulní strany, generuje se automaticky')
+
+ 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']
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.__original_verejna_vysledkovka = self.verejna_vysledkovka
+
+ id = models.AutoField(primary_key=True)
+
+ # V ročníku < 26 nastaveno na datetime.datetime.combine(datetime.date(1994 + cislo.rocnik.rocnik, 6, int(cislo.poradi[0])), datetime.time.min)
+ deadline = models.DateTimeField(blank=False, default=timezone.make_aware(datetime.datetime.combine(timezone.now(), datetime.time.max)))
+
+ cislo = models.ForeignKey(Cislo, verbose_name='deadline v čísle',
+ related_name='deadline_v_cisle', blank=False,
+ on_delete=models.CASCADE)
+
+ TYP_CISLA = 'cisla'
+ TYP_PRVNI_A_SOUS = 'prvniasous'
+ TYP_PRVNI = 'prvni'
+ TYP_SOUS = 'sous'
+ TYP_CHOICES = [
+ (TYP_CISLA, 'Deadline celého čísla'),
+ (TYP_PRVNI, 'První deadline'),
+ (TYP_PRVNI_A_SOUS, 'Sousový a první deadline'),
+ (TYP_SOUS, 'Sousový deadline'),
+ ]
+ CHOICES_MAP = dict(TYP_CHOICES)
+ typ = models.CharField('typ deadlinu', max_length=32,
+ choices=TYP_CHOICES, blank=False)
+
+ verejna_vysledkovka = models.BooleanField('veřejná výsledkovka',
+ db_column='verejna_vysledkovka',
+ default=False)
+
+ def __str__(self):
+ return self.CHOICES_MAP[self.typ] + " " + str(self.cislo)
+
+ def save(self, *args, **kwargs):
+ super().save(*args, **kwargs)
+ if self.verejna_vysledkovka and not self.__original_verejna_vysledkovka:
+ self.vygeneruj_vysledkovku()
+ if not self.verejna_vysledkovka and hasattr(self, "vysledkovka_v_deadlinu"):
+ self.vysledkovka_v_deadlinu.delete()
+
+ def vygeneruj_vysledkovku(self):
+ from vysledkovky.utils import VysledkovkaCisla
+ if hasattr(self, "vysledkovka_v_deadlinu"):
+ self.vysledkovka_v_deadlinu.delete()
+ vysledkovka = VysledkovkaCisla(self.cislo, jen_verejne=True, do_deadlinu=self)
+ if len(vysledkovka.radky_vysledkovky) != 0:
+ ZmrazenaVysledkovka.objects.create(
+ deadline=self,
+ html=render_to_string(
+ "vysledkovky/vysledkovka_cisla.html",
+ context={"vysledkovka": vysledkovka, "oznaceni_vysledkovky": self.id}
+ )
+ )
+
+
+class ZmrazenaVysledkovka(SeminarModelBase):
+ class Meta:
+ db_table = 'seminar_vysledkovky'
+ verbose_name = 'Zmražená výsledkovka'
+ verbose_name_plural = 'Zmražené výsledkovky'
+
+ deadline = models.OneToOneField(
+ Deadline,
+ on_delete=models.CASCADE,
+ primary_key=True,
+ related_name="vysledkovka_v_deadlinu"
+ )
+
+ html = models.TextField(null=False, blank=False)
+
+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'
+
+ id = models.AutoField(primary_key = True)
+
+ problem = models.ForeignKey('Problem', on_delete=models.CASCADE)
+ organizator = models.ForeignKey(Organizator, on_delete=models.CASCADE)
+
+@reversion.register(ignore_duplicates=True)
+# Pozor na následující řádek. *Nekrmit, asi kouše!*
+class Problem(SeminarModelBase,PolymorphicModel):
+
+ class Meta:
+ # Není abstraktní, protože se na něj jinak nedají dělat ForeignKeys.
+ # TODO: Udělat to polymorfní (pomocí django-polymorphic), abychom dostali
+ # po těch vazbách přímo tu úlohu/témátko vč. fieldů, které nejsou součástí
+ # modelu Problem?
+
+ #abstract = True
+ db_table = 'seminar_problemy'
+ verbose_name = 'Problém'
+ verbose_name_plural = 'Problémy'
+ ordering = ['nazev']
+
+ # Interní ID
+ id = models.AutoField(primary_key = True)
+
+ # Název
+ nazev = models.CharField('název', max_length=256) # Zveřejnitelný na stránky
+
+ # Problém má podproblémy
+ nadproblem = models.ForeignKey('self', verbose_name='nadřazený problém',
+ related_name='podproblem', null=True, blank=True,
+ on_delete=models.SET_NULL)
+
+ STAV_NAVRH = 'navrh'
+ STAV_ZADANY = 'zadany'
+ STAV_VYRESENY = 'vyreseny'
+ STAV_SMAZANY = 'smazany'
+ STAV_CHOICES = [
+ (STAV_NAVRH, 'Návrh'),
+ (STAV_ZADANY, 'Zadaný'),
+ (STAV_VYRESENY, 'Vyřešený'),
+ (STAV_SMAZANY, 'Smazaný'),
+ ]
+ stav = models.CharField('stav problému', max_length=32, choices=STAV_CHOICES, blank=False, default=STAV_NAVRH)
+ # Téma je taky Problém, takže má stavy, "zadané" témátko je aktuálně otevřené a dá se k němu něco poslat (řešení nebo článek)
+
+ zamereni = TaggableManager(verbose_name='zaměření',
+ help_text='Zaměření M/F/I/O problému, příp. další tagy', blank=True)
+
+ poznamka = models.TextField('org poznámky (HTML)', blank=True,
+ help_text='Neveřejný návrh úlohy, návrh řešení, text zadání, poznámky ...')
+
+ autor = models.ForeignKey(Organizator, verbose_name='autor problému',
+ related_name='autor_problemu_%(class)s', null=True, blank=True,
+ on_delete=models.SET_NULL)
+
+ garant = models.ForeignKey(Organizator, verbose_name='garant zadaného problému',
+ related_name='garant_problemu_%(class)s', null=True, blank=True,
+ on_delete=models.SET_NULL)
+
+ opravovatele = models.ManyToManyField(Organizator, verbose_name='opravovatelé',
+ blank=True, related_name='opravovatele_%(class)s', 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''
+
+# 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:tvorba_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'
+
+ TEMA_TEMA = 'tema'
+ TEMA_SERIAL = 'serial'
+ TEMA_CHOICES = [
+ (TEMA_TEMA, 'Téma'),
+ (TEMA_SERIAL, 'Seriál'),
+ ]
+ tema_typ = models.CharField('Typ tématu', max_length=16, choices=TEMA_CHOICES,
+ blank=False, default=TEMA_TEMA)
+
+ rocnik = models.ForeignKey(Rocnik, verbose_name='ročník',related_name='temata',blank=True, null=True,
+ on_delete=models.PROTECT)
+
+ abstrakt = models.TextField('Abstrakt na rozcestník', blank=True)
+ obrazek = models.ImageField('Obrázek na rozcestník', null=True, blank=True)
+
+ @cached_property
+ def kod_v_rocniku(self):
+ if self.stav == Problem.STAV_ZADANY or self.stav == Problem.STAV_VYRESENY:
+ if self.nadproblem:
+ return self.nadproblem.kod_v_rocniku+".t{}".format(self.kod)
+ return 't'+self.kod
+ logger.warning(f"K problému {self} byl vyžadován kód v ročníku, i když není zadaný ani vyřešený.")
+ return f''
+
+ def save(self, *args, **kwargs):
+ super().save(*args, **kwargs)
+ # *Node.save() aktualizuje název *Nodu.
+ for tvcn in self.temavcislenode_set.all():
+ tvcn.save()
+
+ def cislo_node(self):
+ tema_node_set = self.temavcislenode_set.all()
+ tema_cisla_vyskyt = []
+ from seminar.models.treenode import CisloNode
+ for tn in tema_node_set:
+ tema_cisla_vyskyt.append(
+ treelib.get_upper_node_of_type(tn, CisloNode).cislo)
+ tema_cisla_vyskyt.sort(key=lambda x:x.datum_vydani)
+ prvni_zadani = tema_cisla_vyskyt[0]
+ return prvni_zadani.cislonode
+
+class Clanek(Problem):
+ class Meta:
+ db_table = 'seminar_clanky'
+ verbose_name = 'Článek'
+ verbose_name_plural = 'Články'
+
+ cislo = models.ForeignKey(Cislo, blank=True, null=True, on_delete=models.PROTECT,
+ verbose_name='číslo vydání', related_name='vydane_clanky')
+
+ strana = models.PositiveIntegerField(verbose_name="první strana", blank=True, null=True)
+
+ @cached_property
+ def kod_v_rocniku(self):
+ if self.stav == Problem.STAV_ZADANY or self.stav == Problem.STAV_VYRESENY:
+# Nemělo by být potřeba
+# if self.nadproblem:
+# return self.nadproblem.kod_v_rocniku+".c{}".format(self.kod)
+ return "c" + self.kod
+ logger.warning(f"K problému {self} byl vyžadován kód v ročníku, i když není zadaný ani vyřešený.")
+ return f''
+
+ def node(self):
+ return None
+
+
+class Uloha(Problem):
+ class Meta:
+ db_table = 'seminar_ulohy'
+ verbose_name = 'Úloha'
+ verbose_name_plural = 'Úlohy'
+
+ cislo_zadani = models.ForeignKey(Cislo, verbose_name='číslo zadání', blank=True,
+ null=True, related_name='zadane_ulohy', on_delete=models.PROTECT)
+
+ cislo_deadline = models.ForeignKey(Cislo, verbose_name='číslo deadlinu', blank=True,
+ null=True, related_name='deadlinove_ulohy', on_delete=models.PROTECT)
+
+ cislo_reseni = models.ForeignKey(Cislo, verbose_name='číslo řešení', blank=True,
+ null=True, related_name='resene_ulohy',
+ help_text='Číslo s řešením úlohy, jen pro úlohy',
+ on_delete=models.PROTECT)
+
+ max_body = models.DecimalField(max_digits=8, decimal_places=1, verbose_name='maximum bodů',
+ blank=True, null=True)
+
+ @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''
+
+ def save(self, *args, **kwargs):
+ super().save(*args, **kwargs)
+ # *Node.save() aktualizuje název *Nodu.
+ try:
+ self.ulohazadaninode.save()
+ except ObjectDoesNotExist:
+ # Neexistující *Node nemá smysl aktualizovat.
+ pass
+ try:
+ self.ulohavzoraknode.save()
+ except ObjectDoesNotExist:
+ # Neexistující *Node nemá smysl aktualizovat.
+ pass
+
+ def cislo_node(self):
+ zadani_node = self.ulohazadaninode
+ from seminar.models.treenode import CisloNode
+ return treelib.get_upper_node_of_type(zadani_node, CisloNode)
+
+
+def aux_generate_filename(self, filename):
+ """Pomocná funkce generující ošetřený název souboru v adresáři s datem"""
+ clean = get_valid_filename(
+ unidecode(filename.replace('/', '-').replace('\0', ''))
+ )
+ datedir = timezone.now().strftime('%Y-%m')
+ fname = "{}/{}".format(
+ timezone.now().strftime('%Y-%m-%d-%H:%M'),
+ clean)
+ return os.path.join(datedir, fname)
+
+
+class Pohadka(SeminarModelBase):
+ """Kus pohádky před/za úlohou v čísle"""
+
+ class Meta:
+ db_table = 'seminar_pohadky'
+ verbose_name = 'Pohádka'
+ verbose_name_plural = 'Pohádky'
+ ordering = ['vytvoreno']
+
+ # Interní ID
+ id = models.AutoField(primary_key=True)
+
+ autor = models.ForeignKey(
+ Organizator,
+ verbose_name="Autor pohádky",
+
+ # Při nahrávání z TeXu není vyplnění vyžadováno, v adminu je
+ null=True,
+ blank=False,
+ on_delete=models.SET_NULL,
+ )
+
+ vytvoreno = models.DateTimeField(
+ 'Vytvořeno',
+ default=timezone.now,
+ blank=True,
+ editable=False
+ )
+
+ 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
+
diff --git a/various/migrations/0004_tvorba_pre.py b/various/migrations/0004_tvorba_pre.py
new file mode 100644
index 00000000..20c1a3a0
--- /dev/null
+++ b/various/migrations/0004_tvorba_pre.py
@@ -0,0 +1,13 @@
+# Generated by Django 4.2.16 on 2024-10-30 01:06
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('various', '0003_fix_permissions'),
+ ]
+
+ operations = [
+ ]
diff --git a/various/migrations/0005_tvorba_relink.py b/various/migrations/0005_tvorba_relink.py
new file mode 100644
index 00000000..5ec264e8
--- /dev/null
+++ b/various/migrations/0005_tvorba_relink.py
@@ -0,0 +1,20 @@
+# Generated by Django 4.2.16 on 2024-10-30 13:18
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tvorba', '0001_tvorba_create'),
+ ('various', '0004_tvorba_pre'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='nastaveni',
+ name='aktualni_cislo',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='tvorba.cislo', verbose_name='Aktuální číslo'),
+ ),
+ ]
diff --git a/various/migrations/0006_tvorba_post.py b/various/migrations/0006_tvorba_post.py
new file mode 100644
index 00000000..4b339a05
--- /dev/null
+++ b/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 = [
+ ]
diff --git a/various/models.py b/various/models.py
index 17632c46..85ba4702 100644
--- a/various/models.py
+++ b/various/models.py
@@ -3,7 +3,7 @@ from django.db import models
from reversion import revisions as reversion
from solo.models import SingletonModel
-from seminar.models import Cislo
+from tvorba.models import Cislo
from django.urls import reverse
@@ -33,7 +33,7 @@ class Nastaveni(SingletonModel):
return 'Nastavení semináře'
def admin_url(self):
- return reverse('admin:seminar_nastaveni_change', args=(self.id, ))
+ return reverse('admin:various_nastaveni_change', args=(self.id, ))
def verejne(self):
return False