==================================
Working with trees in ``newforms``
==================================

.. admonition:: About this document

   This document contains details about the components Django MPTT
   provides for working with trees using Django's excellent `newforms`_
   library.

.. _`newforms`: http://www.djangoproject.com/documentation/newforms/

.. contents::
   :depth: 3

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.

Fields
~~~~~~

The form contains the following fields:

* ``target`` -- a ``ModelChoiceField`` 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. The select's
  options will show the tree structure of the valid target nodes like
  so::

     Root 1
     --- Child 1.1
     ------ Child 1.1.1
     Root 2
     --- Child 2.1
     --- Child 2.2

* ``position`` -- a ``ChoiceField`` for selecting the position of the
  node movement, related to the target node.

  Its options correspond to the valid values for the ``position``
  argument for the ``move_to`` method Django MTTP adds to model
  instances.

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``.

``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.tree.all(),
       })
