123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833 |
- ========
- Formsets
- ========
- .. currentmodule:: django.forms.formsets
- .. 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 class named ``ArticleFormSet``.
- Instantiating 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 create a formset class to display two blank forms::
- >>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
- Iterating over a 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.
- If you use an ``initial`` for displaying a formset, you should pass the same
- ``initial`` when processing that formset's submission so that the formset can
- detect which forms were changed by the user. For example, you might have
- something like: ``ArticleFormSet(request.POST, initial=[...])``.
- .. 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`.
- .. _formsets-absolute-max:
- Limiting the maximum number of instantiated forms
- =================================================
- .. versionadded:: 3.2
- The ``absolute_max`` parameter to :func:`.formset_factory` allows limiting the
- number of forms that can be instantiated when supplying ``POST`` data. This
- protects against memory exhaustion attacks using forged ``POST`` requests::
- >>> from django.forms.formsets import formset_factory
- >>> from myapp.forms import ArticleForm
- >>> ArticleFormSet = formset_factory(ArticleForm, absolute_max=1500)
- >>> data = {
- ... 'form-TOTAL_FORMS': '1501',
- ... 'form-INITIAL_FORMS': '0',
- ... }
- >>> formset = ArticleFormSet(data)
- >>> len(formset.forms)
- 1500
- >>> formset.is_valid()
- False
- >>> formset.non_form_errors()
- ['Please submit at most 1000 forms.']
- When ``absolute_max`` is ``None``, it defaults to ``max_num + 1000``. (If
- ``max_num`` is ``None``, it defaults to ``2000``).
- If ``absolute_max`` is less than ``max_num``, a ``ValueError`` will be raised.
- 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',
- ... }
- >>> 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-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 field in a formset's forms may
- include HTML attributes such as ``maxlength`` for browser validation. However,
- form fields 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-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``) 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, the formset will be invalid::
- >>> data = {
- ... 'form-0-title': 'Test',
- ... 'form-0-pub_date': '',
- ... }
- >>> formset = ArticleFormSet(data)
- >>> formset.is_valid()
- False
- 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).
- .. note::
- As well as the ``form-TOTAL_FORMS`` and ``form-INITIAL_FORMS`` fields shown
- in the examples here, the management form also includes
- ``form-MIN_NUM_FORMS`` and ``form-MAX_NUM_FORMS`` fields. They are output
- with the rest of the management form, but only for the convenience of
- client-side code. These fields are not required and so are not shown in
- the example ``POST`` data.
- .. versionchanged:: 3.2
- ``formset.is_valid()`` now returns ``False`` rather than raising an
- exception when the management form is missing or has been tampered with.
- ``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.
- ``error_messages``
- ------------------
- .. versionadded:: 3.2
- The ``error_messages`` argument lets you override the default messages that the
- formset will raise. Pass in a dictionary with keys matching the error messages
- you want to override. For example, here is the default error message when the
- management form is missing::
- >>> formset = ArticleFormSet({})
- >>> formset.is_valid()
- False
- >>> formset.non_form_errors()
- ['ManagementForm data is missing or has been tampered with. Missing fields: form-TOTAL_FORMS, form-INITIAL_FORMS. You may need to file a bug report if the issue persists.']
- And here is a custom error message::
- >>> formset = ArticleFormSet({}, error_messages={'missing_management_form': 'Sorry, something went wrong.'})
- >>> formset.is_valid()
- False
- >>> formset.non_form_errors()
- ['Sorry, something went wrong.']
- 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.core.exceptions import ValidationError
- >>> 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:
- ... if self.can_delete and self._should_delete_form(form):
- ... continue
- ... title = form.cleaned_data.get('title')
- ... if title in titles:
- ... raise 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-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.
- 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:
- ``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-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 at most 1 form.']
- ``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 ``absolute_max``, then the form will fail to validate as if
- ``validate_max`` were set, and additionally only the first ``absolute_max``
- forms will be validated. The remainder will be truncated entirely. This is
- to protect against memory exhaustion attacks using forged POST requests.
- See :ref:`formsets-absolute-max`.
- ``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-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 at least 3 forms.']
- .. note::
- Regardless of ``validate_min``, if a formset contains no data, then
- ``extra + min_num`` empty forms will be displayed.
- 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-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'}
- :class:`~django.forms.formsets.BaseFormSet` also provides an
- :attr:`~django.forms.formsets.BaseFormSet.ordering_widget` attribute and
- :meth:`~django.forms.formsets.BaseFormSet.get_ordering_widget` method that
- control the widget used with
- :attr:`~django.forms.formsets.BaseFormSet.can_order`.
- ``ordering_widget``
- ^^^^^^^^^^^^^^^^^^^
- .. attribute:: BaseFormSet.ordering_widget
- Default: :class:`~django.forms.NumberInput`
- Set ``ordering_widget`` to specify the widget class to be used with
- ``can_order``::
- >>> from django.forms import BaseFormSet, formset_factory
- >>> from myapp.forms import ArticleForm
- >>> class BaseArticleFormSet(BaseFormSet):
- ... ordering_widget = HiddenInput
- >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet, can_order=True)
- ``get_ordering_widget``
- ^^^^^^^^^^^^^^^^^^^^^^^
- .. method:: BaseFormSet.get_ordering_widget()
- Override ``get_ordering_widget()`` if you need to provide a widget instance for
- use with ``can_order``::
- >>> from django.forms import BaseFormSet, formset_factory
- >>> from myapp.forms import ArticleForm
- >>> class BaseArticleFormSet(BaseFormSet):
- ... def get_ordering_widget(self):
- ... return HiddenInput(attrs={'class': 'ordering'})
- >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet, can_order=True)
- ``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-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.
- ``can_delete_extra``
- --------------------
- .. versionadded:: 3.2
- .. attribute:: BaseFormSet.can_delete_extra
- Default: ``True``
- While setting ``can_delete=True``, specifying ``can_delete_extra=False`` will
- remove the option to delete extra forms.
- 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 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().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, user, **kwargs):
- ... self.user = user
- ... super().__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().get_form_kwargs(index)
- ... kwargs['custom_kwarg'] = index
- ... return kwargs
- .. _formset-prefix:
- Customizing a formset's prefix
- ==============================
- In the rendered HTML, formsets include a prefix on each field's name. By
- default, the prefix is ``'form'``, but it can be customized using the formset's
- ``prefix`` argument.
- For example, in the default case, you might see:
- .. code-block:: html
- <label for="id_form-0-title">Title:</label>
- <input type="text" name="form-0-title" id="id_form-0-title">
- But with ``ArticleFormset(prefix='article')`` that becomes:
- .. code-block:: html
- <label for="id_article-0-title">Title:</label>
- <input type="text" name="article-0-title" id="id_article-0-title">
- This is useful if you want to :ref:`use more than one formset in a view
- <multiple-formsets-in-view>`.
- Using a formset in views and templates
- ======================================
- Using a formset inside a view is not very different from 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">
- {{ 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">
- <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">
- {{ 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 }}``.
- .. _multiple-formsets-in-view:
- 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. Let's 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.
- Each formset's :ref:`prefix <formset-prefix>` replaces the default ``form``
- prefix that's added to each field's ``name`` and ``id`` HTML attributes.
|