mptt.forms: 80 total statements, 0.0% covered

Generated: Wed 2013-03-13 10:33 CET

Source file: /media/Envs/Envs/filer-gallery/lib/python2.7/site-packages/mptt/forms.py

Stats: 0 executed, 73 missed, 7 excluded, 99 ignored

  1. """
  2. Form components for working with trees.
  3. """
  4. from django import forms
  5. from django.forms.forms import NON_FIELD_ERRORS
  6. from django.forms.util import ErrorList
  7. from django.utils.encoding import smart_unicode
  8. from django.utils.html import conditional_escape, mark_safe
  9. from django.utils.translation import ugettext_lazy as _
  10. from mptt.exceptions import InvalidMove
  11. __all__ = ('TreeNodeChoiceField', 'TreeNodeMultipleChoiceField', 'TreeNodePositionField', 'MoveNodeForm')
  12. # Fields ######################################################################
  13. class TreeNodeChoiceField(forms.ModelChoiceField):
  14. """A ModelChoiceField for tree nodes."""
  15. def __init__(self, queryset, *args, **kwargs):
  16. self.level_indicator = kwargs.pop('level_indicator', u'---')
  17. if kwargs.get('required', True) and not 'empty_label' in kwargs:
  18. kwargs['empty_label'] = None
  19. # if a queryset is supplied, enforce ordering
  20. if hasattr(queryset, 'model'):
  21. mptt_opts = queryset.model._mptt_meta
  22. queryset = queryset.order_by(mptt_opts.tree_id_attr, mptt_opts.left_attr)
  23. super(TreeNodeChoiceField, self).__init__(queryset, *args, **kwargs)
  24. def _get_level_indicator(self, obj):
  25. level = getattr(obj, obj._mptt_meta.level_attr)
  26. return mark_safe(conditional_escape(self.level_indicator) * level)
  27. def label_from_instance(self, obj):
  28. """
  29. Creates labels which represent the tree level of each node when
  30. generating option labels.
  31. """
  32. level_indicator = self._get_level_indicator(obj)
  33. return mark_safe(u'%s %s' % (level_indicator, conditional_escape(smart_unicode(obj))))
  34. class TreeNodeMultipleChoiceField(TreeNodeChoiceField, forms.ModelMultipleChoiceField):
  35. """A ModelMultipleChoiceField for tree nodes."""
  36. def __init__(self, queryset, *args, **kwargs):
  37. self.level_indicator = kwargs.pop('level_indicator', u'---')
  38. if kwargs.get('required', True) and not 'empty_label' in kwargs:
  39. kwargs['empty_label'] = None
  40. # if a queryset is supplied, enforce ordering
  41. if hasattr(queryset, 'model'):
  42. mptt_opts = queryset.model._mptt_meta
  43. queryset = queryset.order_by(mptt_opts.tree_id_attr, mptt_opts.left_attr)
  44. # For some reason ModelMultipleChoiceField constructor passes kwargs
  45. # as args to its super(), which causes 'multiple values for keyword arg'
  46. # error sometimes. So we skip it (that constructor does nothing anyway!)
  47. forms.ModelChoiceField.__init__(self, queryset, *args, **kwargs)
  48. class TreeNodePositionField(forms.ChoiceField):
  49. """A ChoiceField for specifying position relative to another node."""
  50. FIRST_CHILD = 'first-child'
  51. LAST_CHILD = 'last-child'
  52. LEFT = 'left'
  53. RIGHT = 'right'
  54. DEFAULT_CHOICES = (
  55. (FIRST_CHILD, _('First child')),
  56. (LAST_CHILD, _('Last child')),
  57. (LEFT, _('Left sibling')),
  58. (RIGHT, _('Right sibling')),
  59. )
  60. def __init__(self, *args, **kwargs):
  61. if 'choices' not in kwargs:
  62. kwargs['choices'] = self.DEFAULT_CHOICES
  63. super(TreeNodePositionField, self).__init__(*args, **kwargs)
  64. # Forms #######################################################################
  65. class MoveNodeForm(forms.Form):
  66. """
  67. A form which allows the user to move a given node from one location
  68. in its tree to another, with optional restriction of the nodes which
  69. are valid target nodes for the move.
  70. """
  71. target = TreeNodeChoiceField(queryset=None)
  72. position = TreeNodePositionField()
  73. def __init__(self, node, *args, **kwargs):
  74. """
  75. The ``node`` to be moved must be provided. The following keyword
  76. arguments are also accepted::
  77. ``valid_targets``
  78. Specifies a ``QuerySet`` of valid targets for the move. If
  79. not provided, valid targets will consist of everything other
  80. node of the same type, apart from the node itself and any
  81. descendants.
  82. For example, if you want to restrict the node to moving
  83. within its own tree, pass a ``QuerySet`` containing
  84. everything in the node's tree except itself and its
  85. descendants (to prevent invalid moves) and the root node (as
  86. a user could choose to make the node a sibling of the root
  87. node).
  88. ``target_select_size``
  89. The size of the select element used for the target node.
  90. Defaults to ``10``.
  91. ``position_choices``
  92. A tuple of allowed position choices and their descriptions.
  93. Defaults to ``TreeNodePositionField.DEFAULT_CHOICES``.
  94. ``level_indicator``
  95. A string which will be used to represent a single tree level
  96. in the target options.
  97. """
  98. self.node = node
  99. valid_targets = kwargs.pop('valid_targets', None)
  100. target_select_size = kwargs.pop('target_select_size', 10)
  101. position_choices = kwargs.pop('position_choices', None)
  102. level_indicator = kwargs.pop('level_indicator', None)
  103. super(MoveNodeForm, self).__init__(*args, **kwargs)
  104. opts = node._mptt_meta
  105. if valid_targets is None:
  106. valid_targets = node._tree_manager.exclude(**{
  107. opts.tree_id_attr: getattr(node, opts.tree_id_attr),
  108. '%s__gte' % opts.left_attr: getattr(node, opts.left_attr),
  109. '%s__lte' % opts.right_attr: getattr(node, opts.right_attr),
  110. })
  111. self.fields['target'].queryset = valid_targets
  112. self.fields['target'].widget.attrs['size'] = target_select_size
  113. if level_indicator:
  114. self.fields['target'].level_indicator = level_indicator
  115. if position_choices:
  116. self.fields['position_choices'].choices = position_choices
  117. def save(self):
  118. """
  119. Attempts to move the node using the selected target and
  120. position.
  121. If an invalid move is attempted, the related error message will
  122. be added to the form's non-field errors and the error will be
  123. re-raised. Callers should attempt to catch ``InvalidNode`` to
  124. redisplay the form with the error, should it occur.
  125. """
  126. try:
  127. self.node.move_to(self.cleaned_data['target'],
  128. self.cleaned_data['position'])
  129. return self.node
  130. except InvalidMove, e:
  131. self.errors[NON_FIELD_ERRORS] = ErrorList(e)
  132. raise
  133. class MPTTAdminForm(forms.ModelForm):
  134. """
  135. A form which validates that the chosen parent for a node isn't one of
  136. it's descendants.
  137. """
  138. def clean(self):
  139. cleaned_data = super(MPTTAdminForm, self).clean()
  140. opts = self._meta.model._mptt_meta
  141. parent = cleaned_data.get(opts.parent_attr)
  142. if self.instance and parent:
  143. if parent.is_descendant_of(self.instance, include_self=True):
  144. if opts.parent_attr not in self._errors:
  145. self._errors[opts.parent_attr] = forms.util.ErrorList()
  146. self._errors[opts.parent_attr].append(_('Invalid parent'))
  147. del self.cleaned_data[opts.parent_attr]
  148. return cleaned_data