/mptt/utils.py

https://github.com/kenthauser/django-cms
Python | 134 lines | 103 code | 7 blank | 24 comment | 6 complexity | 240dce821a4e785822072a817c492f91 MD5 | raw file
  1. """
  2. Utilities for working with lists of model instances which represent
  3. trees.
  4. """
  5. import copy
  6. import itertools
  7. __all__ = ('previous_current_next', 'tree_item_iterator',
  8. 'drilldown_tree_for_node')
  9. def previous_current_next(items):
  10. """
  11. From http://www.wordaligned.org/articles/zippy-triples-served-with-python
  12. Creates an iterator which returns (previous, current, next) triples,
  13. with ``None`` filling in when there is no previous or next
  14. available.
  15. """
  16. extend = itertools.chain([None], items, [None])
  17. previous, current, next = itertools.tee(extend, 3)
  18. try:
  19. current.next()
  20. next.next()
  21. next.next()
  22. except StopIteration:
  23. pass
  24. return itertools.izip(previous, current, next)
  25. def tree_item_iterator(items, ancestors=False):
  26. """
  27. Given a list of tree items, iterates over the list, generating
  28. two-tuples of the current tree item and a ``dict`` containing
  29. information about the tree structure around the item, with the
  30. following keys:
  31. ``'new_level'`
  32. ``True`` if the current item is the start of a new level in
  33. the tree, ``False`` otherwise.
  34. ``'closed_levels'``
  35. A list of levels which end after the current item. This will
  36. be an empty list if the next item is at the same level as the
  37. current item.
  38. If ``ancestors`` is ``True``, the following key will also be
  39. available:
  40. ``'ancestors'``
  41. A list of unicode representations of the ancestors of the
  42. current node, in descending order (root node first, immediate
  43. parent last).
  44. For example: given the sample tree below, the contents of the
  45. list which would be available under the ``'ancestors'`` key
  46. are given on the right::
  47. Books -> []
  48. Sci-fi -> [u'Books']
  49. Dystopian Futures -> [u'Books', u'Sci-fi']
  50. """
  51. structure = {}
  52. opts = None
  53. for previous, current, next in previous_current_next(items):
  54. if opts is None:
  55. opts = current._meta
  56. current_level = getattr(current, opts.level_attr)
  57. if previous:
  58. structure['new_level'] = (getattr(previous,
  59. opts.level_attr) < current_level)
  60. if ancestors:
  61. # If the previous node was the end of any number of
  62. # levels, remove the appropriate number of ancestors
  63. # from the list.
  64. if structure['closed_levels']:
  65. structure['ancestors'] = \
  66. structure['ancestors'][:-len(structure['closed_levels'])]
  67. # If the current node is the start of a new level, add its
  68. # parent to the ancestors list.
  69. if structure['new_level']:
  70. structure['ancestors'].append(unicode(previous))
  71. else:
  72. structure['new_level'] = True
  73. if ancestors:
  74. # Set up the ancestors list on the first item
  75. structure['ancestors'] = []
  76. if next:
  77. structure['closed_levels'] = range(current_level,
  78. getattr(next,
  79. opts.level_attr), -1)
  80. else:
  81. # All remaining levels need to be closed
  82. structure['closed_levels'] = range(current_level, -1, -1)
  83. # Return a deep copy of the structure dict so this function can
  84. # be used in situations where the iterator is consumed
  85. # immediately.
  86. yield current, copy.deepcopy(structure)
  87. def drilldown_tree_for_node(node, rel_cls=None, rel_field=None, count_attr=None,
  88. cumulative=False):
  89. """
  90. Creates a drilldown tree for the given node. A drilldown tree
  91. consists of a node's ancestors, itself and its immediate children,
  92. all in tree order.
  93. Optional arguments may be given to specify a ``Model`` class which
  94. is related to the node's class, for the purpose of adding related
  95. item counts to the node's children:
  96. ``rel_cls``
  97. A ``Model`` class which has a relation to the node's class.
  98. ``rel_field``
  99. The name of the field in ``rel_cls`` which holds the relation
  100. to the node's class.
  101. ``count_attr``
  102. The name of an attribute which should be added to each child in
  103. the drilldown tree, containing a count of how many instances
  104. of ``rel_cls`` are related through ``rel_field``.
  105. ``cumulative``
  106. If ``True``, the count will be for each child and all of its
  107. descendants, otherwise it will be for each child itself.
  108. """
  109. if rel_cls and rel_field and count_attr:
  110. children = node._tree_manager.add_related_count(
  111. node.get_children(), rel_cls, rel_field, count_attr, cumulative)
  112. else:
  113. children = node.get_children()
  114. return itertools.chain(node.get_ancestors(), [node], children)