Pavel 'LEdoian' Turinsky
2 weeks ago
18 changed files with 1172 additions and 18 deletions
@ -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 = [ |
|||
] |
@ -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'), |
|||
), |
|||
] |
@ -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 = [ |
|||
] |
@ -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 = [ |
|||
] |
@ -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'}, |
|||
), |
|||
] |
@ -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 = [ |
|||
] |
@ -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'), |
|||
), |
|||
] |
@ -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?) |
@ -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), |
|||
] |
@ -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 |
|||
|
@ -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 = [ |
|||
] |
@ -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'), |
|||
), |
|||
] |
Loading…
Reference in new issue