|
|
|
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.tvorba import Cislo, Deadline, Problem, Uloha, aux_generate_filename
|
|
|
|
from seminar.models.personalni import Resitel
|
|
|
|
from seminar.models.treenode import TreeNode
|
|
|
|
from seminar.models.base import SeminarModelBase
|
|
|
|
|
|
|
|
|
|
|
|
@reversion.register(ignore_duplicates=True)
|
|
|
|
class Reseni(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']
|
|
|
|
|
|
|
|
# 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(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('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 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(SeminarModelBase):
|
|
|
|
class Meta:
|
|
|
|
db_table = 'seminar_hodnoceni'
|
|
|
|
verbose_name = 'Hodnocení'
|
|
|
|
verbose_name_plural = 'Hodnocení'
|
|
|
|
|
|
|
|
# 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(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(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(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
|
|
|
|
from seminar.utils import inverze_vzorecku_na_prepocet
|
|
|
|
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:
|
|
|
|
from seminar.utils import vzorecek_na_prepocet
|
|
|
|
self.body = vzorecek_na_prepocet(value, self.reseni.resitele.count())
|
|
|
|
|
|
|
|
@property
|
|
|
|
def body_neprepocitane_celkem(self):
|
|
|
|
if self.body_celkem is None:
|
|
|
|
return None
|
|
|
|
from seminar.utils import inverze_vzorecku_na_prepocet
|
|
|
|
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:
|
|
|
|
from seminar.utils import vzorecek_na_prepocet
|
|
|
|
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
|
|
|
|
from seminar.utils import vzorecek_na_prepocet
|
|
|
|
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(), 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,
|
|
|
|
aux_generate_filename(self, filename)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@reversion.register(ignore_duplicates=True)
|
|
|
|
class PrilohaReseni(SeminarModelBase):
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
db_table = 'seminar_priloha_reseni'
|
|
|
|
verbose_name = 'Příloha řešení'
|
|
|
|
verbose_name_plural = 'Přílohy řešení'
|
|
|
|
ordering = ['reseni', 'vytvoreno']
|
|
|
|
|
|
|
|
# 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']
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
|
|
|
class ReseniNode(TreeNode):
|
|
|
|
class Meta:
|
|
|
|
db_table = 'seminar_nodes_otistene_reseni'
|
|
|
|
verbose_name = 'Otištěné řešení (Node)'
|
|
|
|
verbose_name_plural = 'Otištěná řešení (Node)'
|
|
|
|
reseni = models.ForeignKey(Reseni,
|
|
|
|
on_delete=models.PROTECT,
|
|
|
|
verbose_name = 'reseni')
|
|
|
|
|
|
|
|
def aktualizuj_nazev(self):
|
|
|
|
self.nazev = "ReseniNode: "+str(self.reseni)
|
|
|
|
|
|
|
|
def getOdkazStr(self):
|
|
|
|
return str(self.reseni)
|
|
|
|
|