| 1 |
""" |
|---|
| 2 |
A class for storing a tree graph. Primarily used for filter constructs in the |
|---|
| 3 |
ORM. |
|---|
| 4 |
""" |
|---|
| 5 |
|
|---|
| 6 |
from copy import deepcopy |
|---|
| 7 |
|
|---|
| 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' |
|---|
| 17 |
|
|---|
| 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. |
|---|
| 22 |
|
|---|
| 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 |
|---|
| 31 |
|
|---|
| 32 |
# We need this because of django.db.models.query_utils.Q. Q. __init__() is |
|---|
| 33 |
# problematic, but it is a natural Node subclass in all other respects. |
|---|
| 34 |
def _new_instance(cls, children=None, connector=None, negated=False): |
|---|
| 35 |
""" |
|---|
| 36 |
This is called to create a new instance of this class when we need new |
|---|
| 37 |
Nodes (or subclasses) in the internal code in this class. Normally, it |
|---|
| 38 |
just shadows __init__(). However, subclasses with an __init__ signature |
|---|
| 39 |
that is not an extension of Node.__init__ might need to implement this |
|---|
| 40 |
method to allow a Node to create a new instance of them (if they have |
|---|
| 41 |
any extra setting up to do). |
|---|
| 42 |
""" |
|---|
| 43 |
obj = Node(children, connector, negated) |
|---|
| 44 |
obj.__class__ = cls |
|---|
| 45 |
return obj |
|---|
| 46 |
_new_instance = classmethod(_new_instance) |
|---|
| 47 |
|
|---|
| 48 |
def __str__(self): |
|---|
| 49 |
if self.negated: |
|---|
| 50 |
return '(NOT (%s: %s))' % (self.connector, ', '.join([str(c) for c |
|---|
| 51 |
in self.children])) |
|---|
| 52 |
return '(%s: %s)' % (self.connector, ', '.join([str(c) for c in |
|---|
| 53 |
self.children])) |
|---|
| 54 |
|
|---|
| 55 |
def __deepcopy__(self, memodict): |
|---|
| 56 |
""" |
|---|
| 57 |
Utility method used by copy.deepcopy(). |
|---|
| 58 |
""" |
|---|
| 59 |
obj = Node(connector=self.connector, negated=self.negated) |
|---|
| 60 |
obj.__class__ = self.__class__ |
|---|
| 61 |
obj.children = deepcopy(self.children, memodict) |
|---|
| 62 |
obj.subtree_parents = deepcopy(self.subtree_parents, memodict) |
|---|
| 63 |
return obj |
|---|
| 64 |
|
|---|
| 65 |
def __len__(self): |
|---|
| 66 |
""" |
|---|
| 67 |
The size of a node if the number of children it has. |
|---|
| 68 |
""" |
|---|
| 69 |
return len(self.children) |
|---|
| 70 |
|
|---|
| 71 |
def __nonzero__(self): |
|---|
| 72 |
""" |
|---|
| 73 |
For truth value testing. |
|---|
| 74 |
""" |
|---|
| 75 |
return bool(self.children) |
|---|
| 76 |
|
|---|
| 77 |
def __contains__(self, other): |
|---|
| 78 |
""" |
|---|
| 79 |
Returns True is 'other' is a direct child of this instance. |
|---|
| 80 |
""" |
|---|
| 81 |
return other in self.children |
|---|
| 82 |
|
|---|
| 83 |
def add(self, node, conn_type): |
|---|
| 84 |
""" |
|---|
| 85 |
Adds a new node to the tree. If the conn_type is the same as the root's |
|---|
| 86 |
current connector type, the node is added to the first level. |
|---|
| 87 |
Otherwise, the whole tree is pushed down one level and a new root |
|---|
| 88 |
connector is created, connecting the existing tree and the new node. |
|---|
| 89 |
""" |
|---|
| 90 |
if node in self.children: |
|---|
| 91 |
return |
|---|
| 92 |
if len(self.children) < 2: |
|---|
| 93 |
self.connector = conn_type |
|---|
| 94 |
if self.connector == conn_type: |
|---|
| 95 |
if isinstance(node, Node) and (node.connector == conn_type or |
|---|
| 96 |
len(node) == 1): |
|---|
| 97 |
self.children.extend(node.children) |
|---|
| 98 |
else: |
|---|
| 99 |
self.children.append(node) |
|---|
| 100 |
else: |
|---|
| 101 |
obj = self._new_instance(self.children, self.connector, |
|---|
| 102 |
self.negated) |
|---|
| 103 |
self.connector = conn_type |
|---|
| 104 |
self.children = [obj, node] |
|---|
| 105 |
|
|---|
| 106 |
def negate(self): |
|---|
| 107 |
""" |
|---|
| 108 |
Negate the sense of the root connector. This reorganises the children |
|---|
| 109 |
so that the current node has a single child: a negated node containing |
|---|
| 110 |
all the previous children. This slightly odd construction makes adding |
|---|
| 111 |
new children behave more intuitively. |
|---|
| 112 |
|
|---|
| 113 |
Interpreting the meaning of this negate is up to client code. This |
|---|
| 114 |
method is useful for implementing "not" arrangements. |
|---|
| 115 |
""" |
|---|
| 116 |
self.children = [self._new_instance(self.children, self.connector, |
|---|
| 117 |
not self.negated)] |
|---|
| 118 |
self.connector = self.default |
|---|
| 119 |
|
|---|
| 120 |
def start_subtree(self, conn_type): |
|---|
| 121 |
""" |
|---|
| 122 |
Sets up internal state so that new nodes are added to a subtree of the |
|---|
| 123 |
current node. The conn_type specifies how the sub-tree is joined to the |
|---|
| 124 |
existing children. |
|---|
| 125 |
""" |
|---|
| 126 |
if len(self.children) == 1: |
|---|
| 127 |
self.connector = conn_type |
|---|
| 128 |
elif self.connector != conn_type: |
|---|
| 129 |
self.children = [self._new_instance(self.children, self.connector, |
|---|
| 130 |
self.negated)] |
|---|
| 131 |
self.connector = conn_type |
|---|
| 132 |
self.negated = False |
|---|
| 133 |
|
|---|
| 134 |
self.subtree_parents.append(self.__class__(self.children, |
|---|
| 135 |
self.connector, self.negated)) |
|---|
| 136 |
self.connector = self.default |
|---|
| 137 |
self.negated = False |
|---|
| 138 |
self.children = [] |
|---|
| 139 |
|
|---|
| 140 |
def end_subtree(self): |
|---|
| 141 |
""" |
|---|
| 142 |
Closes off the most recently unmatched start_subtree() call. |
|---|
| 143 |
|
|---|
| 144 |
This puts the current state into a node of the parent tree and returns |
|---|
| 145 |
the current instances state to be the parent. |
|---|
| 146 |
""" |
|---|
| 147 |
obj = self.subtree_parents.pop() |
|---|
| 148 |
node = self.__class__(self.children, self.connector) |
|---|
| 149 |
self.connector = obj.connector |
|---|
| 150 |
self.negated = obj.negated |
|---|
| 151 |
self.children = obj.children |
|---|
| 152 |
self.children.append(node) |
|---|