parts/django/docs/topics/forms/formsets.txt
changeset 307 c6bca38c1cbf
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/parts/django/docs/topics/forms/formsets.txt	Sat Jan 08 11:20:57 2011 +0530
@@ -0,0 +1,440 @@
+.. _formsets:
+
+Formsets
+========
+
+A formset is a layer of abstraction to working with multiple forms on the same
+page. It can be best compared to a data grid. Let's say you have the following
+form::
+
+    >>> from django import forms
+    >>> class ArticleForm(forms.Form):
+    ...     title = forms.CharField()
+    ...     pub_date = forms.DateField()
+
+You might want to allow the user to create several articles at once. To create
+a formset out of an ``ArticleForm`` you would do::
+
+    >>> from django.forms.formsets import formset_factory
+    >>> ArticleFormSet = formset_factory(ArticleForm)
+
+You now have created a formset named ``ArticleFormSet``. The formset gives you
+the ability to iterate over the forms in the formset and display them as you
+would with a regular form::
+
+    >>> formset = ArticleFormSet()
+    >>> for form in formset.forms:
+    ...     print form.as_table()
+    <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title" /></td></tr>
+    <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr>
+
+As you can see it only displayed one empty form. The number of empty forms
+that is displayed is controlled by the ``extra`` parameter. By default,
+``formset_factory`` defines one extra form; the following example will
+display two blank forms::
+
+    >>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
+
+Using initial data with a formset
+---------------------------------
+
+Initial data is what drives the main usability of a formset. As shown above
+you can define the number of extra forms. What this means is that you are
+telling the formset how many additional forms to show in addition to the
+number of forms it generates from the initial data. Lets take a look at an
+example::
+
+    >>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
+    >>> formset = ArticleFormSet(initial=[
+    ...     {'title': u'Django is now open source',
+    ...      'pub_date': datetime.date.today()},
+    ... ])
+
+    >>> for form in formset.forms:
+    ...     print form.as_table()
+    <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Django is now open source" id="id_form-0-title" /></td></tr>
+    <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-12" id="id_form-0-pub_date" /></td></tr>
+    <tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" id="id_form-1-title" /></td></tr>
+    <tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" id="id_form-1-pub_date" /></td></tr>
+    <tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title" /></td></tr>
+    <tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date" /></td></tr>
+
+There are now a total of three forms showing above. One for the initial data
+that was passed in and two extra forms. Also note that we are passing in a
+list of dictionaries as the initial data.
+
+.. seealso::
+
+    :ref:`Creating formsets from models with model formsets <model-formsets>`.
+
+.. _formsets-max-num:
+
+Limiting the maximum number of forms
+------------------------------------
+
+The ``max_num`` parameter to ``formset_factory`` gives you the ability to
+limit the maximum number of empty forms the formset will display::
+
+    >>> ArticleFormSet = formset_factory(ArticleForm, extra=2, max_num=1)
+    >>> formset = ArticleFormset()
+    >>> for form in formset.forms:
+    ...     print form.as_table()
+    <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title" /></td></tr>
+    <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr>
+
+.. versionchanged:: 1.2
+
+If the value of ``max_num`` is greater than the number of existing
+objects, up to ``extra`` additional blank forms will be added to the formset,
+so long as the total number of forms does not exceed ``max_num``.
+
+A ``max_num`` value of ``None`` (the default) puts no limit on the number of
+forms displayed. Please note that the default value of ``max_num`` was changed
+from ``0`` to ``None`` in version 1.2 to allow ``0`` as a valid value.
+
+Formset validation
+------------------
+
+Validation with a formset is almost identical to a regular ``Form``. There is
+an ``is_valid`` method on the formset to provide a convenient way to validate
+all forms in the formset::
+
+    >>> ArticleFormSet = formset_factory(ArticleForm)
+    >>> formset = ArticleFormSet({})
+    >>> formset.is_valid()
+    True
+
+We passed in no data to the formset which is resulting in a valid form. The
+formset is smart enough to ignore extra forms that were not changed. If we
+provide an invalid article::
+
+    >>> data = {
+    ...     'form-TOTAL_FORMS': u'2',
+    ...     'form-INITIAL_FORMS': u'0',
+    ...     'form-MAX_NUM_FORMS': u'',
+    ...     'form-0-title': u'Test',
+    ...     'form-0-pub_date': u'16 June 1904',
+    ...     'form-1-title': u'Test',
+    ...     'form-1-pub_date': u'', # <-- this date is missing but required
+    ... }
+    >>> formset = ArticleFormSet(data)
+    >>> formset.is_valid()
+    False
+    >>> formset.errors
+    [{}, {'pub_date': [u'This field is required.']}]
+
+As we can see, ``formset.errors`` is a list whose entries correspond to the
+forms in the formset. Validation was performed for each of the two forms, and
+the expected error message appears for the second item.
+
+.. _understanding-the-managementform:
+
+Understanding the ManagementForm
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You may have noticed the additional data (``form-TOTAL_FORMS``,
+``form-INITIAL_FORMS`` and ``form-MAX_NUM_FORMS``) that was required
+in the formset's data above. This data is required for the
+``ManagementForm``. This form is used by the formset to manage the
+collection of forms contained in the formset. If you don't provide
+this management data, an exception will be raised::
+
+    >>> data = {
+    ...     'form-0-title': u'Test',
+    ...     'form-0-pub_date': u'',
+    ... }
+    >>> formset = ArticleFormSet(data)
+    Traceback (most recent call last):
+    ...
+    django.forms.util.ValidationError: [u'ManagementForm data is missing or has been tampered with']
+
+It is used to keep track of how many form instances are being displayed. If
+you are adding new forms via JavaScript, you should increment the count fields
+in this form as well.
+
+The management form is available as an attribute of the formset
+itself. When rendering a formset in a template, you can include all
+the management data by rendering ``{{ my_formset.management_form }}``
+(substituting the name of your formset as appropriate).
+
+.. versionadded:: 1.1
+
+``total_form_count`` and ``initial_form_count``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``BaseFormSet`` has a couple of methods that are closely related to the
+``ManagementForm``, ``total_form_count`` and ``initial_form_count``.
+
+``total_form_count`` returns the total number of forms in this formset.
+``initial_form_count`` returns the number of forms in the formset that were
+pre-filled, and is also used to determine how many forms are required. You
+will probably never need to override either of these methods, so please be
+sure you understand what they do before doing so.
+
+.. versionadded:: 1.2
+
+``empty_form``
+~~~~~~~~~~~~~~
+
+``BaseFormSet`` provides an additional attribute ``empty_form`` which returns
+a form instance with a prefix of ``__prefix__`` for easier use in dynamic
+forms with JavaScript.
+
+Custom formset validation
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A formset has a ``clean`` method similar to the one on a ``Form`` class. This
+is where you define your own validation that works at the formset level::
+
+    >>> from django.forms.formsets import BaseFormSet
+
+    >>> class BaseArticleFormSet(BaseFormSet):
+    ...     def clean(self):
+    ...         """Checks that no two articles have the same title."""
+    ...         if any(self.errors):
+    ...             # Don't bother validating the formset unless each form is valid on its own
+    ...             return
+    ...         titles = []
+    ...         for i in range(0, self.total_form_count()):
+    ...             form = self.forms[i]
+    ...             title = form.cleaned_data['title']
+    ...             if title in titles:
+    ...                 raise forms.ValidationError, "Articles in a set must have distinct titles."
+    ...             titles.append(title)
+
+    >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
+    >>> data = {
+    ...     'form-TOTAL_FORMS': u'2',
+    ...     'form-INITIAL_FORMS': u'0',
+    ...     'form-MAX_NUM_FORMS': u'',
+    ...     'form-0-title': u'Test',
+    ...     'form-0-pub_date': u'16 June 1904',
+    ...     'form-1-title': u'Test',
+    ...     'form-1-pub_date': u'23 June 1912',
+    ... }
+    >>> formset = ArticleFormSet(data)
+    >>> formset.is_valid()
+    False
+    >>> formset.errors
+    [{}, {}]
+    >>> formset.non_form_errors()
+    [u'Articles in a set must have distinct titles.']
+
+The formset ``clean`` method is called after all the ``Form.clean`` methods
+have been called. The errors will be found using the ``non_form_errors()``
+method on the formset.
+
+Dealing with ordering and deletion of forms
+-------------------------------------------
+
+Common use cases with a formset is dealing with ordering and deletion of the
+form instances. This has been dealt with for you. The ``formset_factory``
+provides two optional parameters ``can_order`` and ``can_delete`` that will do
+the extra work of adding the extra fields and providing simpler ways of
+getting to that data.
+
+``can_order``
+~~~~~~~~~~~~~
+
+Default: ``False``
+
+Lets create a formset with the ability to order::
+
+    >>> ArticleFormSet = formset_factory(ArticleForm, can_order=True)
+    >>> formset = ArticleFormSet(initial=[
+    ...     {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
+    ...     {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
+    ... ])
+    >>> for form in formset.forms:
+    ...     print form.as_table()
+    <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title" /></td></tr>
+    <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date" /></td></tr>
+    <tr><th><label for="id_form-0-ORDER">Order:</label></th><td><input type="text" name="form-0-ORDER" value="1" id="id_form-0-ORDER" /></td></tr>
+    <tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title" /></td></tr>
+    <tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date" /></td></tr>
+    <tr><th><label for="id_form-1-ORDER">Order:</label></th><td><input type="text" name="form-1-ORDER" value="2" id="id_form-1-ORDER" /></td></tr>
+    <tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title" /></td></tr>
+    <tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date" /></td></tr>
+    <tr><th><label for="id_form-2-ORDER">Order:</label></th><td><input type="text" name="form-2-ORDER" id="id_form-2-ORDER" /></td></tr>
+
+This adds an additional field to each form. This new field is named ``ORDER``
+and is an ``forms.IntegerField``. For the forms that came from the initial
+data it automatically assigned them a numeric value. Lets look at what will
+happen when the user changes these values::
+
+    >>> data = {
+    ...     'form-TOTAL_FORMS': u'3',
+    ...     'form-INITIAL_FORMS': u'2',
+    ...     'form-MAX_NUM_FORMS': u'',
+    ...     'form-0-title': u'Article #1',
+    ...     'form-0-pub_date': u'2008-05-10',
+    ...     'form-0-ORDER': u'2',
+    ...     'form-1-title': u'Article #2',
+    ...     'form-1-pub_date': u'2008-05-11',
+    ...     'form-1-ORDER': u'1',
+    ...     'form-2-title': u'Article #3',
+    ...     'form-2-pub_date': u'2008-05-01',
+    ...     'form-2-ORDER': u'0',
+    ... }
+
+    >>> formset = ArticleFormSet(data, initial=[
+    ...     {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
+    ...     {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
+    ... ])
+    >>> formset.is_valid()
+    True
+    >>> for form in formset.ordered_forms:
+    ...     print form.cleaned_data
+    {'pub_date': datetime.date(2008, 5, 1), 'ORDER': 0, 'title': u'Article #3'}
+    {'pub_date': datetime.date(2008, 5, 11), 'ORDER': 1, 'title': u'Article #2'}
+    {'pub_date': datetime.date(2008, 5, 10), 'ORDER': 2, 'title': u'Article #1'}
+
+``can_delete``
+~~~~~~~~~~~~~~
+
+Default: ``False``
+
+Lets create a formset with the ability to delete::
+
+    >>> ArticleFormSet = formset_factory(ArticleForm, can_delete=True)
+    >>> formset = ArticleFormSet(initial=[
+    ...     {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
+    ...     {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
+    ... ])
+    >>> for form in formset.forms:
+    ....    print form.as_table()
+    <input type="hidden" name="form-TOTAL_FORMS" value="3" id="id_form-TOTAL_FORMS" /><input type="hidden" name="form-INITIAL_FORMS" value="2" id="id_form-INITIAL_FORMS" /><input type="hidden" name="form-MAX_NUM_FORMS" id="id_form-MAX_NUM_FORMS" />
+    <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title" /></td></tr>
+    <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date" /></td></tr>
+    <tr><th><label for="id_form-0-DELETE">Delete:</label></th><td><input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE" /></td></tr>
+    <tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title" /></td></tr>
+    <tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date" /></td></tr>
+    <tr><th><label for="id_form-1-DELETE">Delete:</label></th><td><input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE" /></td></tr>
+    <tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title" /></td></tr>
+    <tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date" /></td></tr>
+    <tr><th><label for="id_form-2-DELETE">Delete:</label></th><td><input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE" /></td></tr>
+
+Similar to ``can_order`` this adds a new field to each form named ``DELETE``
+and is a ``forms.BooleanField``. When data comes through marking any of the
+delete fields you can access them with ``deleted_forms``::
+
+    >>> data = {
+    ...     'form-TOTAL_FORMS': u'3',
+    ...     'form-INITIAL_FORMS': u'2',
+    ...     'form-MAX_NUM_FORMS': u'',
+    ...     'form-0-title': u'Article #1',
+    ...     'form-0-pub_date': u'2008-05-10',
+    ...     'form-0-DELETE': u'on',
+    ...     'form-1-title': u'Article #2',
+    ...     'form-1-pub_date': u'2008-05-11',
+    ...     'form-1-DELETE': u'',
+    ...     'form-2-title': u'',
+    ...     'form-2-pub_date': u'',
+    ...     'form-2-DELETE': u'',
+    ... }
+
+    >>> formset = ArticleFormSet(data, initial=[
+    ...     {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
+    ...     {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
+    ... ])
+    >>> [form.cleaned_data for form in formset.deleted_forms]
+    [{'DELETE': True, 'pub_date': datetime.date(2008, 5, 10), 'title': u'Article #1'}]
+
+Adding additional fields to a formset
+-------------------------------------
+
+If you need to add additional fields to the formset this can be easily
+accomplished. The formset base class provides an ``add_fields`` method. You
+can simply override this method to add your own fields or even redefine the
+default fields/attributes of the order and deletion fields::
+
+    >>> class BaseArticleFormSet(BaseFormSet):
+    ...     def add_fields(self, form, index):
+    ...         super(BaseArticleFormSet, self).add_fields(form, index)
+    ...         form.fields["my_field"] = forms.CharField()
+
+    >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
+    >>> formset = ArticleFormSet()
+    >>> for form in formset.forms:
+    ...     print form.as_table()
+    <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title" /></td></tr>
+    <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr>
+    <tr><th><label for="id_form-0-my_field">My field:</label></th><td><input type="text" name="form-0-my_field" id="id_form-0-my_field" /></td></tr>
+
+Using a formset in views and templates
+--------------------------------------
+
+Using a formset inside a view is as easy as using a regular ``Form`` class.
+The only thing you will want to be aware of is making sure to use the
+management form inside the template. Let's look at a sample view:
+
+.. code-block:: python
+
+    def manage_articles(request):
+        ArticleFormSet = formset_factory(ArticleForm)
+        if request.method == 'POST':
+            formset = ArticleFormSet(request.POST, request.FILES)
+            if formset.is_valid():
+                # do something with the formset.cleaned_data
+                pass
+        else:
+            formset = ArticleFormSet()
+        return render_to_response('manage_articles.html', {'formset': formset})
+
+The ``manage_articles.html`` template might look like this:
+
+.. code-block:: html+django
+
+    <form method="post" action="">
+        {{ formset.management_form }}
+        <table>
+            {% for form in formset.forms %}
+            {{ form }}
+            {% endfor %}
+        </table>
+    </form>
+
+However the above can be slightly shortcutted and let the formset itself deal
+with the management form:
+
+.. code-block:: html+django
+
+    <form method="post" action="">
+        <table>
+            {{ formset }}
+        </table>
+    </form>
+
+The above ends up calling the ``as_table`` method on the formset class.
+
+Using more than one formset in a view
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You are able to use more than one formset in a view if you like. Formsets
+borrow much of its behavior from forms. With that said you are able to use
+``prefix`` to prefix formset form field names with a given value to allow
+more than one formset to be sent to a view without name clashing. Lets take
+a look at how this might be accomplished:
+
+.. code-block:: python
+
+    def manage_articles(request):
+        ArticleFormSet = formset_factory(ArticleForm)
+        BookFormSet = formset_factory(BookForm)
+        if request.method == 'POST':
+            article_formset = ArticleFormSet(request.POST, request.FILES, prefix='articles')
+            book_formset = BookFormSet(request.POST, request.FILES, prefix='books')
+            if article_formset.is_valid() and book_formset.is_valid():
+                # do something with the cleaned_data on the formsets.
+                pass
+        else:
+            article_formset = ArticleFormSet(prefix='articles')
+            book_formset = BookFormSet(prefix='books')
+        return render_to_response('manage_articles.html', {
+            'article_formset': article_formset,
+            'book_formset': book_formset,
+        })
+
+You would then render the formsets as normal. It is important to point out
+that you need to pass ``prefix`` on both the POST and non-POST cases so that
+it is rendered and processed correctly.