1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
|
==================================
Working with trees in Django forms
==================================
.. contents::
:depth: 3
Fields
======
The following custom form fields are provided in the ``mptt.forms``
package.
``TreeNodeChoiceField``
-----------------------
This is the default formfield used by ``TreeForeignKey``
A subclass of `ModelChoiceField`_ which represents the tree level of
each node when generating option labels.
For example, where a form which used a ``ModelChoiceField``::
category = ModelChoiceField(queryset=Category.objects.all())
...would result in a select with the following options::
---------
Root 1
Child 1.1
Child 1.1.1
Root 2
Child 2.1
Child 2.1.1
Using a ``TreeNodeChoiceField`` instead::
category = TreeNodeChoiceField(queryset=Category.objects.all())
...would result in a select with the following options::
Root 1
--- Child 1.1
------ Child 1.1.1
Root 2
--- Child 2.1
------ Child 2.1.1
The text used to indicate a tree level can by customised by providing a
``level_indicator`` argument::
category = TreeNodeChoiceField(queryset=Category.objects.all(),
level_indicator='+--')
...which for this example would result in a select with the following
options::
Root 1
+-- Child 1.1
+--+-- Child 1.1.1
Root 2
+-- Child 2.1
+--+-- Child 2.1.1
The starting level can be set so querysets not including the root object can still be displayed in a convenient way. Use the ``start_level`` argument to set the starting point for levels::
obj = Category.objects.get(pk=1)
category = TreeNodeChoiceField(queryset=obj.get_descendants(),
start_level=obj.level)
...which for this example would result in a select with the following
options::
--- Child 1.1.1
.. _`ModelChoiceField`: https://docs.djangoproject.com/en/dev/ref/forms/fields/#django.forms.ModelChoiceField
``TreeNodeMultipleChoiceField``
-------------------------------
Just like ``TreeNodeChoiceField``, but accepts more than one value.
``TreeNodePositionField``
-------------------------
A subclass of `ChoiceField`_ whose choices default to the valid arguments
for the `move_to method`_.
.. _`ChoiceField`: https://docs.djangoproject.com/en/dev/ref/forms/fields/#choicefield
Forms
=====
The following custom form is provided in the ``mptt.forms`` package.
``MoveNodeForm``
----------------
A form which allows the user to move a given node from one location in
its tree to another, with optional restriction of the nodes which are
valid target nodes for the `move_to method`
Fields
~~~~~~
The form contains the following fields:
* ``target`` -- a ``TreeNodeChoiceField`` for selecting the target node
for the node movement.
Target nodes will be displayed as a single ``<select>`` with its
``size`` attribute set, so the user can scroll through the target
nodes without having to open the dropdown list first.
* ``position`` -- a ``TreeNodePositionField`` for selecting the position
of the node movement, related to the target node.
Construction
~~~~~~~~~~~~
Required arguments:
``node``
When constructing the form, the model instance representing the
node to be moved must be passed as the first argument.
Optional arguments:
``valid_targets``
If provided, this keyword argument will define the list of nodes
which are valid for selection in form. Otherwise, any instance of the
same model class as the node being moved will be available for
selection, apart from the node itself and any of its descendants.
For example, if you want to restrict the node to moving within its
own tree, pass a ``QuerySet`` containing everything in the node's
tree except itself and its descendants (to prevent invalid moves) and
the root node (as a user could choose to make the node a sibling of
the root node).
``target_select_size``
If provided, this keyword argument will be used to set the size of
the select used for the target node. Defaults to ``10``.
``position_choices``
A tuple of allowed position choices and their descriptions.
``level_indicator``
A string which will be used to represent a single tree level in the
target options.
``save()`` method
~~~~~~~~~~~~~~~~~
When the form's ``save()`` method is called, it will attempt to perform
the node movement as specified in the form.
If an invalid move is attempted, an error message will be added to the
form's non-field errors (accessible using
``{{ form.non_field_errors }}`` in templates) and the associated
``mptt.exceptions.InvalidMove`` will be re-raised.
It's recommended that you attempt to catch this error and, if caught,
allow your view to to fall through to rendering the form again again, so
the error message is displayed to the user.
Example usage
~~~~~~~~~~~~~
A sample view which shows basic usage of the form is provided below::
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from faqs.models import Category
from mptt.exceptions import InvalidMove
from mptt.forms import MoveNodeForm
def move_category(request, category_pk):
category = get_object_or_404(Category, pk=category_pk)
if request.method == 'POST':
form = MoveNodeForm(category, request.POST)
if form.is_valid():
try:
category = form.save()
return HttpResponseRedirect(category.get_absolute_url())
except InvalidMove:
pass
else:
form = MoveNodeForm(category)
return render_to_response('faqs/move_category.html', {
'form': form,
'category': category,
'category_tree': Category.objects.all(),
})
.. _`move_to method`: models.html#move-to-target-position-first-child
|