PageRenderTime 105ms CodeModel.GetById 40ms app.highlight 4ms RepoModel.GetById 56ms app.codeStats 0ms

/docs/topics/forms/formsets.txt

https://code.google.com/p/mango-py/
Plain Text | 457 lines | 370 code | 87 blank | 0 comment | 0 complexity | d8293d99d0769ac9680894e4ba628935 MD5 | raw file
  1.. _formsets:
  2
  3Formsets
  4========
  5
  6A formset is a layer of abstraction to working with multiple forms on the same
  7page. It can be best compared to a data grid. Let's say you have the following
  8form::
  9
 10    >>> from django import forms
 11    >>> class ArticleForm(forms.Form):
 12    ...     title = forms.CharField()
 13    ...     pub_date = forms.DateField()
 14
 15You might want to allow the user to create several articles at once. To create
 16a formset out of an ``ArticleForm`` you would do::
 17
 18    >>> from django.forms.formsets import formset_factory
 19    >>> ArticleFormSet = formset_factory(ArticleForm)
 20
 21You now have created a formset named ``ArticleFormSet``. The formset gives you
 22the ability to iterate over the forms in the formset and display them as you
 23would with a regular form::
 24
 25    >>> formset = ArticleFormSet()
 26    >>> for form in formset:
 27    ...     print form.as_table()
 28    <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>
 29    <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>
 30
 31As you can see it only displayed one empty form. The number of empty forms
 32that is displayed is controlled by the ``extra`` parameter. By default,
 33``formset_factory`` defines one extra form; the following example will
 34display two blank forms::
 35
 36    >>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
 37
 38.. versionchanged:: 1.3
 39
 40Prior to Django 1.3, formset instances were not iterable. To render
 41the formset you iterated over the ``forms`` attribute::
 42
 43    >>> formset = ArticleFormSet()
 44    >>> for form in formset.forms:
 45    ...     print form.as_table()
 46
 47Iterating over ``formset.forms`` will render the forms in the order
 48they were created. The default formset iterator also renders the forms
 49in this order, but you can change this order by providing an alternate
 50implementation for the :meth:`__iter__()` method.
 51
 52Using initial data with a formset
 53---------------------------------
 54
 55Initial data is what drives the main usability of a formset. As shown above
 56you can define the number of extra forms. What this means is that you are
 57telling the formset how many additional forms to show in addition to the
 58number of forms it generates from the initial data. Lets take a look at an
 59example::
 60
 61    >>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
 62    >>> formset = ArticleFormSet(initial=[
 63    ...     {'title': u'Django is now open source',
 64    ...      'pub_date': datetime.date.today()},
 65    ... ])
 66
 67    >>> for form in formset:
 68    ...     print form.as_table()
 69    <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>
 70    <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>
 71    <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>
 72    <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>
 73    <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>
 74    <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>
 75
 76There are now a total of three forms showing above. One for the initial data
 77that was passed in and two extra forms. Also note that we are passing in a
 78list of dictionaries as the initial data.
 79
 80.. seealso::
 81
 82    :ref:`Creating formsets from models with model formsets <model-formsets>`.
 83
 84.. _formsets-max-num:
 85
 86Limiting the maximum number of forms
 87------------------------------------
 88
 89The ``max_num`` parameter to ``formset_factory`` gives you the ability to
 90limit the maximum number of empty forms the formset will display::
 91
 92    >>> ArticleFormSet = formset_factory(ArticleForm, extra=2, max_num=1)
 93    >>> formset = ArticleFormset()
 94    >>> for form in formset:
 95    ...     print form.as_table()
 96    <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>
 97    <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>
 98
 99.. versionchanged:: 1.2
100
101If the value of ``max_num`` is greater than the number of existing
102objects, up to ``extra`` additional blank forms will be added to the formset,
103so long as the total number of forms does not exceed ``max_num``.
104
105A ``max_num`` value of ``None`` (the default) puts no limit on the number of
106forms displayed. Please note that the default value of ``max_num`` was changed
107from ``0`` to ``None`` in version 1.2 to allow ``0`` as a valid value.
108
109Formset validation
110------------------
111
112Validation with a formset is almost identical to a regular ``Form``. There is
113an ``is_valid`` method on the formset to provide a convenient way to validate
114all forms in the formset::
115
116    >>> ArticleFormSet = formset_factory(ArticleForm)
117    >>> data = {
118    ...     'form-TOTAL_FORMS': u'1',
119    ...     'form-INITIAL_FORMS': u'0',
120    ...     'form-MAX_NUM_FORMS': u'',
121    ... }
122    >>> formset = ArticleFormSet(data)
123    >>> formset.is_valid()
124    True
125
126We passed in no data to the formset which is resulting in a valid form. The
127formset is smart enough to ignore extra forms that were not changed. If we
128provide an invalid article::
129
130    >>> data = {
131    ...     'form-TOTAL_FORMS': u'2',
132    ...     'form-INITIAL_FORMS': u'0',
133    ...     'form-MAX_NUM_FORMS': u'',
134    ...     'form-0-title': u'Test',
135    ...     'form-0-pub_date': u'1904-06-16',
136    ...     'form-1-title': u'Test',
137    ...     'form-1-pub_date': u'', # <-- this date is missing but required
138    ... }
139    >>> formset = ArticleFormSet(data)
140    >>> formset.is_valid()
141    False
142    >>> formset.errors
143    [{}, {'pub_date': [u'This field is required.']}]
144
145As we can see, ``formset.errors`` is a list whose entries correspond to the
146forms in the formset. Validation was performed for each of the two forms, and
147the expected error message appears for the second item.
148
149.. _understanding-the-managementform:
150
151Understanding the ManagementForm
152~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
153
154You may have noticed the additional data (``form-TOTAL_FORMS``,
155``form-INITIAL_FORMS`` and ``form-MAX_NUM_FORMS``) that was required
156in the formset's data above. This data is required for the
157``ManagementForm``. This form is used by the formset to manage the
158collection of forms contained in the formset. If you don't provide
159this management data, an exception will be raised::
160
161    >>> data = {
162    ...     'form-0-title': u'Test',
163    ...     'form-0-pub_date': u'',
164    ... }
165    >>> formset = ArticleFormSet(data)
166    Traceback (most recent call last):
167    ...
168    django.forms.util.ValidationError: [u'ManagementForm data is missing or has been tampered with']
169
170It is used to keep track of how many form instances are being displayed. If
171you are adding new forms via JavaScript, you should increment the count fields
172in this form as well.
173
174The management form is available as an attribute of the formset
175itself. When rendering a formset in a template, you can include all
176the management data by rendering ``{{ my_formset.management_form }}``
177(substituting the name of your formset as appropriate).
178
179``total_form_count`` and ``initial_form_count``
180~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
181
182``BaseFormSet`` has a couple of methods that are closely related to the
183``ManagementForm``, ``total_form_count`` and ``initial_form_count``.
184
185``total_form_count`` returns the total number of forms in this formset.
186``initial_form_count`` returns the number of forms in the formset that were
187pre-filled, and is also used to determine how many forms are required. You
188will probably never need to override either of these methods, so please be
189sure you understand what they do before doing so.
190
191.. versionadded:: 1.2
192
193``empty_form``
194~~~~~~~~~~~~~~
195
196``BaseFormSet`` provides an additional attribute ``empty_form`` which returns
197a form instance with a prefix of ``__prefix__`` for easier use in dynamic
198forms with JavaScript.
199
200Custom formset validation
201~~~~~~~~~~~~~~~~~~~~~~~~~
202
203A formset has a ``clean`` method similar to the one on a ``Form`` class. This
204is where you define your own validation that works at the formset level::
205
206    >>> from django.forms.formsets import BaseFormSet
207
208    >>> class BaseArticleFormSet(BaseFormSet):
209    ...     def clean(self):
210    ...         """Checks that no two articles have the same title."""
211    ...         if any(self.errors):
212    ...             # Don't bother validating the formset unless each form is valid on its own
213    ...             return
214    ...         titles = []
215    ...         for i in range(0, self.total_form_count()):
216    ...             form = self.forms[i]
217    ...             title = form.cleaned_data['title']
218    ...             if title in titles:
219    ...                 raise forms.ValidationError("Articles in a set must have distinct titles.")
220    ...             titles.append(title)
221
222    >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
223    >>> data = {
224    ...     'form-TOTAL_FORMS': u'2',
225    ...     'form-INITIAL_FORMS': u'0',
226    ...     'form-MAX_NUM_FORMS': u'',
227    ...     'form-0-title': u'Test',
228    ...     'form-0-pub_date': u'1904-06-16',
229    ...     'form-1-title': u'Test',
230    ...     'form-1-pub_date': u'1912-06-23',
231    ... }
232    >>> formset = ArticleFormSet(data)
233    >>> formset.is_valid()
234    False
235    >>> formset.errors
236    [{}, {}]
237    >>> formset.non_form_errors()
238    [u'Articles in a set must have distinct titles.']
239
240The formset ``clean`` method is called after all the ``Form.clean`` methods
241have been called. The errors will be found using the ``non_form_errors()``
242method on the formset.
243
244Dealing with ordering and deletion of forms
245-------------------------------------------
246
247Common use cases with a formset is dealing with ordering and deletion of the
248form instances. This has been dealt with for you. The ``formset_factory``
249provides two optional parameters ``can_order`` and ``can_delete`` that will do
250the extra work of adding the extra fields and providing simpler ways of
251getting to that data.
252
253``can_order``
254~~~~~~~~~~~~~
255
256Default: ``False``
257
258Lets create a formset with the ability to order::
259
260    >>> ArticleFormSet = formset_factory(ArticleForm, can_order=True)
261    >>> formset = ArticleFormSet(initial=[
262    ...     {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
263    ...     {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
264    ... ])
265    >>> for form in formset:
266    ...     print form.as_table()
267    <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>
268    <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>
269    <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>
270    <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>
271    <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>
272    <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>
273    <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>
274    <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>
275    <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>
276
277This adds an additional field to each form. This new field is named ``ORDER``
278and is an ``forms.IntegerField``. For the forms that came from the initial
279data it automatically assigned them a numeric value. Lets look at what will
280happen when the user changes these values::
281
282    >>> data = {
283    ...     'form-TOTAL_FORMS': u'3',
284    ...     'form-INITIAL_FORMS': u'2',
285    ...     'form-MAX_NUM_FORMS': u'',
286    ...     'form-0-title': u'Article #1',
287    ...     'form-0-pub_date': u'2008-05-10',
288    ...     'form-0-ORDER': u'2',
289    ...     'form-1-title': u'Article #2',
290    ...     'form-1-pub_date': u'2008-05-11',
291    ...     'form-1-ORDER': u'1',
292    ...     'form-2-title': u'Article #3',
293    ...     'form-2-pub_date': u'2008-05-01',
294    ...     'form-2-ORDER': u'0',
295    ... }
296
297    >>> formset = ArticleFormSet(data, initial=[
298    ...     {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
299    ...     {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
300    ... ])
301    >>> formset.is_valid()
302    True
303    >>> for form in formset.ordered_forms:
304    ...     print form.cleaned_data
305    {'pub_date': datetime.date(2008, 5, 1), 'ORDER': 0, 'title': u'Article #3'}
306    {'pub_date': datetime.date(2008, 5, 11), 'ORDER': 1, 'title': u'Article #2'}
307    {'pub_date': datetime.date(2008, 5, 10), 'ORDER': 2, 'title': u'Article #1'}
308
309``can_delete``
310~~~~~~~~~~~~~~
311
312Default: ``False``
313
314Lets create a formset with the ability to delete::
315
316    >>> ArticleFormSet = formset_factory(ArticleForm, can_delete=True)
317    >>> formset = ArticleFormSet(initial=[
318    ...     {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
319    ...     {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
320    ... ])
321    >>> for form in formset:
322    ....    print form.as_table()
323    <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" />
324    <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>
325    <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>
326    <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>
327    <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>
328    <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>
329    <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>
330    <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>
331    <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>
332    <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>
333
334Similar to ``can_order`` this adds a new field to each form named ``DELETE``
335and is a ``forms.BooleanField``. When data comes through marking any of the
336delete fields you can access them with ``deleted_forms``::
337
338    >>> data = {
339    ...     'form-TOTAL_FORMS': u'3',
340    ...     'form-INITIAL_FORMS': u'2',
341    ...     'form-MAX_NUM_FORMS': u'',
342    ...     'form-0-title': u'Article #1',
343    ...     'form-0-pub_date': u'2008-05-10',
344    ...     'form-0-DELETE': u'on',
345    ...     'form-1-title': u'Article #2',
346    ...     'form-1-pub_date': u'2008-05-11',
347    ...     'form-1-DELETE': u'',
348    ...     'form-2-title': u'',
349    ...     'form-2-pub_date': u'',
350    ...     'form-2-DELETE': u'',
351    ... }
352
353    >>> formset = ArticleFormSet(data, initial=[
354    ...     {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
355    ...     {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
356    ... ])
357    >>> [form.cleaned_data for form in formset.deleted_forms]
358    [{'DELETE': True, 'pub_date': datetime.date(2008, 5, 10), 'title': u'Article #1'}]
359
360Adding additional fields to a formset
361-------------------------------------
362
363If you need to add additional fields to the formset this can be easily
364accomplished. The formset base class provides an ``add_fields`` method. You
365can simply override this method to add your own fields or even redefine the
366default fields/attributes of the order and deletion fields::
367
368    >>> class BaseArticleFormSet(BaseFormSet):
369    ...     def add_fields(self, form, index):
370    ...         super(BaseArticleFormSet, self).add_fields(form, index)
371    ...         form.fields["my_field"] = forms.CharField()
372
373    >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
374    >>> formset = ArticleFormSet()
375    >>> for form in formset:
376    ...     print form.as_table()
377    <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>
378    <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>
379    <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>
380
381Using a formset in views and templates
382--------------------------------------
383
384Using a formset inside a view is as easy as using a regular ``Form`` class.
385The only thing you will want to be aware of is making sure to use the
386management form inside the template. Let's look at a sample view:
387
388.. code-block:: python
389
390    def manage_articles(request):
391        ArticleFormSet = formset_factory(ArticleForm)
392        if request.method == 'POST':
393            formset = ArticleFormSet(request.POST, request.FILES)
394            if formset.is_valid():
395                # do something with the formset.cleaned_data
396                pass
397        else:
398            formset = ArticleFormSet()
399        return render_to_response('manage_articles.html', {'formset': formset})
400
401The ``manage_articles.html`` template might look like this:
402
403.. code-block:: html+django
404
405    <form method="post" action="">
406        {{ formset.management_form }}
407        <table>
408            {% for form in formset %}
409            {{ form }}
410            {% endfor %}
411        </table>
412    </form>
413
414However the above can be slightly shortcutted and let the formset itself deal
415with the management form:
416
417.. code-block:: html+django
418
419    <form method="post" action="">
420        <table>
421            {{ formset }}
422        </table>
423    </form>
424
425The above ends up calling the ``as_table`` method on the formset class.
426
427Using more than one formset in a view
428~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
429
430You are able to use more than one formset in a view if you like. Formsets
431borrow much of its behavior from forms. With that said you are able to use
432``prefix`` to prefix formset form field names with a given value to allow
433more than one formset to be sent to a view without name clashing. Lets take
434a look at how this might be accomplished:
435
436.. code-block:: python
437
438    def manage_articles(request):
439        ArticleFormSet = formset_factory(ArticleForm)
440        BookFormSet = formset_factory(BookForm)
441        if request.method == 'POST':
442            article_formset = ArticleFormSet(request.POST, request.FILES, prefix='articles')
443            book_formset = BookFormSet(request.POST, request.FILES, prefix='books')
444            if article_formset.is_valid() and book_formset.is_valid():
445                # do something with the cleaned_data on the formsets.
446                pass
447        else:
448            article_formset = ArticleFormSet(prefix='articles')
449            book_formset = BookFormSet(prefix='books')
450        return render_to_response('manage_articles.html', {
451            'article_formset': article_formset,
452            'book_formset': book_formset,
453        })
454
455You would then render the formsets as normal. It is important to point out
456that you need to pass ``prefix`` on both the POST and non-POST cases so that
457it is rendered and processed correctly.