diff --git a/seminar/models.py b/seminar/models.py index c045b658..8a4090fa 100644 --- a/seminar/models.py +++ b/seminar/models.py @@ -28,7 +28,6 @@ from reversion import revisions as reversion from seminar.utils import roman, FirstTagParser # Pro získání úryvku z TextNode from unidecode import unidecode # Používám pro získání ID odkazu (ještě je to někde po někom zakomentované) -from seminar.treelib import safe_pred from polymorphic.models import PolymorphicModel @@ -1342,6 +1341,7 @@ class MezicisloNode(TreeNode): # TODO: Využít TreeLib def aktualizuj_nazev(self): + from seminar.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): diff --git a/seminar/treelib.py b/seminar/treelib.py index 95fae9a9..fe4ebce0 100644 --- a/seminar/treelib.py +++ b/seminar/treelib.py @@ -3,6 +3,7 @@ from django.db import transaction # NOTE: node.prev a node.succ jsou implementovány přímo v models.TreeNode # TODO: Všechny tyto funkce se naivně spoléhají na to, že jako parametr dostanou nějaký TreeNode (některé možná zvládnou i None) # TODO: Chceme, aby všechno nějak zvládlo None jako parametr. +# TODO: Do nějakých consistency-checků přidat hledání polo-sirotků (kteří nesplňují invarianty, třeba nejsou dosažitelní a mají root, vyrábějí DAG, ...) # Slouží k debugování pro rychlé získání představy o podobě podstromu pod tímto TreeNode. def print_tree(node,indent=0): @@ -15,11 +16,14 @@ def print_tree(node,indent=0): # Django je trošku hloupé, takže node.prev nevrací None, ale hází django.core.exceptions.ObjectDoesNotExist def safe_pred(node): + if node is None: + return None try: return node.prev except ObjectDoesNotExist: return None +# FIXME: Proč????? def safe_succ(node): try: return node.succ @@ -27,6 +31,8 @@ def safe_succ(node): return None def safe_father_of_first(node): + if node is None: + return None first = first_brother(node) try: return first.father_of_first @@ -42,6 +48,7 @@ def first_brother(node): return brother ## Rodinné vztahy +# Tohle se teď zrovna k None chová správně, ale je potřeba na to dávat pozor def get_parent(node): # Nejdřív získáme prvního potomka... while safe_pred(node) is not None: @@ -50,6 +57,8 @@ def get_parent(node): return safe_father_of_first(node) def get_last_child(node): + if node is None: + return None first = node.first_child if first is None: return None @@ -73,6 +82,8 @@ def is_orphan(node): # Obecný next: další Node v "the-right-order" pořadí (já, pak potomci, pak sousedé) def general_next(node): + if node is None: + return None # Máme potomka? if node.first_child is not None: return node.first_child @@ -85,6 +96,8 @@ def general_next(node): return node.succ def last_brother(node): + if node is None: + return None while node.succ is not None: node = node.succ return node @@ -92,6 +105,7 @@ def last_brother(node): def general_prev(node): # Předchůdce je buď rekurzivně poslední potomek předchůdce, nebo náš otec. # Otce vyřešíme nejdřív: + # Tady se ošetří node=None samo if safe_pred(node) is None: return safe_father_of_first(node) pred = safe_pred(node) @@ -109,12 +123,16 @@ def me_and_right_brothers(node): current = current.succ def right_brothers(node): + if node is None: + return generator = me_and_right_brothers(node.succ) for item in generator: yield item # Generátor všech sourozenců (vč. sám sebe) def all_brothers(node): + if node is None: + return # Najdeme prvního bratra fb = first_brother(node) marb = me_and_right_brothers(fb) @@ -122,6 +140,8 @@ def all_brothers(node): yield cur def all_proper_brothers(node): + if node is None: + return all = all_brothers(node) for br in all: if br is node: @@ -130,12 +150,16 @@ def all_proper_brothers(node): def all_children(node): """ Generátor všech potomků zadaného Node. """ + if node is None: + return brothers = all_brothers(node.first_child) for br in brothers: yield br def all_children_of_type(node, type): """ Generuje všechny potomky daného Node a daného typu. """ + if node is None: + return brothers = all_brothers(node.first_child) for br in brothers: if isinstance(br, type): @@ -144,6 +168,8 @@ def all_children_of_type(node, type): # Generátor následníků v "the-right-order" # Bez tohoto vrcholu def all_following(node): + if node is None: + return current = general_next(node) while current is not None: yield current @@ -153,12 +179,16 @@ def all_following(node): # Najdi dalšího bratra nějakého typu, nebo None. # hledá i podtřídy, i.e. get_next_brother_of_type(neco, TreeNode) je prostě succ. def get_next_brother_of_type(node, type): + if node is None: + return for current in right_brothers(node): if isinstance(current, type): return current return None def get_prev_brother_of_type(node, type): + if node is None: + return # Na tohle není rozumný generátor, ani ho asi nechceme, prostě to implementujeme cyklem. current = node while safe_pred(current) is not None: @@ -169,6 +199,8 @@ def get_prev_brother_of_type(node, type): # Totéž pro "the-right-order" pořadí def get_next_node_of_type(node, type): + if node is None: + return for cur in all_folowing(node): if isinstance(cur, type): return cur @@ -176,6 +208,8 @@ def get_next_node_of_type(node, type): def get_prev_node_of_type(node, type): # Na tohle není rozumný generátor, ani ho asi nechceme, prostě to implementujeme cyklem. + if node is None: + return current = node while general_prev(current) is not None: current = general_prev(current) @@ -185,9 +219,19 @@ def get_prev_node_of_type(node, type): +# Exception, kterou některé metody při špatném použití mohou házet +# Hlavní důvod je možnost informovat o selhání, aby se příslušný problém dal zobrazit na frontendu, +class TreeLibError(RuntimeError): + pass + # Editace stromu: def create_node_after(predecessor, type, **kwargs): + from seminar.models import TreeNode + if predecessor is None: + raise TreeLibError("Nelze vyrábět sirotky! (predecessor=None)") + if not issubclass(type, TreeNode): + raise TreeLibError("Nový node není node!") new_node = type.objects.create(**kwargs) new_node.root = predecessor.root new_node.save() @@ -200,6 +244,11 @@ def create_node_after(predecessor, type, **kwargs): # Vyrábí prvního syna, ostatní nalepí za (existují-li) def create_child(parent, type, **kwargs): + from seminar.models import TreeNode + if parent is None: + raise TreeLibError("Nelze vyrábět sirotky! (parent=None)") + if not issubclass(type, TreeNode): + raise TreeLibError("Nový node není node!") new_node = type.objects.create(**kwargs) new_node.root = parent.root new_node.save() @@ -213,6 +262,8 @@ def create_child(parent, type, **kwargs): return new_node def insert_last_child(parent, node): + if parent is None: + raise TreeLibError("Nelze vyrábět sirotky! (parent=None)") """ Zadaný Node přidá jako posledního potomka otce. """ last = get_last_child(parent) if not is_orphan(node): @@ -231,6 +282,11 @@ def insert_last_child(parent, node): last.save() def create_node_before(successor, type, **kwargs): + from seminar.models import TreeNode + if successor is None: + raise TreeLibError("Nelze vyrábět sirotky! (successor=None)") + if not issubclass(type, TreeNode): + raise TreeLibError("Nový node není node!") if safe_pred(successor) is not None: # Easy: přidáme za předchůdce create_node_after(successor.prev, type, **kwargs) @@ -251,11 +307,6 @@ def create_node_before(successor, type, **kwargs): def swap(node, other): raise NotImplementedError("YAGNI (You aren't gonna need it).") -# Exception, kterou některé metody při špatném použití mohou házet -# Hlavní důvod je možnost informovat o selhání, aby se příslušný problém dal zobrazit na frontendu, -class TreeLibError(RuntimeError): - pass - @transaction.atomic def swap_succ(node): @@ -297,55 +348,76 @@ def swap_pred(node): # Rotace stromu # Dokumentace viz wiki: -# (lower bude jednoduchá rotace, ne mega, existence jednoduché rotace mi došla až po nakreslení obrázku) def raise_node(node): if node is None: raise TreeLibError("Nelze přesunout None. Tohle by se nemělo stát.") # Pojmenování viz WIKI (as of 2020-03-19 01:33:44 GMT+1) - # FIXME: Velmi naivní, chybí error checky + # FIXME: Trochu méně naivní, nevěřím tomu, prosím otestovat D = node C = get_parent(D) - E = C.succ - subtree4_head = D.first_child - subtree4_tail = last_brother(subtree4_head) - subtree3P_head = D.succ - subtree3L_head = C.first_child - subtree3L_tail = safe_pred(D) + if C is None: + raise TreeLibError("Nelze povýšit vrchol, jenž nemá otce.") + E = C.succ # Může být None a ničemu to nevadí + subtree4_head = D.first_child # Může být None, ale pak se musí z 3P udělat přímo potomek D + subtree4_tail = last_brother(subtree4_head) # Měl by být None právě když je sub4_head=None + subtree3P_head = D.succ # Může být None a ničemu to nevadí + subtree3L_tail = safe_pred(D) # Pokud je None, D je první syn C a C má tedy skončit bezdětný # Prostor pro motlitbu... pass # Amen. - C.succ = D + # Teď už nesmíme spadnout, protože jinak skončíme se stromem v nekonzistentním stavu + C.succ = D # Nespadne C.save() - D.succ = E + D.succ = E # Nespadne D.save() - subtree3L_tail.succ = None - subtree3L_tail.save() - subtree4_tail.succ = subtree3P.head - subtree4_tail.save() + + if subtree3L_tail is not None: + subtree3L_tail.succ = None + subtree3L_tail.save() + else: + assert C.first_child is D + C.first_child = None + C.save() + + if subtree4_tail is not None: + subtree4_tail.succ = subtree3P_head + subtree4_tail.save() + else: + D.first_child = subtree3P_head + D.save() # To by mělo být všechno... +# (lower bude jednoduchá rotace, ne mega, existence jednoduché rotace mi došla až po nakreslení obrázku) def lower_node(node): if node is None: raise TreeLibError("Nelze přesunout None. Tohle by se nemělo stát.") # Pojmenování viz WIKI (as of 2020-03-19 01:33:44 GMT+1) - # FIXME: Velmi naivní, chybí error checky + # FIXME: Trochu naivní, prosím otestovat C = node - D = C.succ + D = C.succ # Může být None a ničemu to nevadí B = safe_pred(C) - subtree2_head = B.first_child - subtree2_tail = last_brother(subtree2_head) + if B is None: + raise TreeLibError("Nelze ponížit prvního syna (není pod co)") + subtree2_head = B.first_child # Je-li None, pak se z C má stát první syn + subtree2_tail = last_brother(subtree2_head) # None iff head=None, doufám # Prostor pro motlitbu... pass # Amen. - B.succ = D + # Teď už nesmíme spadnout, protože jinak skončíme se stromem v nekonzistentním stavu + B.succ = D # Nespadne B.save() - subtree2_tail.succ = C - subtree2_tail.save() + if subtree2_tail is not None: + subtree2_tail.succ = C + subtree2_tail.save() + else: + assert subtree2_head is None + B.first_child = C + B.save() # To by mělo být všechno...