mirror of
https://github.com/django/django.git
synced 2025-10-26 07:06:08 +00:00
Merged the queryset-refactor branch into trunk.
This is a big internal change, but mostly backwards compatible with existing code. Also adds a couple of new features. Fixed #245, #1050, #1656, #1801, #2076, #2091, #2150, #2253, #2306, #2400, #2430, #2482, #2496, #2676, #2737, #2874, #2902, #2939, #3037, #3141, #3288, #3440, #3592, #3739, #4088, #4260, #4289, #4306, #4358, #4464, #4510, #4858, #5012, #5020, #5261, #5295, #5321, #5324, #5325, #5555, #5707, #5796, #5817, #5987, #6018, #6074, #6088, #6154, #6177, #6180, #6203, #6658 git-svn-id: http://code.djangoproject.com/svn/django/trunk@7477 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
134
django/utils/tree.py
Normal file
134
django/utils/tree.py
Normal file
@@ -0,0 +1,134 @@
|
||||
"""
|
||||
A class for storing a tree graph. Primarily used for filter constructs in the
|
||||
ORM.
|
||||
"""
|
||||
|
||||
from copy 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
|
||||
|
||||
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:
|
||||
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 = Node(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 = [Node(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 = [Node(self.children, self.connector, self.negated)]
|
||||
self.connector = conn_type
|
||||
self.negated = False
|
||||
|
||||
self.subtree_parents.append(Node(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 = Node(self.children, self.connector)
|
||||
self.connector = obj.connector
|
||||
self.negated = obj.negated
|
||||
self.children = obj.children
|
||||
self.children.append(node)
|
||||
|
||||
Reference in New Issue
Block a user