import decimal 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 personalni as pm from seminar.models import treenode as tm from seminar.models import base as bm @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'] # 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(pm.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 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í' # 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)') # TODO najít správné místo @staticmethod def vzorecek_na_prepocet(body, resitelu): return body * 3 / (resitelu + 2) # TODO najít správné místo @staticmethod def inverze_vzorecku_na_prepocet(body: decimal.Decimal, resitelu) -> decimal.Decimal: return round(body * (resitelu + 2) / 3, 1) @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 self.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 = self.vzorecek_na_prepocet(value, self.reseni.resitele.count()) @property def body_neprepocitane_celkem(self): if self.body_celkem is None: return None return self.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 = self.vzorecek_na_prepocet(value, self.reseni.resitele.count()) @property def body_max(self): if self.body_neprepocitane_max is None: return None return self.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'] # 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(pm.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(tm.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)