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)