Pavel "LEdoian" Turinsky
4 weeks ago
5 changed files with 347 additions and 244 deletions
@ -0,0 +1,99 @@ |
|||||
|
# Generated by Django 4.2.13 on 2024-10-22 22:51 |
||||
|
|
||||
|
from django.db import migrations, models |
||||
|
import django.utils.timezone |
||||
|
import odevzdavatko.models |
||||
|
|
||||
|
def nastav_nove_contenttypes(apps, schema_editor): |
||||
|
ContentType = apps.get_model('contenttypes', 'ContentType') |
||||
|
for m in ('reseni', 'hodnoceni', 'reseni_resitele', 'prilohareseni'): |
||||
|
ContentType.objects.filter(app_label='seminar', model=m).update(app_label='odevzdavatko') |
||||
|
|
||||
|
def nastav_stare_contenttypes(apps, schema_editor): |
||||
|
ContentType = apps.get_model('contenttypes', 'ContentType') |
||||
|
for m in ('reseni', 'hodnoceni', 'reseni_resitele', 'prilohareseni'): |
||||
|
ContentType.objects.filter(app_label='odevzdavatko', model=m).update(app_label='seminar') |
||||
|
|
||||
|
class Migration(migrations.Migration): |
||||
|
|
||||
|
initial = True |
||||
|
|
||||
|
dependencies = [ |
||||
|
('seminar', '0132_unmanage_odevzdavatko'), |
||||
|
] |
||||
|
|
||||
|
operations = [ |
||||
|
migrations.CreateModel( |
||||
|
name='Hodnoceni', |
||||
|
fields=[ |
||||
|
('id', models.AutoField(primary_key=True, serialize=False)), |
||||
|
('body', models.DecimalField(blank=True, decimal_places=1, max_digits=8, null=True, verbose_name='body')), |
||||
|
('feedback', models.TextField(blank=True, default='', help_text='Zpětná vazba řešiteli (plain text)', verbose_name='zpětná vazba')), |
||||
|
('cislo_body', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='hodnoceni', to='seminar.cislo', verbose_name='číslo pro body')), |
||||
|
('deadline_body', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='hodnoceni', to='seminar.deadline', verbose_name='deadline pro body')), |
||||
|
('problem', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='hodnoceni', to='seminar.problem', verbose_name='problém')), |
||||
|
('reseni', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='odevzdavatko.reseni', verbose_name='řešení')), |
||||
|
], |
||||
|
options={ |
||||
|
'verbose_name': 'Hodnocení', |
||||
|
'verbose_name_plural': 'Hodnocení', |
||||
|
'db_table': 'seminar_hodnoceni', |
||||
|
'managed': False, |
||||
|
}, |
||||
|
), |
||||
|
migrations.CreateModel( |
||||
|
name='PrilohaReseni', |
||||
|
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')), |
||||
|
('soubor', models.FileField(upload_to=odevzdavatko.models.generate_filename, verbose_name='soubor')), |
||||
|
('poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka k příloze řešení (plain text), např. o původu', verbose_name='neveřejná poznámka')), |
||||
|
('res_poznamka', models.TextField(blank=True, help_text='Poznámka k příloze řešení, např. co daný soubor obsahuje', verbose_name='poznámka řešitele')), |
||||
|
('reseni', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='prilohy', to='odevzdavatko.reseni', verbose_name='řešení')), |
||||
|
], |
||||
|
options={ |
||||
|
'verbose_name': 'Příloha řešení', |
||||
|
'verbose_name_plural': 'Přílohy řešení', |
||||
|
'db_table': 'seminar_priloha_reseni', |
||||
|
'ordering': ['reseni', 'vytvoreno'], |
||||
|
'managed': False, |
||||
|
}, |
||||
|
), |
||||
|
migrations.CreateModel( |
||||
|
name='Reseni', |
||||
|
fields=[ |
||||
|
('id', models.AutoField(primary_key=True, serialize=False)), |
||||
|
('cas_doruceni', models.DateTimeField(blank=True, default=django.utils.timezone.now, verbose_name='čas_doručení')), |
||||
|
('forma', models.CharField(choices=[('papir', 'Papírové řešení'), ('email', 'Emailem'), ('upload', 'Upload přes web')], default='email', max_length=16, verbose_name='forma řešení')), |
||||
|
('poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka k řešení (plain text)', verbose_name='neveřejná poznámka')), |
||||
|
('zverejneno', models.BooleanField(default=False, help_text='Udává, zda je řešení zveřejněno', verbose_name='řešení zveřejněno')), |
||||
|
('problem', models.ManyToManyField(help_text='Problém', through='odevzdavatko.Hodnoceni', to='seminar.problem', verbose_name='problém')), |
||||
|
('resitele', models.ManyToManyField(help_text='Seznam autorů řešení', through='odevzdavatko.Reseni_Resitele', to='personalni.resitel', verbose_name='autoři řešení')), |
||||
|
('text_cely', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='reseni_cely_set', to='seminar.reseninode', verbose_name='Plná verze textu řešení')), |
||||
|
], |
||||
|
options={ |
||||
|
'verbose_name': 'Řešení', |
||||
|
'verbose_name_plural': 'Řešení', |
||||
|
'db_table': 'seminar_reseni', |
||||
|
'ordering': ['-cas_doruceni'], |
||||
|
'managed': False, |
||||
|
}, |
||||
|
), |
||||
|
migrations.CreateModel( |
||||
|
name='Reseni_Resitele', |
||||
|
fields=[ |
||||
|
('id', models.AutoField(primary_key=True, serialize=False)), |
||||
|
('reseni', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='odevzdavatko.reseni', verbose_name='řešení')), |
||||
|
('resitele', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='personalni.resitel', verbose_name='řešitel')), |
||||
|
], |
||||
|
options={ |
||||
|
'verbose_name': 'Řešení řešitelů', |
||||
|
'verbose_name_plural': 'Řešení řešitelů', |
||||
|
'db_table': 'seminar_reseni_resitele', |
||||
|
'ordering': ['reseni', 'resitele'], |
||||
|
'managed': False, |
||||
|
}, |
||||
|
), |
||||
|
migrations.RunPython(nastav_nove_contenttypes, nastav_stare_contenttypes), |
||||
|
|
||||
|
] |
@ -0,0 +1,243 @@ |
|||||
|
import os |
||||
|
|
||||
|
import reversion |
||||
|
|
||||
|
from django.contrib.sites.shortcuts import get_current_site |
||||
|
from django.db import models |
||||
|
from django.db.models import Sum |
||||
|
from django.urls import reverse_lazy |
||||
|
from django.utils import timezone |
||||
|
from django.conf import settings |
||||
|
|
||||
|
from seminar.models import tvorba as am |
||||
|
from seminar.models import base as bm |
||||
|
|
||||
|
from odevzdavatko.utils import vzorecek_na_prepocet, inverze_vzorecku_na_prepocet |
||||
|
from personalni.models import Resitel |
||||
|
|
||||
|
@reversion.register(ignore_duplicates=True) |
||||
|
class Reseni(bm.SeminarModelBase): |
||||
|
|
||||
|
class Meta: |
||||
|
db_table = 'seminar_reseni' |
||||
|
verbose_name = 'Řešení' |
||||
|
verbose_name_plural = 'Řešení' |
||||
|
#ordering = ['-problem', 'resitele'] # FIXME: Takhle to chceme, ale nefunguje to. |
||||
|
ordering = ['-cas_doruceni'] |
||||
|
managed = False |
||||
|
|
||||
|
# Interní ID |
||||
|
id = models.AutoField(primary_key = True) |
||||
|
|
||||
|
# Ke každé dvojici řešní a problém existuje nanejvýš jedno hodnocení, doplnění vazby. |
||||
|
problem = models.ManyToManyField(am.Problem, verbose_name='problém', help_text='Problém', |
||||
|
through='Hodnoceni') |
||||
|
|
||||
|
resitele = models.ManyToManyField(Resitel, verbose_name='autoři řešení', |
||||
|
help_text='Seznam autorů řešení', through='Reseni_Resitele') |
||||
|
|
||||
|
|
||||
|
cas_doruceni = models.DateTimeField('čas_doručení', default=timezone.now, blank=True) |
||||
|
|
||||
|
FORMA_PAPIR = 'papir' |
||||
|
FORMA_EMAIL = 'email' |
||||
|
FORMA_UPLOAD = 'upload' |
||||
|
FORMA_CHOICES = [ |
||||
|
(FORMA_PAPIR, 'Papírové řešení'), |
||||
|
(FORMA_EMAIL, 'Emailem'), |
||||
|
(FORMA_UPLOAD, 'Upload přes web'), |
||||
|
] |
||||
|
forma = models.CharField('forma řešení', max_length=16, choices=FORMA_CHOICES, blank=False, |
||||
|
default=FORMA_EMAIL) |
||||
|
|
||||
|
text_cely = models.OneToOneField('seminar.ReseniNode', verbose_name='Plná verze textu řešení', |
||||
|
blank=True, null=True, related_name="reseni_cely_set", |
||||
|
on_delete=models.PROTECT) |
||||
|
|
||||
|
poznamka = models.TextField('neveřejná poznámka', blank=True, |
||||
|
help_text='Neveřejná poznámka k řešení (plain text)') |
||||
|
|
||||
|
zverejneno = models.BooleanField('řešení zveřejněno', default=False, |
||||
|
help_text='Udává, zda je řešení zveřejněno') |
||||
|
|
||||
|
def verejne_url(self): |
||||
|
return str(reverse_lazy('odevzdavatko_detail_reseni', args=[self.id])) |
||||
|
|
||||
|
def absolute_url(self): |
||||
|
return "https://" + str(get_current_site(None)) + self.verejne_url() |
||||
|
|
||||
|
# má OneToOneField s: |
||||
|
# Konfera |
||||
|
|
||||
|
# má ForeignKey s: |
||||
|
# Hodnoceni |
||||
|
|
||||
|
def sum_body(self): |
||||
|
return self.hodnoceni_set.all().aggregate(Sum('body'))["body__sum"] |
||||
|
|
||||
|
def __str__(self): |
||||
|
return "{}({}): {}({})".format(self.resitele.first(),len(self.resitele.all()), self.problem.first() ,len(self.problem.all())) |
||||
|
# NOTE: Potenciální DB HOG (bez select_related) |
||||
|
|
||||
|
def deadline_reseni(self): |
||||
|
return am.Deadline.objects.filter(deadline__gte=self.cas_doruceni).order_by("deadline").first() |
||||
|
|
||||
|
## Pravdepodobne uz nebude potreba: |
||||
|
# def save(self, *args, **kwargs): |
||||
|
# if ((self.cislo_body is None) and (self.problem.cislo_reseni) and |
||||
|
# (self.problem.typ == Problem.TYP_ULOHA)): |
||||
|
# self.cislo_body = self.problem.cislo_reseni |
||||
|
# super(Reseni, self).save(*args, **kwargs) |
||||
|
|
||||
|
class Hodnoceni(bm.SeminarModelBase): |
||||
|
class Meta: |
||||
|
db_table = 'seminar_hodnoceni' |
||||
|
verbose_name = 'Hodnocení' |
||||
|
verbose_name_plural = 'Hodnocení' |
||||
|
managed = False |
||||
|
|
||||
|
# Interní ID |
||||
|
id = models.AutoField(primary_key = True) |
||||
|
|
||||
|
|
||||
|
body = models.DecimalField(max_digits=8, decimal_places=1, verbose_name='body', |
||||
|
blank=True, null=True) |
||||
|
|
||||
|
cislo_body = models.ForeignKey(am.Cislo, verbose_name='číslo pro body', |
||||
|
related_name='hodnoceni', blank=True, null=True, on_delete=models.PROTECT) |
||||
|
|
||||
|
# V ročníku < 26 nastaveno na deadline vygenerovaný pro původní cislo_body |
||||
|
deadline_body = models.ForeignKey(am.Deadline, verbose_name='deadline pro body', |
||||
|
related_name='hodnoceni', blank=True, null=True, on_delete=models.PROTECT) |
||||
|
|
||||
|
reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE) |
||||
|
|
||||
|
problem = models.ForeignKey(am.Problem, verbose_name='problém', |
||||
|
related_name='hodnoceni', on_delete=models.PROTECT) |
||||
|
|
||||
|
feedback = models.TextField('zpětná vazba', blank=True, default='', help_text='Zpětná vazba řešiteli (plain text)') |
||||
|
|
||||
|
@property |
||||
|
def body_celkem(self): |
||||
|
# FIXME řeším jen prvního řešitele. |
||||
|
return Hodnoceni.objects.filter(problem=self.problem, reseni__resitele=self.reseni.resitele.first(), body__isnull=False).aggregate(Sum("body"))["body__sum"] |
||||
|
|
||||
|
@body_celkem.setter |
||||
|
def body_celkem(self, value): |
||||
|
if value is None: |
||||
|
self.body = None |
||||
|
else: |
||||
|
if self.body is None: |
||||
|
self.body = 0 |
||||
|
if self.body_celkem is None: |
||||
|
self.body += value |
||||
|
else: |
||||
|
self.body += value - self.body_celkem |
||||
|
|
||||
|
@property |
||||
|
def body_neprepocitane(self): |
||||
|
if self.body is None: |
||||
|
return None |
||||
|
return inverze_vzorecku_na_prepocet(self.body, self.reseni.resitele.count()) |
||||
|
|
||||
|
@body_neprepocitane.setter |
||||
|
def body_neprepocitane(self, value): |
||||
|
if value is None: |
||||
|
self.body = None |
||||
|
else: |
||||
|
self.body = vzorecek_na_prepocet(value, self.reseni.resitele.count()) |
||||
|
|
||||
|
@property |
||||
|
def body_neprepocitane_celkem(self): |
||||
|
if self.body_celkem is None: |
||||
|
return None |
||||
|
return inverze_vzorecku_na_prepocet(self.body_celkem, self.reseni.resitele.count()) |
||||
|
|
||||
|
@body_neprepocitane_celkem.setter |
||||
|
def body_neprepocitane_celkem(self, value): |
||||
|
if value is None: |
||||
|
self.body = None |
||||
|
else: |
||||
|
self.body_celkem = vzorecek_na_prepocet(value, self.reseni.resitele.count()) |
||||
|
|
||||
|
@property |
||||
|
def body_max(self): |
||||
|
if self.body_neprepocitane_max is None: |
||||
|
return None |
||||
|
return vzorecek_na_prepocet(self.body_neprepocitane_max, self.reseni.resitele.count()) |
||||
|
|
||||
|
@property |
||||
|
def body_neprepocitane_max(self): |
||||
|
if not isinstance(self.problem.get_real_instance(), am.Uloha): |
||||
|
return None |
||||
|
return self.problem.uloha.max_body |
||||
|
|
||||
|
def __str__(self): |
||||
|
return "{}, {}, {}".format(self.problem, self.reseni, self.body) |
||||
|
|
||||
|
def generate_filename(self, filename): |
||||
|
return os.path.join( |
||||
|
settings.SEMINAR_RESENI_DIR, |
||||
|
am.aux_generate_filename(self, filename) |
||||
|
) |
||||
|
|
||||
|
|
||||
|
@reversion.register(ignore_duplicates=True) |
||||
|
class PrilohaReseni(bm.SeminarModelBase): |
||||
|
|
||||
|
class Meta: |
||||
|
db_table = 'seminar_priloha_reseni' |
||||
|
verbose_name = 'Příloha řešení' |
||||
|
verbose_name_plural = 'Přílohy řešení' |
||||
|
ordering = ['reseni', 'vytvoreno'] |
||||
|
managed = False |
||||
|
|
||||
|
# Interní ID |
||||
|
id = models.AutoField(primary_key = True) |
||||
|
|
||||
|
reseni = models.ForeignKey(Reseni, verbose_name='řešení', related_name='prilohy', |
||||
|
on_delete=models.CASCADE) |
||||
|
|
||||
|
vytvoreno = models.DateTimeField('vytvořeno', default=timezone.now, blank=True, editable=False) |
||||
|
|
||||
|
soubor = models.FileField('soubor', upload_to = generate_filename) |
||||
|
|
||||
|
poznamka = models.TextField('neveřejná poznámka', blank=True, |
||||
|
help_text='Neveřejná poznámka k příloze řešení (plain text), např. o původu') |
||||
|
|
||||
|
res_poznamka = models.TextField('poznámka řešitele', blank=True, |
||||
|
help_text='Poznámka k příloze řešení, např. co daný soubor obsahuje') |
||||
|
|
||||
|
def __str__(self): |
||||
|
return str(self.soubor) |
||||
|
|
||||
|
def split(self): |
||||
|
"Vrátí cestu rozsekanou po složkách. To se hodí v templatech" |
||||
|
# Věřím, že tohle funguje, případně použít os.path nebo pathlib. |
||||
|
return self.soubor.url.split('/') |
||||
|
|
||||
|
|
||||
|
# Vazebna tabulka. Mozna se generuje automaticky. |
||||
|
@reversion.register(ignore_duplicates=True) |
||||
|
class Reseni_Resitele(models.Model): |
||||
|
|
||||
|
class Meta: |
||||
|
db_table = 'seminar_reseni_resitele' |
||||
|
verbose_name = 'Řešení řešitelů' |
||||
|
verbose_name_plural = 'Řešení řešitelů' |
||||
|
ordering = ['reseni', 'resitele'] |
||||
|
managed = False |
||||
|
|
||||
|
# Interní ID |
||||
|
id = models.AutoField(primary_key = True) |
||||
|
|
||||
|
resitele = models.ForeignKey(Resitel, verbose_name='řešitel', on_delete=models.PROTECT) |
||||
|
|
||||
|
reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE) |
||||
|
|
||||
|
# podil - jakou merou se ktery resitel podilel na danem reseni |
||||
|
# - pouziti v budoucnu, pokud by resitele nemeli dostat vsichni stejne bodu za spolecne reseni |
||||
|
|
||||
|
def __str__(self): |
||||
|
return '{} od {}'.format(self.reseni, self.resitel) |
||||
|
# NOTE: Poteciální DB HOG bez select_related |
Loading…
Reference in new issue