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.

Comments (0)

Files changed (2)

 
 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')
 """
 
 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:
-
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.