odstřel tvorby: pre – relink
This commit is contained in:
parent
b8f377b15d
commit
92c05342fb
18 changed files with 1172 additions and 18 deletions
13
odevzdavatko/migrations/0004_tvorba_pre.py
Normal file
13
odevzdavatko/migrations/0004_tvorba_pre.py
Normal file
|
@ -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 = [
|
||||||
|
]
|
35
odevzdavatko/migrations/0005_tvorba_relink.py
Normal file
35
odevzdavatko/migrations/0005_tvorba_relink.py
Normal file
|
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -9,7 +9,7 @@ from django.urls import reverse_lazy
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
import seminar.models as am # tvorba
|
import tvorba.models as am
|
||||||
from seminar.models import base as bm
|
from seminar.models import base as bm
|
||||||
|
|
||||||
from odevzdavatko.utils import vzorecek_na_prepocet, inverze_vzorecku_na_prepocet
|
from odevzdavatko.utils import vzorecek_na_prepocet, inverze_vzorecku_na_prepocet
|
||||||
|
|
13
personalni/migrations/0014_tvorba_pre.py
Normal file
13
personalni/migrations/0014_tvorba_pre.py
Normal file
|
@ -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 = [
|
||||||
|
]
|
21
seminar/migrations/0136_tvorba_pre.py
Normal file
21
seminar/migrations/0136_tvorba_pre.py
Normal file
|
@ -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 = [
|
||||||
|
]
|
59
seminar/migrations/0137_tvorba_unmanage.py
Normal file
59
seminar/migrations/0137_tvorba_unmanage.py
Normal file
|
@ -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'},
|
||||||
|
),
|
||||||
|
]
|
|
@ -9,6 +9,7 @@ from personalni.models import Organizator, Resitel, Skola, Prijemce, Osoba
|
||||||
from soustredeni.models import Soustredeni, Soustredeni_Ucastnici, Soustredeni_Organizatori, Konfera, Konfery_Ucastnici
|
from soustredeni.models import Soustredeni, Soustredeni_Ucastnici, Soustredeni_Organizatori, Konfera, Konfery_Ucastnici
|
||||||
from novinky.models import Novinky
|
from novinky.models import Novinky
|
||||||
from odevzdavatko.models import Reseni, PrilohaReseni, Reseni_Resitele, Hodnoceni
|
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
|
# Kvůli migr. 0041
|
||||||
from soustredeni.models import generate_filename_konfera
|
from soustredeni.models import generate_filename_konfera
|
||||||
|
|
|
@ -54,6 +54,7 @@ class Rocnik(SeminarModelBase):
|
||||||
verbose_name = 'Ročník'
|
verbose_name = 'Ročník'
|
||||||
verbose_name_plural = 'Ročníky'
|
verbose_name_plural = 'Ročníky'
|
||||||
ordering = ['-rocnik']
|
ordering = ['-rocnik']
|
||||||
|
managed = False
|
||||||
|
|
||||||
# Interní ID
|
# Interní ID
|
||||||
id = models.AutoField(primary_key = True)
|
id = models.AutoField(primary_key = True)
|
||||||
|
@ -144,11 +145,12 @@ class Cislo(SeminarModelBase):
|
||||||
verbose_name = 'Číslo'
|
verbose_name = 'Číslo'
|
||||||
verbose_name_plural = 'Čísla'
|
verbose_name_plural = 'Čísla'
|
||||||
ordering = ['-rocnik__rocnik', '-poradi']
|
ordering = ['-rocnik__rocnik', '-poradi']
|
||||||
|
managed = False
|
||||||
|
|
||||||
# Interní ID
|
# Interní ID
|
||||||
id = models.AutoField(primary_key = True)
|
id = models.AutoField(primary_key = True)
|
||||||
|
|
||||||
rocnik = models.ForeignKey(Rocnik, verbose_name='ročník', related_name='cisla',
|
rocnik = models.ForeignKey(Rocnik, verbose_name='ročník', related_name='cisla_old',
|
||||||
db_index=True,on_delete=models.PROTECT)
|
db_index=True,on_delete=models.PROTECT)
|
||||||
|
|
||||||
poradi = models.CharField('název čísla', max_length=32, db_index=True,
|
poradi = models.CharField('název čísla', max_length=32, db_index=True,
|
||||||
|
@ -338,6 +340,7 @@ class Deadline(SeminarModelBase):
|
||||||
verbose_name = 'Deadline'
|
verbose_name = 'Deadline'
|
||||||
verbose_name_plural = 'Deadliny'
|
verbose_name_plural = 'Deadliny'
|
||||||
ordering = ['deadline']
|
ordering = ['deadline']
|
||||||
|
managed = False
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
@ -349,7 +352,7 @@ class Deadline(SeminarModelBase):
|
||||||
deadline = models.DateTimeField(blank=False, default=timezone.make_aware(datetime.datetime.combine(timezone.now(), datetime.time.max)))
|
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',
|
cislo = models.ForeignKey(Cislo, verbose_name='deadline v čísle',
|
||||||
related_name='deadline_v_cisle', blank=False,
|
related_name='deadline_v_cisle_old', blank=False,
|
||||||
on_delete=models.CASCADE)
|
on_delete=models.CASCADE)
|
||||||
|
|
||||||
TYP_CISLA = 'cisla'
|
TYP_CISLA = 'cisla'
|
||||||
|
@ -400,16 +403,31 @@ class ZmrazenaVysledkovka(SeminarModelBase):
|
||||||
db_table = 'seminar_vysledkovky'
|
db_table = 'seminar_vysledkovky'
|
||||||
verbose_name = 'Zmražená výsledkovka'
|
verbose_name = 'Zmražená výsledkovka'
|
||||||
verbose_name_plural = 'Zmražené výsledkovky'
|
verbose_name_plural = 'Zmražené výsledkovky'
|
||||||
|
managed = False
|
||||||
|
|
||||||
deadline = models.OneToOneField(
|
deadline = models.OneToOneField(
|
||||||
Deadline,
|
Deadline,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
primary_key=True,
|
primary_key=True,
|
||||||
related_name="vysledkovka_v_deadlinu"
|
related_name="vysledkovka_v_deadlinu_old"
|
||||||
)
|
)
|
||||||
|
|
||||||
html = models.TextField(null=False, blank=False)
|
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)
|
@reversion.register(ignore_duplicates=True)
|
||||||
# Pozor na následující řádek. *Nekrmit, asi kouše!*
|
# Pozor na následující řádek. *Nekrmit, asi kouše!*
|
||||||
|
@ -426,6 +444,7 @@ class Problem(SeminarModelBase,PolymorphicModel):
|
||||||
verbose_name = 'Problém'
|
verbose_name = 'Problém'
|
||||||
verbose_name_plural = 'Problémy'
|
verbose_name_plural = 'Problémy'
|
||||||
ordering = ['nazev']
|
ordering = ['nazev']
|
||||||
|
managed = False
|
||||||
|
|
||||||
# Interní ID
|
# Interní ID
|
||||||
id = models.AutoField(primary_key = True)
|
id = models.AutoField(primary_key = True)
|
||||||
|
@ -435,7 +454,7 @@ class Problem(SeminarModelBase,PolymorphicModel):
|
||||||
|
|
||||||
# Problém má podproblémy
|
# Problém má podproblémy
|
||||||
nadproblem = models.ForeignKey('self', verbose_name='nadřazený problém',
|
nadproblem = models.ForeignKey('self', verbose_name='nadřazený problém',
|
||||||
related_name='podproblem', null=True, blank=True,
|
related_name='podproblem_old', null=True, blank=True,
|
||||||
on_delete=models.SET_NULL)
|
on_delete=models.SET_NULL)
|
||||||
|
|
||||||
STAV_NAVRH = 'navrh'
|
STAV_NAVRH = 'navrh'
|
||||||
|
@ -451,22 +470,22 @@ class Problem(SeminarModelBase,PolymorphicModel):
|
||||||
stav = models.CharField('stav problému', max_length=32, choices=STAV_CHOICES, blank=False, default=STAV_NAVRH)
|
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)
|
# 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í',
|
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)
|
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,
|
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 ...')
|
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',
|
autor = models.ForeignKey(Organizator, verbose_name='autor problému',
|
||||||
related_name='autor_problemu_%(class)s', null=True, blank=True,
|
related_name='autor_problemu_%(class)s_old', null=True, blank=True,
|
||||||
on_delete=models.SET_NULL)
|
on_delete=models.SET_NULL)
|
||||||
|
|
||||||
garant = models.ForeignKey(Organizator, verbose_name='garant zadaného problému',
|
garant = models.ForeignKey(Organizator, verbose_name='garant zadaného problému',
|
||||||
related_name='garant_problemu_%(class)s', null=True, blank=True,
|
related_name='garant_problemu_%(class)s_old', null=True, blank=True,
|
||||||
on_delete=models.SET_NULL)
|
on_delete=models.SET_NULL)
|
||||||
|
|
||||||
opravovatele = models.ManyToManyField(Organizator, verbose_name='opravovatelé',
|
opravovatele = models.ManyToManyField(Organizator, verbose_name='opravovatelé',
|
||||||
blank=True, related_name='opravovatele_%(class)s')
|
blank=True, related_name='opravovatele_%(class)s_old', through=Problemy_Opravovatele)
|
||||||
|
|
||||||
kod = models.CharField('lokální kód', max_length=32, blank=True, default='',
|
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')
|
help_text='Číslo/kód úlohy v čísle nebo kód tématu/článku/seriálu v ročníku')
|
||||||
|
@ -545,6 +564,7 @@ class Tema(Problem):
|
||||||
db_table = 'seminar_temata'
|
db_table = 'seminar_temata'
|
||||||
verbose_name = 'Téma'
|
verbose_name = 'Téma'
|
||||||
verbose_name_plural = 'Témata'
|
verbose_name_plural = 'Témata'
|
||||||
|
managed = False
|
||||||
|
|
||||||
TEMA_TEMA = 'tema'
|
TEMA_TEMA = 'tema'
|
||||||
TEMA_SERIAL = 'serial'
|
TEMA_SERIAL = 'serial'
|
||||||
|
@ -555,7 +575,7 @@ class Tema(Problem):
|
||||||
tema_typ = models.CharField('Typ tématu', max_length=16, choices=TEMA_CHOICES,
|
tema_typ = models.CharField('Typ tématu', max_length=16, choices=TEMA_CHOICES,
|
||||||
blank=False, default=TEMA_TEMA)
|
blank=False, default=TEMA_TEMA)
|
||||||
|
|
||||||
rocnik = models.ForeignKey(Rocnik, verbose_name='ročník',related_name='temata',blank=True, null=True,
|
rocnik = models.ForeignKey(Rocnik, verbose_name='ročník',related_name='temata_old',blank=True, null=True,
|
||||||
on_delete=models.PROTECT)
|
on_delete=models.PROTECT)
|
||||||
|
|
||||||
abstrakt = models.TextField('Abstrakt na rozcestník', blank=True)
|
abstrakt = models.TextField('Abstrakt na rozcestník', blank=True)
|
||||||
|
@ -592,9 +612,10 @@ class Clanek(Problem):
|
||||||
db_table = 'seminar_clanky'
|
db_table = 'seminar_clanky'
|
||||||
verbose_name = 'Článek'
|
verbose_name = 'Článek'
|
||||||
verbose_name_plural = 'Články'
|
verbose_name_plural = 'Články'
|
||||||
|
managed = False
|
||||||
|
|
||||||
cislo = models.ForeignKey(Cislo, blank=True, null=True, on_delete=models.PROTECT,
|
cislo = models.ForeignKey(Cislo, blank=True, null=True, on_delete=models.PROTECT,
|
||||||
verbose_name='číslo vydání', related_name='vydane_clanky')
|
verbose_name='číslo vydání', related_name='vydane_clanky_old')
|
||||||
|
|
||||||
strana = models.PositiveIntegerField(verbose_name="první strana", blank=True, null=True)
|
strana = models.PositiveIntegerField(verbose_name="první strana", blank=True, null=True)
|
||||||
|
|
||||||
|
@ -617,15 +638,16 @@ class Uloha(Problem):
|
||||||
db_table = 'seminar_ulohy'
|
db_table = 'seminar_ulohy'
|
||||||
verbose_name = 'Úloha'
|
verbose_name = 'Úloha'
|
||||||
verbose_name_plural = 'Úlohy'
|
verbose_name_plural = 'Úlohy'
|
||||||
|
managed = False
|
||||||
|
|
||||||
cislo_zadani = models.ForeignKey(Cislo, verbose_name='číslo zadání', blank=True,
|
cislo_zadani = models.ForeignKey(Cislo, verbose_name='číslo zadání', blank=True,
|
||||||
null=True, related_name='zadane_ulohy', on_delete=models.PROTECT)
|
null=True, related_name='zadane_ulohy_old', on_delete=models.PROTECT)
|
||||||
|
|
||||||
cislo_deadline = models.ForeignKey(Cislo, verbose_name='číslo deadlinu', blank=True,
|
cislo_deadline = models.ForeignKey(Cislo, verbose_name='číslo deadlinu', blank=True,
|
||||||
null=True, related_name='deadlinove_ulohy', on_delete=models.PROTECT)
|
null=True, related_name='deadlinove_ulohy_old', on_delete=models.PROTECT)
|
||||||
|
|
||||||
cislo_reseni = models.ForeignKey(Cislo, verbose_name='číslo řešení', blank=True,
|
cislo_reseni = models.ForeignKey(Cislo, verbose_name='číslo řešení', blank=True,
|
||||||
null=True, related_name='resene_ulohy',
|
null=True, related_name='resene_ulohy_old',
|
||||||
help_text='Číslo s řešením úlohy, jen pro úlohy',
|
help_text='Číslo s řešením úlohy, jen pro úlohy',
|
||||||
on_delete=models.PROTECT)
|
on_delete=models.PROTECT)
|
||||||
|
|
||||||
|
@ -683,6 +705,7 @@ class Pohadka(SeminarModelBase):
|
||||||
verbose_name = 'Pohádka'
|
verbose_name = 'Pohádka'
|
||||||
verbose_name_plural = 'Pohádky'
|
verbose_name_plural = 'Pohádky'
|
||||||
ordering = ['vytvoreno']
|
ordering = ['vytvoreno']
|
||||||
|
managed = False
|
||||||
|
|
||||||
# Interní ID
|
# Interní ID
|
||||||
id = models.AutoField(primary_key=True)
|
id = models.AutoField(primary_key=True)
|
||||||
|
@ -694,7 +717,8 @@ class Pohadka(SeminarModelBase):
|
||||||
# Při nahrávání z TeXu není vyplnění vyžadováno, v adminu je
|
# Při nahrávání z TeXu není vyplnění vyžadováno, v adminu je
|
||||||
null=True,
|
null=True,
|
||||||
blank=False,
|
blank=False,
|
||||||
on_delete=models.SET_NULL
|
on_delete=models.SET_NULL,
|
||||||
|
related_name='awawa3_old',
|
||||||
)
|
)
|
||||||
|
|
||||||
vytvoreno = models.DateTimeField(
|
vytvoreno = models.DateTimeField(
|
||||||
|
|
13
soustredeni/migrations/0004_tvorba_pre.py
Normal file
13
soustredeni/migrations/0004_tvorba_pre.py
Normal file
|
@ -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 = [
|
||||||
|
]
|
25
soustredeni/migrations/0005_tvorba_relink.py
Normal file
25
soustredeni/migrations/0005_tvorba_relink.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# 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 = [
|
||||||
|
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'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -10,7 +10,7 @@ from django.conf import settings
|
||||||
from personalni.models import Resitel, Organizator
|
from personalni.models import Resitel, Organizator
|
||||||
|
|
||||||
from seminar.models.base import SeminarModelBase
|
from seminar.models.base import SeminarModelBase
|
||||||
import seminar.models as am # tvorba
|
import tvorba.models as am
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
3
split-apps-meta/polymorphic
Normal file
3
split-apps-meta/polymorphic
Normal file
|
@ -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?)
|
|
@ -9,7 +9,7 @@ from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
import soustredeni.models
|
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(Rocnik)
|
||||||
admin.site.register(ZmrazenaVysledkovka)
|
admin.site.register(ZmrazenaVysledkovka)
|
||||||
|
|
197
tvorba/migrations/0001_tvorba_create.py
Normal file
197
tvorba/migrations/0001_tvorba_create.py
Normal file
|
@ -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),
|
||||||
|
]
|
717
tvorba/models.py
Normal file
717
tvorba/models.py
Normal file
|
@ -0,0 +1,717 @@
|
||||||
|
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 import SeminarModelBase, 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']
|
||||||
|
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')
|
||||||
|
|
||||||
|
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',
|
||||||
|
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']
|
||||||
|
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', 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
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']
|
||||||
|
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', 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'<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',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')
|
||||||
|
|
||||||
|
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', 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'<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,
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
13
various/migrations/0004_tvorba_pre.py
Normal file
13
various/migrations/0004_tvorba_pre.py
Normal file
|
@ -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 = [
|
||||||
|
]
|
20
various/migrations/0005_tvorba_relink.py
Normal file
20
various/migrations/0005_tvorba_relink.py
Normal file
|
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -3,7 +3,7 @@ from django.db import models
|
||||||
from reversion import revisions as reversion
|
from reversion import revisions as reversion
|
||||||
from solo.models import SingletonModel
|
from solo.models import SingletonModel
|
||||||
|
|
||||||
from seminar.models import Cislo
|
from tvorba.models import Cislo
|
||||||
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue