| .. _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: |
| ... 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) |
| |
| .. versionchanged:: 1.3 |
| |
| Prior to Django 1.3, formset instances were not iterable. To render |
| the formset you iterated over the ``forms`` attribute:: |
| |
| >>> formset = ArticleFormSet() |
| >>> for form in formset.forms: |
| ... print form.as_table() |
| |
| Iterating over ``formset.forms`` will render the forms in the order |
| they were created. The default formset iterator also renders the forms |
| in this order, but you can change this order by providing an alternate |
| implementation for the :meth:`__iter__()` method. |
| |
| 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: |
| ... 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: |
| ... 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) |
| >>> data = { |
| ... 'form-TOTAL_FORMS': u'1', |
| ... 'form-INITIAL_FORMS': u'0', |
| ... 'form-MAX_NUM_FORMS': u'', |
| ... } |
| >>> 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': u'2', |
| ... 'form-INITIAL_FORMS': u'0', |
| ... 'form-MAX_NUM_FORMS': u'', |
| ... 'form-0-title': u'Test', |
| ... 'form-0-pub_date': u'1904-06-16', |
| ... '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). |
| |
| ``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'1904-06-16', |
| ... 'form-1-title': u'Test', |
| ... 'form-1-pub_date': u'1912-06-23', |
| ... } |
| >>> 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: |
| ... 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: |
| .... 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: |
| ... 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 %} |
| {{ 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. |