Added some tests to modeltests/many_to_one that demonstrate a post-queryset-refactor...
[fdr-django.git] / django / utils / tree.py
bloba62a4ae6c3a6359ebd07233e2478ca1aafc794b7
1 """
2 A class for storing a tree graph. Primarily used for filter constructs in the
3 ORM.
4 """
6 from copy import deepcopy
8 class Node(object):
9 """
10 A single internal node in the tree graph. A Node should be viewed as a
11 connection (the root) with the children being either leaf nodes or other
12 Node instances.
13 """
14 # Standard connector type. Clients usually won't use this at all and
15 # subclasses will usually override the value.
16 default = 'DEFAULT'
18 def __init__(self, children=None, connector=None, negated=False):
19 """
20 Constructs a new Node. If no connector is given, the default will be
21 used.
23 Warning: You probably don't want to pass in the 'negated' parameter. It
24 is NOT the same as constructing a node and calling negate() on the
25 result.
26 """
27 self.children = children and children[:] or []
28 self.connector = connector or self.default
29 self.subtree_parents = []
30 self.negated = negated
32 def __str__(self):
33 if self.negated:
34 return '(NOT (%s: %s))' % (self.connector, ', '.join([str(c) for c
35 in self.children]))
36 return '(%s: %s)' % (self.connector, ', '.join([str(c) for c in
37 self.children]))
39 def __deepcopy__(self, memodict):
40 """
41 Utility method used by copy.deepcopy().
42 """
43 obj = Node(connector=self.connector, negated=self.negated)
44 obj.__class__ = self.__class__
45 obj.children = deepcopy(self.children, memodict)
46 obj.subtree_parents = deepcopy(self.subtree_parents, memodict)
47 return obj
49 def __len__(self):
50 """
51 The size of a node if the number of children it has.
52 """
53 return len(self.children)
55 def __nonzero__(self):
56 """
57 For truth value testing.
58 """
59 return bool(self.children)
61 def __contains__(self, other):
62 """
63 Returns True is 'other' is a direct child of this instance.
64 """
65 return other in self.children
67 def add(self, node, conn_type):
68 """
69 Adds a new node to the tree. If the conn_type is the same as the root's
70 current connector type, the node is added to the first level.
71 Otherwise, the whole tree is pushed down one level and a new root
72 connector is created, connecting the existing tree and the new node.
73 """
74 if node in self.children:
75 return
76 if len(self.children) < 2:
77 self.connector = conn_type
78 if self.connector == conn_type:
79 if isinstance(node, Node) and (node.connector == conn_type or
80 len(node) == 1):
81 self.children.extend(node.children)
82 else:
83 self.children.append(node)
84 else:
85 obj = Node(self.children, self.connector, self.negated)
86 self.connector = conn_type
87 self.children = [obj, node]
89 def negate(self):
90 """
91 Negate the sense of the root connector. This reorganises the children
92 so that the current node has a single child: a negated node containing
93 all the previous children. This slightly odd construction makes adding
94 new children behave more intuitively.
96 Interpreting the meaning of this negate is up to client code. This
97 method is useful for implementing "not" arrangements.
98 """
99 self.children = [Node(self.children, self.connector, not self.negated)]
100 self.connector = self.default
102 def start_subtree(self, conn_type):
104 Sets up internal state so that new nodes are added to a subtree of the
105 current node. The conn_type specifies how the sub-tree is joined to the
106 existing children.
108 if len(self.children) == 1:
109 self.connector = conn_type
110 elif self.connector != conn_type:
111 self.children = [Node(self.children, self.connector, self.negated)]
112 self.connector = conn_type
113 self.negated = False
115 self.subtree_parents.append(Node(self.children, self.connector,
116 self.negated))
117 self.connector = self.default
118 self.negated = False
119 self.children = []
121 def end_subtree(self):
123 Closes off the most recently unmatched start_subtree() call.
125 This puts the current state into a node of the parent tree and returns
126 the current instances state to be the parent.
128 obj = self.subtree_parents.pop()
129 node = Node(self.children, self.connector)
130 self.connector = obj.connector
131 self.negated = obj.negated
132 self.children = obj.children
133 self.children.append(node)