mirror of
				https://github.com/django/django.git
				synced 2025-11-03 21:25:09 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			683 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
			
		
		
	
	
			683 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
========
 | 
						|
Formsets
 | 
						|
========
 | 
						|
 | 
						|
.. module:: django.forms.formsets
 | 
						|
    :synopsis: An abstraction for working with multiple forms on the same page.
 | 
						|
 | 
						|
.. class:: BaseFormSet
 | 
						|
 | 
						|
A formset is a layer of abstraction to work 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 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:
 | 
						|
    ...     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,
 | 
						|
:func:`~django.forms.formsets.formset_factory` defines one extra form; the
 | 
						|
following example will display two blank forms::
 | 
						|
 | 
						|
    >>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
 | 
						|
 | 
						|
Iterating over the ``formset`` will render the forms in the order they were
 | 
						|
created. You can change this order by providing an alternate implementation for
 | 
						|
the ``__iter__()`` method.
 | 
						|
 | 
						|
Formsets can also be indexed into, which returns the corresponding form. If you
 | 
						|
override ``__iter__``, you will need to also override ``__getitem__`` to have
 | 
						|
matching behavior.
 | 
						|
 | 
						|
.. _formsets-initial-data:
 | 
						|
 | 
						|
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. Let's take a look at an
 | 
						|
example::
 | 
						|
 | 
						|
    >>> import datetime
 | 
						|
    >>> from django.forms import formset_factory
 | 
						|
    >>> from myapp.forms import ArticleForm
 | 
						|
    >>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
 | 
						|
    >>> formset = ArticleFormSet(initial=[
 | 
						|
    ...     {'title': 'Django is now open source',
 | 
						|
    ...      'pub_date': datetime.date.today(),}
 | 
						|
    ... ])
 | 
						|
 | 
						|
    >>> for form in formset:
 | 
						|
    ...     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 :func:`~django.forms.formsets.formset_factory`
 | 
						|
gives you the ability to limit the number of forms the formset will display::
 | 
						|
 | 
						|
    >>> from django.forms import formset_factory
 | 
						|
    >>> from myapp.forms import ArticleForm
 | 
						|
    >>> ArticleFormSet = formset_factory(ArticleForm, extra=2, max_num=1)
 | 
						|
    >>> formset = ArticleFormSet()
 | 
						|
    >>> for form in formset:
 | 
						|
    ...     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>
 | 
						|
 | 
						|
If the value of ``max_num`` is greater than the number of existing items in the
 | 
						|
initial data, 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``. For
 | 
						|
example, if ``extra=2`` and ``max_num=2`` and the formset is initialized with
 | 
						|
one ``initial`` item, a form for the initial item and one blank form will be
 | 
						|
displayed.
 | 
						|
 | 
						|
If the number of items in the initial data exceeds ``max_num``, all initial
 | 
						|
data forms will be displayed regardless of the value of ``max_num`` and no
 | 
						|
extra forms will be displayed. For example, if ``extra=3`` and ``max_num=1``
 | 
						|
and the formset is initialized with two initial items, two forms with the
 | 
						|
initial data will be displayed.
 | 
						|
 | 
						|
A ``max_num`` value of ``None`` (the default) puts a high limit on the number
 | 
						|
of forms displayed (1000). In practice this is equivalent to no limit.
 | 
						|
 | 
						|
By default, ``max_num`` only affects how many forms are displayed and does not
 | 
						|
affect validation.  If ``validate_max=True`` is passed to the
 | 
						|
:func:`~django.forms.formsets.formset_factory`, then ``max_num`` will affect
 | 
						|
validation.  See :ref:`validate_max`.
 | 
						|
 | 
						|
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::
 | 
						|
 | 
						|
    >>> from django.forms import formset_factory
 | 
						|
    >>> from myapp.forms import ArticleForm
 | 
						|
    >>> ArticleFormSet = formset_factory(ArticleForm)
 | 
						|
    >>> data = {
 | 
						|
    ...     'form-TOTAL_FORMS': '1',
 | 
						|
    ...     'form-INITIAL_FORMS': '0',
 | 
						|
    ...     'form-MAX_NUM_FORMS': '',
 | 
						|
    ... }
 | 
						|
    >>> formset = ArticleFormSet(data)
 | 
						|
    >>> 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': '2',
 | 
						|
    ...     'form-INITIAL_FORMS': '0',
 | 
						|
    ...     'form-MAX_NUM_FORMS': '',
 | 
						|
    ...     'form-0-title': 'Test',
 | 
						|
    ...     'form-0-pub_date': '1904-06-16',
 | 
						|
    ...     'form-1-title': 'Test',
 | 
						|
    ...     'form-1-pub_date': '', # <-- this date is missing but required
 | 
						|
    ... }
 | 
						|
    >>> formset = ArticleFormSet(data)
 | 
						|
    >>> formset.is_valid()
 | 
						|
    False
 | 
						|
    >>> formset.errors
 | 
						|
    [{}, {'pub_date': ['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.
 | 
						|
 | 
						|
Just like when using a normal ``Form``, each form in the formset may include
 | 
						|
HTML attributes such as ``maxlength`` for browser validation. However, forms of
 | 
						|
formsets won't include the ``required`` attribute as that validation may be
 | 
						|
incorrect when adding and deleting forms.
 | 
						|
 | 
						|
.. method:: BaseFormSet.total_error_count()
 | 
						|
 | 
						|
To check how many errors there are in the formset, we can use the
 | 
						|
``total_error_count`` method::
 | 
						|
 | 
						|
    >>> # Using the previous example
 | 
						|
    >>> formset.errors
 | 
						|
    [{}, {'pub_date': ['This field is required.']}]
 | 
						|
    >>> len(formset.errors)
 | 
						|
    2
 | 
						|
    >>> formset.total_error_count()
 | 
						|
    1
 | 
						|
 | 
						|
We can also check if form data differs from the initial data (i.e. the form was
 | 
						|
sent without any data)::
 | 
						|
 | 
						|
    >>> data = {
 | 
						|
    ...     'form-TOTAL_FORMS': '1',
 | 
						|
    ...     'form-INITIAL_FORMS': '0',
 | 
						|
    ...     'form-MAX_NUM_FORMS': '',
 | 
						|
    ...     'form-0-title': '',
 | 
						|
    ...     'form-0-pub_date': '',
 | 
						|
    ... }
 | 
						|
    >>> formset = ArticleFormSet(data)
 | 
						|
    >>> formset.has_changed()
 | 
						|
    False
 | 
						|
 | 
						|
.. _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': 'Test',
 | 
						|
    ...     'form-0-pub_date': '',
 | 
						|
    ... }
 | 
						|
    >>> formset = ArticleFormSet(data)
 | 
						|
    >>> formset.is_valid()
 | 
						|
    Traceback (most recent call last):
 | 
						|
    ...
 | 
						|
    django.forms.utils.ValidationError: ['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. On the other hand, if you are using JavaScript to allow
 | 
						|
deletion of existing objects, then you need to ensure the ones being removed
 | 
						|
are properly marked for deletion by including ``form-#-DELETE`` in the ``POST``
 | 
						|
data. It is expected that all forms are present in the ``POST`` data regardless.
 | 
						|
 | 
						|
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).
 | 
						|
 | 
						|
``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.
 | 
						|
 | 
						|
.. _empty_form:
 | 
						|
 | 
						|
``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 import BaseFormSet
 | 
						|
    >>> from django.forms import formset_factory
 | 
						|
    >>> from myapp.forms import ArticleForm
 | 
						|
 | 
						|
    >>> 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 form in self.forms:
 | 
						|
    ...             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': '2',
 | 
						|
    ...     'form-INITIAL_FORMS': '0',
 | 
						|
    ...     'form-MAX_NUM_FORMS': '',
 | 
						|
    ...     'form-0-title': 'Test',
 | 
						|
    ...     'form-0-pub_date': '1904-06-16',
 | 
						|
    ...     'form-1-title': 'Test',
 | 
						|
    ...     'form-1-pub_date': '1912-06-23',
 | 
						|
    ... }
 | 
						|
    >>> formset = ArticleFormSet(data)
 | 
						|
    >>> formset.is_valid()
 | 
						|
    False
 | 
						|
    >>> formset.errors
 | 
						|
    [{}, {}]
 | 
						|
    >>> formset.non_form_errors()
 | 
						|
    ['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.
 | 
						|
 | 
						|
.. _validate_max:
 | 
						|
 | 
						|
Validating the number of forms in a formset
 | 
						|
===========================================
 | 
						|
 | 
						|
Django provides a couple ways to validate the minimum or maximum number of
 | 
						|
submitted forms. Applications which need more customizable validation of the
 | 
						|
number of forms should use custom formset validation.
 | 
						|
 | 
						|
``validate_max``
 | 
						|
----------------
 | 
						|
 | 
						|
If ``validate_max=True`` is passed to
 | 
						|
:func:`~django.forms.formsets.formset_factory`, validation will also check
 | 
						|
that the number of forms in the data set, minus those marked for
 | 
						|
deletion, is less than or equal to ``max_num``.
 | 
						|
 | 
						|
    >>> from django.forms import formset_factory
 | 
						|
    >>> from myapp.forms import ArticleForm
 | 
						|
    >>> ArticleFormSet = formset_factory(ArticleForm, max_num=1, validate_max=True)
 | 
						|
    >>> data = {
 | 
						|
    ...     'form-TOTAL_FORMS': '2',
 | 
						|
    ...     'form-INITIAL_FORMS': '0',
 | 
						|
    ...     'form-MIN_NUM_FORMS': '',
 | 
						|
    ...     'form-MAX_NUM_FORMS': '',
 | 
						|
    ...     'form-0-title': 'Test',
 | 
						|
    ...     'form-0-pub_date': '1904-06-16',
 | 
						|
    ...     'form-1-title': 'Test 2',
 | 
						|
    ...     'form-1-pub_date': '1912-06-23',
 | 
						|
    ... }
 | 
						|
    >>> formset = ArticleFormSet(data)
 | 
						|
    >>> formset.is_valid()
 | 
						|
    False
 | 
						|
    >>> formset.errors
 | 
						|
    [{}, {}]
 | 
						|
    >>> formset.non_form_errors()
 | 
						|
    ['Please submit 1 or fewer forms.']
 | 
						|
 | 
						|
``validate_max=True`` validates against ``max_num`` strictly even if
 | 
						|
``max_num`` was exceeded because the amount of initial data supplied was
 | 
						|
excessive.
 | 
						|
 | 
						|
.. note::
 | 
						|
 | 
						|
    Regardless of ``validate_max``, if the number of forms in a data set
 | 
						|
    exceeds ``max_num`` by more than 1000, then the form will fail to validate
 | 
						|
    as if ``validate_max`` were set, and additionally only the first 1000
 | 
						|
    forms above ``max_num`` will be validated.  The remainder will be
 | 
						|
    truncated entirely.  This is to protect against memory exhaustion attacks
 | 
						|
    using forged POST requests.
 | 
						|
 | 
						|
``validate_min``
 | 
						|
----------------
 | 
						|
 | 
						|
If ``validate_min=True`` is passed to
 | 
						|
:func:`~django.forms.formsets.formset_factory`, validation will also check
 | 
						|
that the number of forms in the data set, minus those marked for
 | 
						|
deletion, is greater than or equal to ``min_num``.
 | 
						|
 | 
						|
    >>> from django.forms import formset_factory
 | 
						|
    >>> from myapp.forms import ArticleForm
 | 
						|
    >>> ArticleFormSet = formset_factory(ArticleForm, min_num=3, validate_min=True)
 | 
						|
    >>> data = {
 | 
						|
    ...     'form-TOTAL_FORMS': '2',
 | 
						|
    ...     'form-INITIAL_FORMS': '0',
 | 
						|
    ...     'form-MIN_NUM_FORMS': '',
 | 
						|
    ...     'form-MAX_NUM_FORMS': '',
 | 
						|
    ...     'form-0-title': 'Test',
 | 
						|
    ...     'form-0-pub_date': '1904-06-16',
 | 
						|
    ...     'form-1-title': 'Test 2',
 | 
						|
    ...     'form-1-pub_date': '1912-06-23',
 | 
						|
    ... }
 | 
						|
    >>> formset = ArticleFormSet(data)
 | 
						|
    >>> formset.is_valid()
 | 
						|
    False
 | 
						|
    >>> formset.errors
 | 
						|
    [{}, {}]
 | 
						|
    >>> formset.non_form_errors()
 | 
						|
    ['Please submit 3 or more forms.']
 | 
						|
 | 
						|
Dealing with ordering and deletion of forms
 | 
						|
===========================================
 | 
						|
 | 
						|
The :func:`~django.forms.formsets.formset_factory` provides two optional
 | 
						|
parameters ``can_order`` and ``can_delete`` to help with ordering of forms in
 | 
						|
formsets and deletion of forms from a formset.
 | 
						|
 | 
						|
``can_order``
 | 
						|
-------------
 | 
						|
 | 
						|
.. attribute:: BaseFormSet.can_order
 | 
						|
 | 
						|
Default: ``False``
 | 
						|
 | 
						|
Lets you create a formset with the ability to order::
 | 
						|
 | 
						|
    >>> from django.forms import formset_factory
 | 
						|
    >>> from myapp.forms import ArticleForm
 | 
						|
    >>> ArticleFormSet = formset_factory(ArticleForm, can_order=True)
 | 
						|
    >>> formset = ArticleFormSet(initial=[
 | 
						|
    ...     {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
 | 
						|
    ...     {'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
 | 
						|
    ... ])
 | 
						|
    >>> for form in formset:
 | 
						|
    ...     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="number" 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="number" 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="number" 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. Let's look at what will
 | 
						|
happen when the user changes these values::
 | 
						|
 | 
						|
    >>> data = {
 | 
						|
    ...     'form-TOTAL_FORMS': '3',
 | 
						|
    ...     'form-INITIAL_FORMS': '2',
 | 
						|
    ...     'form-MAX_NUM_FORMS': '',
 | 
						|
    ...     'form-0-title': 'Article #1',
 | 
						|
    ...     'form-0-pub_date': '2008-05-10',
 | 
						|
    ...     'form-0-ORDER': '2',
 | 
						|
    ...     'form-1-title': 'Article #2',
 | 
						|
    ...     'form-1-pub_date': '2008-05-11',
 | 
						|
    ...     'form-1-ORDER': '1',
 | 
						|
    ...     'form-2-title': 'Article #3',
 | 
						|
    ...     'form-2-pub_date': '2008-05-01',
 | 
						|
    ...     'form-2-ORDER': '0',
 | 
						|
    ... }
 | 
						|
 | 
						|
    >>> formset = ArticleFormSet(data, initial=[
 | 
						|
    ...     {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
 | 
						|
    ...     {'title': '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': 'Article #3'}
 | 
						|
    {'pub_date': datetime.date(2008, 5, 11), 'ORDER': 1, 'title': 'Article #2'}
 | 
						|
    {'pub_date': datetime.date(2008, 5, 10), 'ORDER': 2, 'title': 'Article #1'}
 | 
						|
 | 
						|
``can_delete``
 | 
						|
--------------
 | 
						|
 | 
						|
.. attribute:: BaseFormSet.can_delete
 | 
						|
 | 
						|
Default: ``False``
 | 
						|
 | 
						|
Lets you create a formset with the ability to select forms for deletion::
 | 
						|
 | 
						|
    >>> from django.forms import formset_factory
 | 
						|
    >>> from myapp.forms import ArticleForm
 | 
						|
    >>> ArticleFormSet = formset_factory(ArticleForm, can_delete=True)
 | 
						|
    >>> formset = ArticleFormSet(initial=[
 | 
						|
    ...     {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
 | 
						|
    ...     {'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
 | 
						|
    ... ])
 | 
						|
    >>> for form in formset:
 | 
						|
    ...     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-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': '3',
 | 
						|
    ...     'form-INITIAL_FORMS': '2',
 | 
						|
    ...     'form-MAX_NUM_FORMS': '',
 | 
						|
    ...     'form-0-title': 'Article #1',
 | 
						|
    ...     'form-0-pub_date': '2008-05-10',
 | 
						|
    ...     'form-0-DELETE': 'on',
 | 
						|
    ...     'form-1-title': 'Article #2',
 | 
						|
    ...     'form-1-pub_date': '2008-05-11',
 | 
						|
    ...     'form-1-DELETE': '',
 | 
						|
    ...     'form-2-title': '',
 | 
						|
    ...     'form-2-pub_date': '',
 | 
						|
    ...     'form-2-DELETE': '',
 | 
						|
    ... }
 | 
						|
 | 
						|
    >>> formset = ArticleFormSet(data, initial=[
 | 
						|
    ...     {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
 | 
						|
    ...     {'title': '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': 'Article #1'}]
 | 
						|
 | 
						|
If you are using a :class:`ModelFormSet<django.forms.models.BaseModelFormSet>`,
 | 
						|
model instances for deleted forms will be deleted when you call
 | 
						|
``formset.save()``.
 | 
						|
 | 
						|
If you call ``formset.save(commit=False)``, objects will not be deleted
 | 
						|
automatically.  You'll need to call ``delete()`` on each of the
 | 
						|
:attr:`formset.deleted_objects
 | 
						|
<django.forms.models.BaseModelFormSet.deleted_objects>` to actually delete
 | 
						|
them::
 | 
						|
 | 
						|
    >>> instances = formset.save(commit=False)
 | 
						|
    >>> for obj in formset.deleted_objects:
 | 
						|
    ...     obj.delete()
 | 
						|
 | 
						|
On the other hand, if you are using a plain ``FormSet``, it's up to you to
 | 
						|
handle ``formset.deleted_forms``, perhaps in your formset's ``save()`` method,
 | 
						|
as there's no general notion of what it means to delete a form.
 | 
						|
 | 
						|
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::
 | 
						|
 | 
						|
    >>> from django.forms import BaseFormSet
 | 
						|
    >>> from django.forms import formset_factory
 | 
						|
    >>> from myapp.forms import ArticleForm
 | 
						|
    >>> 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:
 | 
						|
    ...     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>
 | 
						|
 | 
						|
.. _custom-formset-form-kwargs:
 | 
						|
 | 
						|
Passing custom parameters to formset forms
 | 
						|
==========================================
 | 
						|
 | 
						|
Sometimes your form class takes custom parameters, like ``MyArticleForm``.
 | 
						|
You can pass this parameter when instantiating the formset::
 | 
						|
 | 
						|
    >>> from django.forms import BaseFormSet
 | 
						|
    >>> from django.forms import formset_factory
 | 
						|
    >>> from myapp.forms import ArticleForm
 | 
						|
 | 
						|
    >>> class MyArticleForm(ArticleForm):
 | 
						|
    ...     def __init__(self, *args, **kwargs):
 | 
						|
    ...         self.user = kwargs.pop('user')
 | 
						|
    ...         super(MyArticleForm, self).__init__(*args, **kwargs)
 | 
						|
 | 
						|
    >>> ArticleFormSet = formset_factory(MyArticleForm)
 | 
						|
    >>> formset = ArticleFormSet(form_kwargs={'user': request.user})
 | 
						|
 | 
						|
The ``form_kwargs`` may also depend on the specific form instance. The formset
 | 
						|
base class provides a ``get_form_kwargs`` method. The method takes a single
 | 
						|
argument - the index of the form in the formset. The index is ``None`` for the
 | 
						|
:ref:`empty_form`::
 | 
						|
 | 
						|
    >>> from django.forms import BaseFormSet
 | 
						|
    >>> from django.forms import formset_factory
 | 
						|
 | 
						|
    >>> class BaseArticleFormSet(BaseFormSet):
 | 
						|
    ...     def get_form_kwargs(self, index):
 | 
						|
    ...         kwargs = super(BaseArticleFormSet, self).get_form_kwargs(index)
 | 
						|
    ...         kwargs['custom_kwarg'] = index
 | 
						|
    ...         return kwargs
 | 
						|
 | 
						|
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::
 | 
						|
 | 
						|
    from django.forms import formset_factory
 | 
						|
    from django.shortcuts import render
 | 
						|
    from myapp.forms import ArticleForm
 | 
						|
 | 
						|
    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(request, '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 %}
 | 
						|
            {{ form }}
 | 
						|
            {% endfor %}
 | 
						|
        </table>
 | 
						|
    </form>
 | 
						|
 | 
						|
However there's a slight shortcut for the above by letting 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.
 | 
						|
 | 
						|
.. _manually-rendered-can-delete-and-can-order:
 | 
						|
 | 
						|
Manually rendered ``can_delete`` and ``can_order``
 | 
						|
--------------------------------------------------
 | 
						|
 | 
						|
If you manually render fields in the template, you can render
 | 
						|
``can_delete`` parameter with ``{{ form.DELETE }}``:
 | 
						|
 | 
						|
.. code-block:: html+django
 | 
						|
 | 
						|
    <form method="post" action="">
 | 
						|
        {{ formset.management_form }}
 | 
						|
        {% for form in formset %}
 | 
						|
            <ul>
 | 
						|
                <li>{{ form.title }}</li>
 | 
						|
                <li>{{ form.pub_date }}</li>
 | 
						|
                {% if formset.can_delete %}
 | 
						|
                    <li>{{ form.DELETE }}</li>
 | 
						|
                {% endif %}
 | 
						|
            </ul>
 | 
						|
        {% endfor %}
 | 
						|
    </form>
 | 
						|
 | 
						|
 | 
						|
Similarly, if the formset has the ability to order (``can_order=True``), it is
 | 
						|
possible to render it with ``{{ form.ORDER }}``.
 | 
						|
 | 
						|
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::
 | 
						|
 | 
						|
    from django.forms import formset_factory
 | 
						|
    from django.shortcuts import render
 | 
						|
    from myapp.forms import ArticleForm, BookForm
 | 
						|
 | 
						|
    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(request, '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.
 |