1. Lars Yencken
  2. django-hierarchy

Commits

Lars Yencken  committed eeb2f25

Adds debugging checks for out-of-date tree position, and a way to update it.

Operations which change the tree can cause Django ORM objects to become out of
date. It is left to the user to ensure that this does not occur. Now it is
checked explicitly when DEBUG=True before tree-changing operations are made.
The user should circumvent such issues by calling update_position() on any
object they expect to be out of date before using it.

  • Participants
  • Parent commits 440aa36
  • Branches default
  • Tags 0.2.3

Comments (0)

Files changed (2)

File setup.py

View file
  • Ignore whitespace
 
 from setuptools import setup
 
-VERSION = '0.2.2'
+VERSION = '0.2.3'
 
 f = open('src/__version__.py', 'w')
 f.write('# Autogenerated by setup.py\n')

File src/models.py

View file
  • Ignore whitespace
 """
 
 from django.db import models, connection
+from django.conf import settings
+from itertools import dropwhile
 
 import model_tree
 
         return cls.objects.get(left_visit=1)
 
     def get_ancestors(self):
+        if settings.DEBUG:
+            assert not self.refresh_position(), "node was out of date"
         return self.__class__.objects.filter(
                 left_visit__lte=self.left_visit,
                 right_visit__gte=self.right_visit,
             ).order_by('left_visit')
 
+    def get_children(self):
+        if settings.DEBUG:
+            assert not self.refresh_position(), "node was out of date"
+        valid_children = []
+        subtree_nodes = iter(self.get_subtree())
+        parent = subtree_nodes.next()
+        try:
+            while True:
+                node = subtree_nodes.next()
+                valid_children.append(node)
+                subtree_nodes = dropwhile(
+                        lambda n: n.right_visit < node.right_visit,
+                        subtree_nodes
+                    )
+        except StopIteration:
+            pass
+        return valid_children
+
     def get_subtree(self):
+        if settings.DEBUG:
+            assert not self.refresh_position(), "node was out of date"
         return self.__class__.objects.filter(
                 left_visit__gte=self.left_visit,
                 right_visit__lte=self.right_visit,
             ).order_by('left_visit')
 
     def add_child(self, **kwargs):
+        if settings.DEBUG:
+            assert not self.refresh_position(), "node was out of date"
         cls = self.__class__
         parent_left_visit = self.left_visit
         parent_right_visit = self.right_visit
         self.right_visit += 2
         return new_node
 
+    def refresh_position(self):
+        """
+        Fetches an authoritative position of this node in the tree from the
+        database. This is very important to do when the tree has been
+        modified, otherwise tree-based operations have undefined results.
+        Returns true if the node was out of date, False otherwise.
+        """
+        cls = self.__class__
+        record = cls.objects.values('left_visit', 'right_visit').get(
+                pk=self.pk)
+        was_dirty = False
+        if self.left_visit != record['left_visit']:
+            self.left_visit = record['left_visit']
+            was_dirty = True
+
+        if self.right_visit != record['right_visit']:
+            self.right_visit = record['right_visit']
+            was_dirty = True
+
+        return was_dirty
+
     def __cmp__(self, rhs):
-        return cmp(self.left_visit, self.right_visit)
+        if settings.DEBUG:
+            assert not self.refresh_position(), "node was out of date"
+        return cmp(self.left_visit, rhs.right_visit)
 
     def leaves(self):
         "Returns all leaf nodes under this node."
+        if settings.DEBUG:
+            assert not self.refresh_position(), "node was out of date"
         return self.get_subtree().extra(
                 where=['left_visit + 1 = right_visit'])
     
         Fetches the entire subtree from the database, returning it as a 
         TreeNode structure.
         """
+        if settings.DEBUG:
+            assert not self.refresh_position(), "node was out of date"
         if not label_field:
             if hasattr(self, 'label_field'):
                 label_field = self.label_field
     
     def layout(self, label_field=None, method=None):
         "Pretty-prints the subtree rooted at this node."
+        if settings.DEBUG:
+            assert not self.refresh_position(), "node was out of date"
         return self.to_tree(label_field=label_field).layout(method=method)
 
 # vim: ts=4 sw=4 sts=4 et tw=78:
-