/django/utils/tree.py
Python | 153 lines | 64 code | 5 blank | 84 comment | 6 complexity | 6b2162345a0ec6ec0aac71584abf79f3 MD5 | raw file
- """
- A class for storing a tree graph. Primarily used for filter constructs in the
- ORM.
- """
- from django.utils.copycompat import deepcopy
- class Node(object):
- """
- A single internal node in the tree graph. A Node should be viewed as a
- connection (the root) with the children being either leaf nodes or other
- Node instances.
- """
- # Standard connector type. Clients usually won't use this at all and
- # subclasses will usually override the value.
- default = 'DEFAULT'
- def __init__(self, children=None, connector=None, negated=False):
- """
- Constructs a new Node. If no connector is given, the default will be
- used.
- Warning: You probably don't want to pass in the 'negated' parameter. It
- is NOT the same as constructing a node and calling negate() on the
- result.
- """
- self.children = children and children[:] or []
- self.connector = connector or self.default
- self.subtree_parents = []
- self.negated = negated
- # We need this because of django.db.models.query_utils.Q. Q. __init__() is
- # problematic, but it is a natural Node subclass in all other respects.
- def _new_instance(cls, children=None, connector=None, negated=False):
- """
- This is called to create a new instance of this class when we need new
- Nodes (or subclasses) in the internal code in this class. Normally, it
- just shadows __init__(). However, subclasses with an __init__ signature
- that is not an extension of Node.__init__ might need to implement this
- method to allow a Node to create a new instance of them (if they have
- any extra setting up to do).
- """
- obj = Node(children, connector, negated)
- obj.__class__ = cls
- return obj
- _new_instance = classmethod(_new_instance)
- def __str__(self):
- if self.negated:
- return '(NOT (%s: %s))' % (self.connector, ', '.join([str(c) for c
- in self.children]))
- return '(%s: %s)' % (self.connector, ', '.join([str(c) for c in
- self.children]))
- def __deepcopy__(self, memodict):
- """
- Utility method used by copy.deepcopy().
- """
- obj = Node(connector=self.connector, negated=self.negated)
- obj.__class__ = self.__class__
- obj.children = deepcopy(self.children, memodict)
- obj.subtree_parents = deepcopy(self.subtree_parents, memodict)
- return obj
- def __len__(self):
- """
- The size of a node if the number of children it has.
- """
- return len(self.children)
- def __nonzero__(self):
- """
- For truth value testing.
- """
- return bool(self.children)
- def __contains__(self, other):
- """
- Returns True is 'other' is a direct child of this instance.
- """
- return other in self.children
- def add(self, node, conn_type):
- """
- Adds a new node to the tree. If the conn_type is the same as the root's
- current connector type, the node is added to the first level.
- Otherwise, the whole tree is pushed down one level and a new root
- connector is created, connecting the existing tree and the new node.
- """
- if node in self.children and conn_type == self.connector:
- return
- if len(self.children) < 2:
- self.connector = conn_type
- if self.connector == conn_type:
- if isinstance(node, Node) and (node.connector == conn_type or
- len(node) == 1):
- self.children.extend(node.children)
- else:
- self.children.append(node)
- else:
- obj = self._new_instance(self.children, self.connector,
- self.negated)
- self.connector = conn_type
- self.children = [obj, node]
- def negate(self):
- """
- Negate the sense of the root connector. This reorganises the children
- so that the current node has a single child: a negated node containing
- all the previous children. This slightly odd construction makes adding
- new children behave more intuitively.
- Interpreting the meaning of this negate is up to client code. This
- method is useful for implementing "not" arrangements.
- """
- self.children = [self._new_instance(self.children, self.connector,
- not self.negated)]
- self.connector = self.default
- def start_subtree(self, conn_type):
- """
- Sets up internal state so that new nodes are added to a subtree of the
- current node. The conn_type specifies how the sub-tree is joined to the
- existing children.
- """
- if len(self.children) == 1:
- self.connector = conn_type
- elif self.connector != conn_type:
- self.children = [self._new_instance(self.children, self.connector,
- self.negated)]
- self.connector = conn_type
- self.negated = False
- self.subtree_parents.append(self.__class__(self.children,
- self.connector, self.negated))
- self.connector = self.default
- self.negated = False
- self.children = []
- def end_subtree(self):
- """
- Closes off the most recently unmatched start_subtree() call.
- This puts the current state into a node of the parent tree and returns
- the current instances state to be the parent.
- """
- obj = self.subtree_parents.pop()
- node = self.__class__(self.children, self.connector)
- self.connector = obj.connector
- self.negated = obj.negated
- self.children = obj.children
- self.children.append(node)