/django/utils/tree.py

https://github.com/TerrorBite/Mediasnak
Python | 153 lines | 64 code | 5 blank | 84 comment | 6 complexity | 6b2162345a0ec6ec0aac71584abf79f3 MD5 | raw file
  1. """
  2. A class for storing a tree graph. Primarily used for filter constructs in the
  3. ORM.
  4. """
  5. from django.utils.copycompat import deepcopy
  6. class Node(object):
  7. """
  8. A single internal node in the tree graph. A Node should be viewed as a
  9. connection (the root) with the children being either leaf nodes or other
  10. Node instances.
  11. """
  12. # Standard connector type. Clients usually won't use this at all and
  13. # subclasses will usually override the value.
  14. default = 'DEFAULT'
  15. def __init__(self, children=None, connector=None, negated=False):
  16. """
  17. Constructs a new Node. If no connector is given, the default will be
  18. used.
  19. Warning: You probably don't want to pass in the 'negated' parameter. It
  20. is NOT the same as constructing a node and calling negate() on the
  21. result.
  22. """
  23. self.children = children and children[:] or []
  24. self.connector = connector or self.default
  25. self.subtree_parents = []
  26. self.negated = negated
  27. # We need this because of django.db.models.query_utils.Q. Q. __init__() is
  28. # problematic, but it is a natural Node subclass in all other respects.
  29. def _new_instance(cls, children=None, connector=None, negated=False):
  30. """
  31. This is called to create a new instance of this class when we need new
  32. Nodes (or subclasses) in the internal code in this class. Normally, it
  33. just shadows __init__(). However, subclasses with an __init__ signature
  34. that is not an extension of Node.__init__ might need to implement this
  35. method to allow a Node to create a new instance of them (if they have
  36. any extra setting up to do).
  37. """
  38. obj = Node(children, connector, negated)
  39. obj.__class__ = cls
  40. return obj
  41. _new_instance = classmethod(_new_instance)
  42. def __str__(self):
  43. if self.negated:
  44. return '(NOT (%s: %s))' % (self.connector, ', '.join([str(c) for c
  45. in self.children]))
  46. return '(%s: %s)' % (self.connector, ', '.join([str(c) for c in
  47. self.children]))
  48. def __deepcopy__(self, memodict):
  49. """
  50. Utility method used by copy.deepcopy().
  51. """
  52. obj = Node(connector=self.connector, negated=self.negated)
  53. obj.__class__ = self.__class__
  54. obj.children = deepcopy(self.children, memodict)
  55. obj.subtree_parents = deepcopy(self.subtree_parents, memodict)
  56. return obj
  57. def __len__(self):
  58. """
  59. The size of a node if the number of children it has.
  60. """
  61. return len(self.children)
  62. def __nonzero__(self):
  63. """
  64. For truth value testing.
  65. """
  66. return bool(self.children)
  67. def __contains__(self, other):
  68. """
  69. Returns True is 'other' is a direct child of this instance.
  70. """
  71. return other in self.children
  72. def add(self, node, conn_type):
  73. """
  74. Adds a new node to the tree. If the conn_type is the same as the root's
  75. current connector type, the node is added to the first level.
  76. Otherwise, the whole tree is pushed down one level and a new root
  77. connector is created, connecting the existing tree and the new node.
  78. """
  79. if node in self.children and conn_type == self.connector:
  80. return
  81. if len(self.children) < 2:
  82. self.connector = conn_type
  83. if self.connector == conn_type:
  84. if isinstance(node, Node) and (node.connector == conn_type or
  85. len(node) == 1):
  86. self.children.extend(node.children)
  87. else:
  88. self.children.append(node)
  89. else:
  90. obj = self._new_instance(self.children, self.connector,
  91. self.negated)
  92. self.connector = conn_type
  93. self.children = [obj, node]
  94. def negate(self):
  95. """
  96. Negate the sense of the root connector. This reorganises the children
  97. so that the current node has a single child: a negated node containing
  98. all the previous children. This slightly odd construction makes adding
  99. new children behave more intuitively.
  100. Interpreting the meaning of this negate is up to client code. This
  101. method is useful for implementing "not" arrangements.
  102. """
  103. self.children = [self._new_instance(self.children, self.connector,
  104. not self.negated)]
  105. self.connector = self.default
  106. def start_subtree(self, conn_type):
  107. """
  108. Sets up internal state so that new nodes are added to a subtree of the
  109. current node. The conn_type specifies how the sub-tree is joined to the
  110. existing children.
  111. """
  112. if len(self.children) == 1:
  113. self.connector = conn_type
  114. elif self.connector != conn_type:
  115. self.children = [self._new_instance(self.children, self.connector,
  116. self.negated)]
  117. self.connector = conn_type
  118. self.negated = False
  119. self.subtree_parents.append(self.__class__(self.children,
  120. self.connector, self.negated))
  121. self.connector = self.default
  122. self.negated = False
  123. self.children = []
  124. def end_subtree(self):
  125. """
  126. Closes off the most recently unmatched start_subtree() call.
  127. This puts the current state into a node of the parent tree and returns
  128. the current instances state to be the parent.
  129. """
  130. obj = self.subtree_parents.pop()
  131. node = self.__class__(self.children, self.connector)
  132. self.connector = obj.connector
  133. self.negated = obj.negated
  134. self.children = obj.children
  135. self.children.append(node)