Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

tree.py 4.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. """
  2. A class for storing a tree graph. Primarily used for filter constructs in the
  3. ORM.
  4. """
  5. import copy
  6. class Node:
  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. """Construct a new Node. If no connector is given, use the default."""
  17. self.children = children[:] if children else []
  18. self.connector = connector or self.default
  19. self.negated = negated
  20. # Required because django.db.models.query_utils.Q. Q. __init__() is
  21. # problematic, but it is a natural Node subclass in all other respects.
  22. @classmethod
  23. def _new_instance(cls, children=None, connector=None, negated=False):
  24. """
  25. Create a new instance of this class when new Nodes (or subclasses) are
  26. needed in the internal code in this class. Normally, it just shadows
  27. __init__(). However, subclasses with an __init__ signature that aren't
  28. an extension of Node.__init__ might need to implement this method to
  29. allow a Node to create a new instance of them (if they have any extra
  30. setting up to do).
  31. """
  32. obj = Node(children, connector, negated)
  33. obj.__class__ = cls
  34. return obj
  35. def __str__(self):
  36. template = '(NOT (%s: %s))' if self.negated else '(%s: %s)'
  37. return template % (self.connector, ', '.join(str(c) for c in self.children))
  38. def __repr__(self):
  39. return "<%s: %s>" % (self.__class__.__name__, self)
  40. def __deepcopy__(self, memodict):
  41. obj = Node(connector=self.connector, negated=self.negated)
  42. obj.__class__ = self.__class__
  43. obj.children = copy.deepcopy(self.children, memodict)
  44. return obj
  45. def __len__(self):
  46. """Return the the number of children this node has."""
  47. return len(self.children)
  48. def __bool__(self):
  49. """Return whether or not this node has children."""
  50. return bool(self.children)
  51. def __contains__(self, other):
  52. """Return True if 'other' is a direct child of this instance."""
  53. return other in self.children
  54. def __eq__(self, other):
  55. if self.__class__ != other.__class__:
  56. return False
  57. if (self.connector, self.negated) == (other.connector, other.negated):
  58. return self.children == other.children
  59. return False
  60. def __hash__(self):
  61. return hash((self.__class__, self.connector, self.negated) + tuple(self.children))
  62. def add(self, data, conn_type, squash=True):
  63. """
  64. Combine this tree and the data represented by data using the
  65. connector conn_type. The combine is done by squashing the node other
  66. away if possible.
  67. This tree (self) will never be pushed to a child node of the
  68. combined tree, nor will the connector or negated properties change.
  69. Return a node which can be used in place of data regardless if the
  70. node other got squashed or not.
  71. If `squash` is False the data is prepared and added as a child to
  72. this tree without further logic.
  73. """
  74. if data in self.children:
  75. return data
  76. if not squash:
  77. self.children.append(data)
  78. return data
  79. if self.connector == conn_type:
  80. # We can reuse self.children to append or squash the node other.
  81. if (isinstance(data, Node) and not data.negated and
  82. (data.connector == conn_type or len(data) == 1)):
  83. # We can squash the other node's children directly into this
  84. # node. We are just doing (AB)(CD) == (ABCD) here, with the
  85. # addition that if the length of the other node is 1 the
  86. # connector doesn't matter. However, for the len(self) == 1
  87. # case we don't want to do the squashing, as it would alter
  88. # self.connector.
  89. self.children.extend(data.children)
  90. return self
  91. else:
  92. # We could use perhaps additional logic here to see if some
  93. # children could be used for pushdown here.
  94. self.children.append(data)
  95. return data
  96. else:
  97. obj = self._new_instance(self.children, self.connector,
  98. self.negated)
  99. self.connector = conn_type
  100. self.children = [obj, data]
  101. return data
  102. def negate(self):
  103. """Negate the sense of the root connector."""
  104. self.negated = not self.negated