import logging from django.db import models from django.urls import reverse from django.contrib.contenttypes.models import ContentType 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 seminar.models import SeminarModelBase from personalni.models import Organizator from odevzdavatko.models import Reseni logger = logging.getLogger(__name__) import tvorba.models as am class TreeNode(PolymorphicModel): class Meta: db_table = "seminar_nodes_treenode" verbose_name = "TreeNode" verbose_name_plural = "TreeNody" # TODO: Nechceme radši jako root vyžadovat přímo RocnikNode? root = models.ForeignKey('TreeNode', related_name="potomci_set", null = True, blank = False, on_delete = models.SET_NULL, # Vrcholy s null kořenem jsou sirotci bez ročníku verbose_name="kořen stromu") first_child = models.OneToOneField('TreeNode', related_name='father_of_first', null = True, blank = True, on_delete=models.SET_NULL, verbose_name="první potomek") succ = models.OneToOneField('TreeNode', related_name="prev", null = True, blank = True, on_delete=models.SET_NULL, verbose_name="další element na stejné úrovni") nazev = models.TextField("název tohoto node", help_text = "Tento název se zobrazuje v nabídkách pro výběr vhodného TreeNode", blank=False, null=True) # Nezveřejnitelný název na stránky - pouze do adminu zajimave = models.BooleanField(default = False, verbose_name = "Zajímavé", help_text = "Zobrazí se daná věc na rozcestníku témátek") srolovatelne = models.BooleanField(null = True, blank = True, verbose_name = "Srolovatelné", help_text = "Bude na stránce témátka možnost tuto položku skrýt") def getOdkazStr(self): # String na rozcestník return self.first_child.getOdkazStr() def getOdkaz(self): # ID HTML tagu, na který se bude scrollovat #{{self.getOdkaz}} # Jsem si vědom, že tu potenciálně vznikají kolize. # Přijdou mi natolik nepravděpodobné, že je neřeším # Chtěl jsem ale hezké odkazy string = unidecode(self.getOdkazStr()) returnVal = "" i = 0 while len(returnVal) < 16: # Max 15 znaků if i == len(string): break if string[i] == " ": returnVal += "-" if string[i].isalnum(): returnVal += string[i].lower() i += 1 return returnVal def __str__(self): if self.nazev: return self.nazev else: #TODO: logování return "Nepojmenovaný Treenode" def save(self, *args, **kwargs): self.aktualizuj_nazev() super().save(*args, **kwargs) def aktualizuj_nazev(self): raise NotImplementedError("Pokus o aktualizaci názvu obecného TreeNode místo konkrétní instance") def get_admin_url(self): content_type = ContentType.objects.get_for_model(self.__class__) return reverse("admin:%s_%s_change" % (content_type.app_label, content_type.model), args=(self.id,)) class RocnikNode(TreeNode): class Meta: db_table = 'seminar_nodes_rocnik' verbose_name = 'Ročník (Node)' verbose_name_plural = 'Ročníky (Node)' rocnik = models.OneToOneField(am.Rocnik, on_delete = models.PROTECT, # Pokud chci mazat ročník, musím si Node pořešit ručně verbose_name = "ročník") def aktualizuj_nazev(self): self.nazev = "RocnikNode: "+str(self.rocnik) class CisloNode(TreeNode): class Meta: db_table = 'seminar_nodes_cislo' verbose_name = 'Číslo (Node)' verbose_name_plural = 'Čísla (Node)' cislo = models.OneToOneField(am.Cislo, on_delete = models.PROTECT, # Pokud chci mazat číslo, musím si Node pořešit ručně verbose_name = "číslo") def aktualizuj_nazev(self): self.nazev = "CisloNode: "+str(self.cislo) def getOdkazStr(self): return "Číslo " + str(self.cislo) class MezicisloNode(TreeNode): class Meta: db_table = 'seminar_nodes_mezicislo' verbose_name = 'Mezičíslo (Node)' verbose_name_plural = 'Mezičísla (Node)' # TODO: Využít TreeLib def aktualizuj_nazev(self): from treenode.treelib import safe_pred if safe_pred(self) is not None: if (self.prev.get_real_instance_class() != CisloNode and self.prev.get_real_instance_class() != MezicisloNode): raise ValueError("Předchůdce není číslo!") posledni = self.prev.cislo self.nazev = "MezicisloNode: Mezičíslo po čísle"+str(posledni) elif self.root: if self.root.get_real_instance_class() != RocnikNode: raise ValueError("Kořen stromu není ročník!") rocnik = self.root.rocnik self.nazev = "MezicisloNode: První mezičíslo ročníku "+str(rocnik) else: print("!!!!! Nějaké neidentifikované mezičíslo !!!!!") self.nazev = "MezicisloNode: Neidentifikovatelné mezičíslo!" def getOdkazStr(self): return "Obsah dostupný pouze na webu" class TemaVCisleNode(TreeNode): """ Obsahuje příspěvky k tématu v daném čísle """ class Meta: db_table = 'seminar_nodes_temavcisle' verbose_name = 'Téma v čísle (Node)' verbose_name_plural = 'Témata v čísle (Node)' tema = models.ForeignKey(am.Tema, on_delete=models.PROTECT, # Pokud chci mazat téma, musím si Node pořešit ručně verbose_name = "téma v čísle") def aktualizuj_nazev(self): self.nazev = "TemaVCisleNode: "+str(self.tema) def getOdkazStr(self): return str(self.tema) class OrgTextNode(TreeNode): class Meta: db_table = 'seminar_nodes_orgtextnode' verbose_name = 'Organizátorský článek (Node)' verbose_name_plural = 'Organizátorské články (Node)' organizator = models.ForeignKey(Organizator, null=False, blank=False, on_delete=models.DO_NOTHING, verbose_name="Organizátor", ) org_verejny = models.BooleanField(default = True, verbose_name = "Org je veřejný?", help_text = "Pokud ano, bude org pod článkem podepsaný", null=False, ) def aktualizuj_nazev(self): return f"OrgTextNode začínající následujícim: {self.first_child.nazev}" # FIXME!!! #def getOdkazStr(self): # return str(self.clanek) class UlohaZadaniNode(TreeNode): class Meta: db_table = 'seminar_nodes_uloha_zadani' verbose_name = 'Zadání úlohy (Node)' verbose_name_plural = 'Zadání úloh (Node)' uloha = models.OneToOneField(am.Uloha, on_delete=models.PROTECT, # Pokud chci mazat téma, musím si Node pořešit ručně verbose_name = "úloha", null=True, blank=False) def aktualizuj_nazev(self): self.nazev = "UlohaZadaniNode: "+str(self.uloha) def getOdkazStr(self): return str(self.uloha) class PohadkaNode(TreeNode): class Meta: db_table = 'seminar_nodes_pohadka' verbose_name = 'Pohádka (Node)' verbose_name_plural = 'Pohádky (Node)' pohadka = models.OneToOneField(am.Pohadka, on_delete=models.PROTECT, # Pokud chci mazat pohádku, musím si Node pořešit ručně verbose_name = "pohádka", ) def aktualizuj_nazev(self): self.nazev = "PohadkaNode: "+str(self.pohadka) class UlohaVzorakNode(TreeNode): class Meta: db_table = 'seminar_nodes_uloha_vzorak' verbose_name = 'Vzorák úlohy (Node)' verbose_name_plural = 'Vzoráky úloh (Node)' uloha = models.OneToOneField(am.Uloha, on_delete=models.PROTECT, # Pokud chci mazat téma, musím si Node pořešit ručně verbose_name = "úloha", null=True, blank=False) def aktualizuj_nazev(self): self.nazev = "UlohaVzorakNode: "+str(self.uloha) def getOdkazStr(self): return str(self.uloha) 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) class Text(SeminarModelBase): class Meta: db_table = 'seminar_texty' verbose_name = 'text' verbose_name_plural = 'texty' na_web = models.TextField( 'text na web', blank=True, help_text='Text ke zveřejnění na webu') do_cisla = models.TextField( 'text do čísla', blank=True, help_text='Text ke zveřejnění v čísle') # má OneToOneField s: # Reseni (je u něj jako reseni_cele) # obrázky mají návaznost opačným směrem (vazba z druhé strany) def save(self, *args, **kwargs): super().save(*args, **kwargs) # *Node.save() aktualizuje název *Nodu. for tn in self.textnode_set.all(): tn.save() def __str__(self): return str(self.na_web)[:20] class TextNode(TreeNode): class Meta: db_table = 'seminar_nodes_obsah' verbose_name = 'Text (Node)' verbose_name_plural = 'Text (Node)' text = models.ForeignKey(Text, on_delete=models.CASCADE, verbose_name = 'text') def aktualizuj_nazev(self): self.nazev = "TextNode: "+str(self.text) def getOdkazStr(self): return str(self.text) class CastNode(TreeNode): class Meta: db_table = 'seminar_nodes_cast' verbose_name = 'Část (Node)' verbose_name_plural = 'Části (Node)' nadpis = models.CharField('Nadpis', max_length=100, help_text = 'Nadpis podvěšené části obsahu') def aktualizuj_nazev(self): self.nazev = "CastNode: "+str(self.nadpis) def getOdkazStr(self): return str(self.nadpis) class Obrazek(SeminarModelBase): class Meta: db_table = 'seminar_obrazky' verbose_name = 'obrázek' verbose_name_plural = 'obrázky' # Interní ID id = models.AutoField(primary_key=True) na_web = models.ImageField( 'obrázek na web', upload_to='obrazky/%Y/%m/%d/', null=True, blank=True) text = models.ForeignKey( Text, verbose_name='text', help_text='text, ve kterém se obrázek vyskytuje', null=False, blank=False, on_delete=models.CASCADE) do_cisla_barevny = models.FileField( 'barevný obrázek do čísla', help_text='Barevná verze obrázku do čísla', upload_to='obrazky/%Y/%m/%d/', blank=True, null=True) do_cisla_cernobily = models.FileField( 'černobílý obrázek do čísla', help_text='Černobílá verze obrázku do čísla', upload_to='obrazky/%Y/%m/%d/', blank=True, null=True) # TODO placement hint - chci ho tady / pred textem / za textem