~ubuntu-branches/ubuntu/quantal/python-django/quantal

« back to all changes in this revision

Viewing changes to docs/topics/forms/formsets.txt

  • Committer: Bazaar Package Importer
  • Author(s): Scott James Remnant, Eddy Mulyono
  • Date: 2008-09-16 12:18:47 UTC
  • mfrom: (1.1.5 upstream) (4.1.1 lenny)
  • Revision ID: james.westby@ubuntu.com-20080916121847-mg225rg5mnsdqzr0
Tags: 1.0-1ubuntu1
* Merge from Debian (LP: #264191), remaining changes:
  - Run test suite on build.

[Eddy Mulyono]
* Update patch to workaround network test case failures.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
.. _topics-forms-formsets:
 
2
.. _formsets:
 
3
 
 
4
Formsets
 
5
========
 
6
 
 
7
A formset is a layer of abstraction to working with multiple forms on the same
 
8
page. It can be best compared to a data grid. Let's say you have the following
 
9
form::
 
10
 
 
11
    >>> from django import forms
 
12
    >>> class ArticleForm(forms.Form):
 
13
    ...     title = forms.CharField()
 
14
    ...     pub_date = forms.DateField()
 
15
 
 
16
You might want to allow the user to create several articles at once. To create
 
17
a formset out of an ``ArticleForm`` you would do::
 
18
 
 
19
    >>> from django.forms.formsets import formset_factory
 
20
    >>> ArticleFormSet = formset_factory(ArticleForm)
 
21
 
 
22
You now have created a formset named ``ArticleFormSet``. The formset gives you
 
23
the ability to iterate over the forms in the formset and display them as you
 
24
would with a regular form::
 
25
 
 
26
    >>> formset = ArticleFormSet()
 
27
    >>> for form in formset.forms:
 
28
    ...     print form.as_table()
 
29
    <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>
 
30
    <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>
 
31
 
 
32
As you can see it only displayed one form. This is because by default the
 
33
``formset_factory`` defines one extra form. This can be controlled with the
 
34
``extra`` parameter::
 
35
 
 
36
    >>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
 
37
 
 
38
Using initial data with a formset
 
39
---------------------------------
 
40
 
 
41
Initial data is what drives the main usability of a formset. As shown above
 
42
you can define the number of extra forms. What this means is that you are
 
43
telling the formset how many additional forms to show in addition to the
 
44
number of forms it generates from the initial data. Lets take a look at an
 
45
example::
 
46
 
 
47
    >>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
 
48
    >>> formset = ArticleFormSet(initial=[
 
49
    ...     {'title': u'Django is now open source',
 
50
    ...      'pub_date': datetime.date.today()},
 
51
    ... ])
 
52
 
 
53
    >>> for form in formset.forms:
 
54
    ...     print form.as_table()
 
55
    <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>
 
56
    <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>
 
57
    <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>
 
58
    <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>
 
59
    <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>
 
60
    <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>
 
61
 
 
62
There are now a total of three forms showing above. One for the initial data
 
63
that was passed in and two extra forms. Also note that we are passing in a
 
64
list of dictionaries as the initial data.
 
65
 
 
66
Limiting the maximum number of forms
 
67
------------------------------------
 
68
 
 
69
The ``max_num`` parameter to ``formset_factory`` gives you the ability to
 
70
force the maximum number of forms the formset will display::
 
71
 
 
72
    >>> ArticleFormSet = formset_factory(ArticleForm, extra=2, max_num=1)
 
73
    >>> formset = ArticleFormset()
 
74
    >>> for form in formset.forms:
 
75
    ...     print form.as_table()
 
76
    <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>
 
77
    <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>
 
78
 
 
79
The default value of ``max_num`` is ``0`` which is the same as saying put no
 
80
limit on the number forms displayed.
 
81
 
 
82
Formset validation
 
83
------------------
 
84
 
 
85
Validation with a formset is about identical to a regular ``Form``. There is
 
86
an ``is_valid`` method on the formset to provide a convenient way to validate
 
87
each form in the formset::
 
88
 
 
89
    >>> ArticleFormSet = formset_factory(ArticleForm)
 
90
    >>> formset = ArticleFormSet({})
 
91
    >>> formset.is_valid()
 
92
    True
 
93
 
 
94
We passed in no data to the formset which is resulting in a valid form. The
 
95
formset is smart enough to ignore extra forms that were not changed. If we
 
96
attempt to provide an article, but fail to do so::
 
97
 
 
98
    >>> data = {
 
99
    ...     'form-TOTAL_FORMS': u'1',
 
100
    ...     'form-INITIAL_FORMS': u'1',
 
101
    ...     'form-0-title': u'Test',
 
102
    ...     'form-0-pub_date': u'',
 
103
    ... }
 
104
    >>> formset = ArticleFormSet(data)
 
105
    >>> formset.is_valid()
 
106
    False
 
107
    >>> formset.errors
 
108
    [{'pub_date': [u'This field is required.']}]
 
109
 
 
110
As we can see the formset properly performed validation and gave us the
 
111
expected errors.
 
112
 
 
113
Understanding the ManagementForm
 
114
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
115
 
 
116
You may have noticed the additional data that was required in the formset's
 
117
data above. This data is coming from the ``ManagementForm``. This form is
 
118
dealt with internally to the formset. If you don't use it, it will result in
 
119
an exception::
 
120
 
 
121
    >>> data = {
 
122
    ...     'form-0-title': u'Test',
 
123
    ...     'form-0-pub_date': u'',
 
124
    ... }
 
125
    >>> formset = ArticleFormSet(data)
 
126
    Traceback (most recent call last):
 
127
    ...
 
128
    django.forms.util.ValidationError: [u'ManagementForm data is missing or has been tampered with']
 
129
 
 
130
It is used to keep track of how many form instances are being displayed. If
 
131
you are adding new forms via JavaScript, you should increment the count fields
 
132
in this form as well.
 
133
 
 
134
Custom formset validation
 
135
~~~~~~~~~~~~~~~~~~~~~~~~~
 
136
 
 
137
A formset has a ``clean`` method similar to the one on a ``Form`` class. This
 
138
is where you define your own validation that deals at the formset level::
 
139
 
 
140
    >>> from django.forms.formsets import BaseFormSet
 
141
 
 
142
    >>> class BaseArticleFormSet(BaseFormSet):
 
143
    ...     def clean(self):
 
144
    ...         raise forms.ValidationError, u'An error occured.'
 
145
 
 
146
    >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
 
147
    >>> formset = ArticleFormSet({})
 
148
    >>> formset.is_valid()
 
149
    False
 
150
    >>> formset.non_form_errors()
 
151
    [u'An error occured.']
 
152
 
 
153
The formset ``clean`` method is called after all the ``Form.clean`` methods
 
154
have been called. The errors will be found using the ``non_form_errors()``
 
155
method on the formset.
 
156
 
 
157
Dealing with ordering and deletion of forms
 
158
-------------------------------------------
 
159
 
 
160
Common use cases with a formset is dealing with ordering and deletion of the
 
161
form instances. This has been dealt with for you. The ``formset_factory``
 
162
provides two optional parameters ``can_order`` and ``can_delete`` that will do
 
163
the extra work of adding the extra fields and providing simpler ways of
 
164
getting to that data.
 
165
 
 
166
``can_order``
 
167
~~~~~~~~~~~~~
 
168
 
 
169
Default: ``False``
 
170
 
 
171
Lets create a formset with the ability to order::
 
172
 
 
173
    >>> ArticleFormSet = formset_factory(ArticleForm, can_order=True)
 
174
    >>> formset = ArticleFormSet(initial=[
 
175
    ...     {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
 
176
    ...     {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
 
177
    ... ])
 
178
    >>> for form in formset.forms:
 
179
    ...     print form.as_table()
 
180
    <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>
 
181
    <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>
 
182
    <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>
 
183
    <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>
 
184
    <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>
 
185
    <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>
 
186
    <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>
 
187
    <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>
 
188
    <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>
 
189
 
 
190
This adds an additional field to each form. This new field is named ``ORDER``
 
191
and is an ``forms.IntegerField``. For the forms that came from the initial
 
192
data it automatically assigned them a numeric value. Lets look at what will
 
193
happen when the user changes these values::
 
194
 
 
195
    >>> data = {
 
196
    ...     'form-TOTAL_FORMS': u'3',
 
197
    ...     'form-INITIAL_FORMS': u'2',
 
198
    ...     'form-0-title': u'Article #1',
 
199
    ...     'form-0-pub_date': u'2008-05-10',
 
200
    ...     'form-0-ORDER': u'2',
 
201
    ...     'form-1-title': u'Article #2',
 
202
    ...     'form-1-pub_date': u'2008-05-11',
 
203
    ...     'form-1-ORDER': u'1',
 
204
    ...     'form-2-title': u'Article #3',
 
205
    ...     'form-2-pub_date': u'2008-05-01',
 
206
    ...     'form-2-ORDER': u'0',
 
207
    ... }
 
208
 
 
209
    >>> formset = ArticleFormSet(data, initial=[
 
210
    ...     {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
 
211
    ...     {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
 
212
    ... ])
 
213
    >>> formset.is_valid()
 
214
    True
 
215
    >>> for form in formset.ordered_forms:
 
216
    ...     print form.cleaned_data
 
217
    {'pub_date': datetime.date(2008, 5, 1), 'ORDER': 0, 'title': u'Article #3'}
 
218
    {'pub_date': datetime.date(2008, 5, 11), 'ORDER': 1, 'title': u'Article #2'}
 
219
    {'pub_date': datetime.date(2008, 5, 10), 'ORDER': 2, 'title': u'Article #1'}
 
220
 
 
221
``can_delete``
 
222
~~~~~~~~~~~~~~
 
223
 
 
224
Default: ``False``
 
225
 
 
226
Lets create a formset with the ability to delete::
 
227
 
 
228
    >>> ArticleFormSet = formset_factory(ArticleForm, can_delete=True)
 
229
    >>> formset = ArticleFormSet(initial=[
 
230
    ...     {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
 
231
    ...     {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
 
232
    ... ])
 
233
    >>> for form in formset.forms:
 
234
    ....    print form.as_table()
 
235
    <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" />
 
236
    <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>
 
237
    <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>
 
238
    <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>
 
239
    <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>
 
240
    <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>
 
241
    <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>
 
242
    <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>
 
243
    <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>
 
244
    <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>
 
245
 
 
246
Similar to ``can_order`` this adds a new field to each form named ``DELETE``
 
247
and is a ``forms.BooleanField``. When data comes through marking any of the
 
248
delete fields you can access them with ``deleted_forms``::
 
249
 
 
250
    >>> data = {
 
251
    ...     'form-TOTAL_FORMS': u'3',
 
252
    ...     'form-INITIAL_FORMS': u'2',
 
253
    ...     'form-0-title': u'Article #1',
 
254
    ...     'form-0-pub_date': u'2008-05-10',
 
255
    ...     'form-0-DELETE': u'on',
 
256
    ...     'form-1-title': u'Article #2',
 
257
    ...     'form-1-pub_date': u'2008-05-11',
 
258
    ...     'form-1-DELETE': u'',
 
259
    ...     'form-2-title': u'',
 
260
    ...     'form-2-pub_date': u'',
 
261
    ...     'form-2-DELETE': u'',
 
262
    ... }
 
263
 
 
264
    >>> formset = ArticleFormSet(data, initial=[
 
265
    ...     {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
 
266
    ...     {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
 
267
    ... ])
 
268
    >>> [form.cleaned_data for form in formset.deleted_forms]
 
269
    [{'DELETE': True, 'pub_date': datetime.date(2008, 5, 10), 'title': u'Article #1'}]
 
270
 
 
271
Adding additional fields to a formset
 
272
-------------------------------------
 
273
 
 
274
If you need to add additional fields to the formset this can be easily
 
275
accomplished. The formset base class provides an ``add_fields`` method. You
 
276
can simply override this method to add your own fields or even redefine the
 
277
default fields/attributes of the order and deletion fields::
 
278
 
 
279
    >>> class BaseArticleFormSet(BaseFormSet):
 
280
    ...     def add_fields(self, form, index):
 
281
    ...         super(BaseArticleFormSet, self).add_fields(form, index)
 
282
    ...         form.fields["my_field"] = forms.CharField()
 
283
 
 
284
    >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
 
285
    >>> formset = ArticleFormSet()
 
286
    >>> for form in formset.forms:
 
287
    ...     print form.as_table()
 
288
    <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>
 
289
    <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>
 
290
    <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>
 
291
 
 
292
Using a formset in views and templates
 
293
--------------------------------------
 
294
 
 
295
Using a formset inside a view is as easy as using a regular ``Form`` class.
 
296
The only thing you will want to be aware of is making sure to use the
 
297
management form inside the template. Lets look at a sample view::
 
298
 
 
299
    def manage_articles(request):
 
300
        ArticleFormSet = formset_factory(ArticleForm)
 
301
        if request.method == 'POST':
 
302
            formset = ArticleFormSet(request.POST, request.FILES)
 
303
            if formset.is_valid():
 
304
                # do something with the formset.cleaned_data
 
305
        else:
 
306
            formset = ArticleFormSet()
 
307
        return render_to_response('manage_articles.html', {'formset': formset})
 
308
 
 
309
The ``manage_articles.html`` template might look like this::
 
310
 
 
311
    <form method="POST" action="">
 
312
        {{ formset.management_form }}
 
313
        <table>
 
314
            {% for form in formset.forms %}
 
315
            {{ form }}
 
316
            {% endfor %}
 
317
        </table>
 
318
    </form>
 
319
 
 
320
However the above can be slightly shortcutted and let the formset itself deal
 
321
with the management form::
 
322
 
 
323
    <form method="POST" action="">
 
324
        <table>
 
325
            {{ formset }}
 
326
        </table>
 
327
    </form>
 
328
 
 
329
The above ends up calling the ``as_table`` method on the formset class.
 
330
 
 
331
Using more than one formset in a view
 
332
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
333
 
 
334
You are able to use more than one formset in a view if you like. Formsets
 
335
borrow much of its behavior from forms. With that said you are able to use
 
336
``prefix`` to prefix formset form field names with a given value to allow
 
337
more than one formset to be sent to a view without name clashing. Lets take
 
338
a look at how this might be accomplished::
 
339
 
 
340
    def manage_articles(request):
 
341
        ArticleFormSet = formset_factory(ArticleForm)
 
342
        BookFormSet = formset_factory(BookForm)
 
343
        if request.method == 'POST':
 
344
            article_formset = ArticleFormSet(request.POST, request.FILES, prefix='articles')
 
345
            book_formset = BookFormSet(request.POST, request.FILES, prefix='books')
 
346
            if article_formset.is_valid() and book_formset.is_valid():
 
347
                # do something with the cleaned_data on the formsets.
 
348
        else:
 
349
            article_formset = ArticleFormSet(prefix='articles')
 
350
            book_formset = BookFormSet(prefix='books')
 
351
        return render_to_response('manage_articles.html', {
 
352
            'article_formset': article_formset,
 
353
            'book_formset': book_formset,
 
354
        })
 
355
 
 
356
You would then render the formsets as normal. It is important to point out
 
357
that you need to pass ``prefix`` on both the POST and non-POST cases so that
 
358
it is rendered and processed correctly.