From 970352322c057965c698ba734b5de7eb0e592556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Sun, 7 Nov 2021 10:47:39 +0100 Subject: [PATCH] =?UTF-8?q?Move=20treenode=20modely=20do=20vlastn=C3=ADho?= =?UTF-8?q?=20souboru?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/models/__init__.py | 1 + seminar/models/models_all.py | 257 +------------------------------ seminar/models/odevzdavatko.py | 3 +- seminar/models/treenode.py | 266 +++++++++++++++++++++++++++++++++ 4 files changed, 274 insertions(+), 253 deletions(-) create mode 100644 seminar/models/treenode.py diff --git a/seminar/models/__init__.py b/seminar/models/__init__.py index cb55c941..a78eccf7 100644 --- a/seminar/models/__init__.py +++ b/seminar/models/__init__.py @@ -4,3 +4,4 @@ from .base import * from .personalni import * from .soustredeni import * from .pomocne import * +from .treenode import * diff --git a/seminar/models/models_all.py b/seminar/models/models_all.py index 032139ee..47777bb4 100644 --- a/seminar/models/models_all.py +++ b/seminar/models/models_all.py @@ -12,7 +12,6 @@ from django.conf import settings from django.urls import reverse from django.core.cache import cache from django.core.exceptions import ObjectDoesNotExist, ValidationError -from django.contrib.contenttypes.models import ContentType from django.utils.text import get_valid_filename from imagekit.models import ImageSpecField from imagekit.processors import ResizeToFit @@ -23,7 +22,7 @@ from taggit.managers import TaggableManager from reversion import revisions as reversion -from seminar.utils import roman, FirstTagParser # Pro získání úryvku z TextNode +from seminar.utils import roman from seminar.utils import hlavni_problem from treenode import treelib @@ -37,7 +36,6 @@ from seminar.utils import aktivniResitele from . import personalni as pm from .base import SeminarModelBase -from .pomocne import Text logger = logging.getLogger(__name__) @@ -304,6 +302,7 @@ class Cislo(SeminarModelBase): except ObjectDoesNotExist: # Neexistující *Node nemá smysl aktualizovat, ale je potřeba ho naopak vyrobit logger.warning(f'Číslo {self} nemělo ČísloNode, vyrábím…') + from seminar.models.treenode import CisloNode CisloNode.objects.create(cislo=self) def clean(self): @@ -480,6 +479,7 @@ class Tema(Problem): def cislo_node(self): tema_node_set = self.temavcislenode_set.all() tema_cisla_vyskyt = [] + from seminar.models.treenode import CisloNode for tn in tema_node_set: tema_cisla_vyskyt.append( treelib.get_upper_node_of_type(tn, CisloNode).cislo) @@ -557,7 +557,8 @@ class Uloha(Problem): pass def cislo_node(self): - zadani_node = self.ulohazadaninode + zadani_node = self.ulohazadaninode + from seminar.models.treenode import CisloNode return treelib.get_upper_node_of_type(zadani_node, CisloNode) @@ -619,254 +620,6 @@ class Pohadka(SeminarModelBase): pass -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(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(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(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(pm.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(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(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(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 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) - - @reversion.register(ignore_duplicates=True) class Nastaveni(SingletonModel): diff --git a/seminar/models/odevzdavatko.py b/seminar/models/odevzdavatko.py index f922e19a..e450712e 100644 --- a/seminar/models/odevzdavatko.py +++ b/seminar/models/odevzdavatko.py @@ -11,6 +11,7 @@ from django.conf import settings from seminar.models import models_all as am from seminar.models import personalni as pm +from seminar.models import treenode as tm from seminar.models.base import SeminarModelBase @@ -173,7 +174,7 @@ class Reseni_Resitele(models.Model): return '{} od {}'.format(self.reseni, self.resitel) # NOTE: Poteciální DB HOG bez select_related -class ReseniNode(am.TreeNode): +class ReseniNode(tm.TreeNode): class Meta: db_table = 'seminar_nodes_otistene_reseni' verbose_name = 'Otištěné řešení (Node)' diff --git a/seminar/models/treenode.py b/seminar/models/treenode.py new file mode 100644 index 00000000..012fd097 --- /dev/null +++ b/seminar/models/treenode.py @@ -0,0 +1,266 @@ +# -*- coding: utf-8 -*- +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 . import personalni as pm + +from .pomocne import Text + +logger = logging.getLogger(__name__) + +from seminar.models import models_all 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(pm.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 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)