1
# -*- coding: utf-8 -*-
3
# Basic FormSet creation and usage ############################################
5
FormSet allows us to use multiple instance of the same form on 1 page. For now,
6
the best way to create a FormSet is by using the formset_factory function.
8
>>> from django.forms import Form, CharField, IntegerField, ValidationError
9
>>> from django.forms.formsets import formset_factory, BaseFormSet
11
>>> class Choice(Form):
12
... choice = CharField()
13
... votes = IntegerField()
15
>>> ChoiceFormSet = formset_factory(Choice)
17
A FormSet constructor takes the same arguments as Form. Let's create a FormSet
18
for adding data. By default, it displays 1 blank form. It can display more,
19
but we'll look at how to do so later.
21
>>> formset = ChoiceFormSet(auto_id=False, prefix='choices')
23
<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" />
24
<tr><th>Choice:</th><td><input type="text" name="choices-0-choice" /></td></tr>
25
<tr><th>Votes:</th><td><input type="text" name="choices-0-votes" /></td></tr>
28
On thing to note is that there needs to be a special value in the data. This
29
value tells the FormSet how many forms were displayed so it can tell how
30
many forms it needs to clean and validate. You could use javascript to create
31
new forms on the client side, but they won't get validated unless you increment
32
the TOTAL_FORMS field appropriately.
35
... 'choices-TOTAL_FORMS': '1', # the number of forms rendered
36
... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
37
... 'choices-0-choice': 'Calexico',
38
... 'choices-0-votes': '100',
41
We treat FormSet pretty much like we would treat a normal Form. FormSet has an
42
is_valid method, and a cleaned_data or errors attribute depending on whether all
43
the forms passed validation. However, unlike a Form instance, cleaned_data and
44
errors will be a list of dicts rather than just a single dict.
46
>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
47
>>> formset.is_valid()
49
>>> [form.cleaned_data for form in formset.forms]
50
[{'votes': 100, 'choice': u'Calexico'}]
52
If a FormSet was not passed any data, its is_valid method should return False.
53
>>> formset = ChoiceFormSet()
54
>>> formset.is_valid()
57
FormSet instances can also have an error attribute if validation failed for
61
... 'choices-TOTAL_FORMS': '1', # the number of forms rendered
62
... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
63
... 'choices-0-choice': 'Calexico',
64
... 'choices-0-votes': '',
67
>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
68
>>> formset.is_valid()
71
[{'votes': [u'This field is required.']}]
74
We can also prefill a FormSet with existing data by providing an ``initial``
75
argument to the constructor. ``initial`` should be a list of dicts. By default,
76
an extra blank form is included.
78
>>> initial = [{'choice': u'Calexico', 'votes': 100}]
79
>>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
80
>>> for form in formset.forms:
81
... print form.as_ul()
82
<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
83
<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
84
<li>Choice: <input type="text" name="choices-1-choice" /></li>
85
<li>Votes: <input type="text" name="choices-1-votes" /></li>
88
Let's simulate what would happen if we submitted this form.
91
... 'choices-TOTAL_FORMS': '2', # the number of forms rendered
92
... 'choices-INITIAL_FORMS': '1', # the number of forms with initial data
93
... 'choices-0-choice': 'Calexico',
94
... 'choices-0-votes': '100',
95
... 'choices-1-choice': '',
96
... 'choices-1-votes': '',
99
>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
100
>>> formset.is_valid()
102
>>> [form.cleaned_data for form in formset.forms]
103
[{'votes': 100, 'choice': u'Calexico'}, {}]
105
But the second form was blank! Shouldn't we get some errors? No. If we display
106
a form as blank, it's ok for it to be submitted as blank. If we fill out even
107
one of the fields of a blank form though, it will be validated. We may want to
108
required that at least x number of forms are completed, but we'll show how to
112
... 'choices-TOTAL_FORMS': '2', # the number of forms rendered
113
... 'choices-INITIAL_FORMS': '1', # the number of forms with initial data
114
... 'choices-0-choice': 'Calexico',
115
... 'choices-0-votes': '100',
116
... 'choices-1-choice': 'The Decemberists',
117
... 'choices-1-votes': '', # missing value
120
>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
121
>>> formset.is_valid()
124
[{}, {'votes': [u'This field is required.']}]
126
If we delete data that was pre-filled, we should get an error. Simply removing
127
data from form fields isn't the proper way to delete it. We'll see how to
128
handle that case later.
131
... 'choices-TOTAL_FORMS': '2', # the number of forms rendered
132
... 'choices-INITIAL_FORMS': '1', # the number of forms with initial data
133
... 'choices-0-choice': '', # deleted value
134
... 'choices-0-votes': '', # deleted value
135
... 'choices-1-choice': '',
136
... 'choices-1-votes': '',
139
>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
140
>>> formset.is_valid()
143
[{'votes': [u'This field is required.'], 'choice': [u'This field is required.']}, {}]
146
# Displaying more than 1 blank form ###########################################
148
We can also display more than 1 empty form at a time. To do so, pass a
149
extra argument to formset_factory.
151
>>> ChoiceFormSet = formset_factory(Choice, extra=3)
153
>>> formset = ChoiceFormSet(auto_id=False, prefix='choices')
154
>>> for form in formset.forms:
155
... print form.as_ul()
156
<li>Choice: <input type="text" name="choices-0-choice" /></li>
157
<li>Votes: <input type="text" name="choices-0-votes" /></li>
158
<li>Choice: <input type="text" name="choices-1-choice" /></li>
159
<li>Votes: <input type="text" name="choices-1-votes" /></li>
160
<li>Choice: <input type="text" name="choices-2-choice" /></li>
161
<li>Votes: <input type="text" name="choices-2-votes" /></li>
163
Since we displayed every form as blank, we will also accept them back as blank.
164
This may seem a little strange, but later we will show how to require a minimum
165
number of forms to be completed.
168
... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
169
... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
170
... 'choices-0-choice': '',
171
... 'choices-0-votes': '',
172
... 'choices-1-choice': '',
173
... 'choices-1-votes': '',
174
... 'choices-2-choice': '',
175
... 'choices-2-votes': '',
178
>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
179
>>> formset.is_valid()
181
>>> [form.cleaned_data for form in formset.forms]
185
We can just fill out one of the forms.
188
... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
189
... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
190
... 'choices-0-choice': 'Calexico',
191
... 'choices-0-votes': '100',
192
... 'choices-1-choice': '',
193
... 'choices-1-votes': '',
194
... 'choices-2-choice': '',
195
... 'choices-2-votes': '',
198
>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
199
>>> formset.is_valid()
201
>>> [form.cleaned_data for form in formset.forms]
202
[{'votes': 100, 'choice': u'Calexico'}, {}, {}]
205
And once again, if we try to partially complete a form, validation will fail.
208
... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
209
... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
210
... 'choices-0-choice': 'Calexico',
211
... 'choices-0-votes': '100',
212
... 'choices-1-choice': 'The Decemberists',
213
... 'choices-1-votes': '', # missing value
214
... 'choices-2-choice': '',
215
... 'choices-2-votes': '',
218
>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
219
>>> formset.is_valid()
222
[{}, {'votes': [u'This field is required.']}, {}]
225
The extra argument also works when the formset is pre-filled with initial
228
>>> initial = [{'choice': u'Calexico', 'votes': 100}]
229
>>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
230
>>> for form in formset.forms:
231
... print form.as_ul()
232
<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
233
<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
234
<li>Choice: <input type="text" name="choices-1-choice" /></li>
235
<li>Votes: <input type="text" name="choices-1-votes" /></li>
236
<li>Choice: <input type="text" name="choices-2-choice" /></li>
237
<li>Votes: <input type="text" name="choices-2-votes" /></li>
238
<li>Choice: <input type="text" name="choices-3-choice" /></li>
239
<li>Votes: <input type="text" name="choices-3-votes" /></li>
242
# FormSets with deletion ######################################################
244
We can easily add deletion ability to a FormSet with an agrument to
245
formset_factory. This will add a boolean field to each form instance. When
246
that boolean field is True, the form will be in formset.deleted_forms
248
>>> ChoiceFormSet = formset_factory(Choice, can_delete=True)
250
>>> initial = [{'choice': u'Calexico', 'votes': 100}, {'choice': u'Fergie', 'votes': 900}]
251
>>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
252
>>> for form in formset.forms:
253
... print form.as_ul()
254
<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
255
<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
256
<li>Delete: <input type="checkbox" name="choices-0-DELETE" /></li>
257
<li>Choice: <input type="text" name="choices-1-choice" value="Fergie" /></li>
258
<li>Votes: <input type="text" name="choices-1-votes" value="900" /></li>
259
<li>Delete: <input type="checkbox" name="choices-1-DELETE" /></li>
260
<li>Choice: <input type="text" name="choices-2-choice" /></li>
261
<li>Votes: <input type="text" name="choices-2-votes" /></li>
262
<li>Delete: <input type="checkbox" name="choices-2-DELETE" /></li>
264
To delete something, we just need to set that form's special delete field to
265
'on'. Let's go ahead and delete Fergie.
268
... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
269
... 'choices-INITIAL_FORMS': '2', # the number of forms with initial data
270
... 'choices-0-choice': 'Calexico',
271
... 'choices-0-votes': '100',
272
... 'choices-0-DELETE': '',
273
... 'choices-1-choice': 'Fergie',
274
... 'choices-1-votes': '900',
275
... 'choices-1-DELETE': 'on',
276
... 'choices-2-choice': '',
277
... 'choices-2-votes': '',
278
... 'choices-2-DELETE': '',
281
>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
282
>>> formset.is_valid()
284
>>> [form.cleaned_data for form in formset.forms]
285
[{'votes': 100, 'DELETE': False, 'choice': u'Calexico'}, {'votes': 900, 'DELETE': True, 'choice': u'Fergie'}, {}]
286
>>> [form.cleaned_data for form in formset.deleted_forms]
287
[{'votes': 900, 'DELETE': True, 'choice': u'Fergie'}]
290
# FormSets with ordering ######################################################
292
We can also add ordering ability to a FormSet with an agrument to
293
formset_factory. This will add a integer field to each form instance. When
294
form validation succeeds, [form.cleaned_data for form in formset.forms] will have the data in the correct
295
order specified by the ordering fields. If a number is duplicated in the set
296
of ordering fields, for instance form 0 and form 3 are both marked as 1, then
297
the form index used as a secondary ordering criteria. In order to put
298
something at the front of the list, you'd need to set it's order to 0.
300
>>> ChoiceFormSet = formset_factory(Choice, can_order=True)
302
>>> initial = [{'choice': u'Calexico', 'votes': 100}, {'choice': u'Fergie', 'votes': 900}]
303
>>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
304
>>> for form in formset.forms:
305
... print form.as_ul()
306
<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
307
<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
308
<li>Order: <input type="text" name="choices-0-ORDER" value="1" /></li>
309
<li>Choice: <input type="text" name="choices-1-choice" value="Fergie" /></li>
310
<li>Votes: <input type="text" name="choices-1-votes" value="900" /></li>
311
<li>Order: <input type="text" name="choices-1-ORDER" value="2" /></li>
312
<li>Choice: <input type="text" name="choices-2-choice" /></li>
313
<li>Votes: <input type="text" name="choices-2-votes" /></li>
314
<li>Order: <input type="text" name="choices-2-ORDER" /></li>
317
... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
318
... 'choices-INITIAL_FORMS': '2', # the number of forms with initial data
319
... 'choices-0-choice': 'Calexico',
320
... 'choices-0-votes': '100',
321
... 'choices-0-ORDER': '1',
322
... 'choices-1-choice': 'Fergie',
323
... 'choices-1-votes': '900',
324
... 'choices-1-ORDER': '2',
325
... 'choices-2-choice': 'The Decemberists',
326
... 'choices-2-votes': '500',
327
... 'choices-2-ORDER': '0',
330
>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
331
>>> formset.is_valid()
333
>>> for form in formset.ordered_forms:
334
... print form.cleaned_data
335
{'votes': 500, 'ORDER': 0, 'choice': u'The Decemberists'}
336
{'votes': 100, 'ORDER': 1, 'choice': u'Calexico'}
337
{'votes': 900, 'ORDER': 2, 'choice': u'Fergie'}
339
Ordering fields are allowed to be left blank, and if they *are* left blank,
340
they will be sorted below everything else.
343
... 'choices-TOTAL_FORMS': '4', # the number of forms rendered
344
... 'choices-INITIAL_FORMS': '3', # the number of forms with initial data
345
... 'choices-0-choice': 'Calexico',
346
... 'choices-0-votes': '100',
347
... 'choices-0-ORDER': '1',
348
... 'choices-1-choice': 'Fergie',
349
... 'choices-1-votes': '900',
350
... 'choices-1-ORDER': '2',
351
... 'choices-2-choice': 'The Decemberists',
352
... 'choices-2-votes': '500',
353
... 'choices-2-ORDER': '',
354
... 'choices-3-choice': 'Basia Bulat',
355
... 'choices-3-votes': '50',
356
... 'choices-3-ORDER': '',
359
>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
360
>>> formset.is_valid()
362
>>> for form in formset.ordered_forms:
363
... print form.cleaned_data
364
{'votes': 100, 'ORDER': 1, 'choice': u'Calexico'}
365
{'votes': 900, 'ORDER': 2, 'choice': u'Fergie'}
366
{'votes': 500, 'ORDER': None, 'choice': u'The Decemberists'}
367
{'votes': 50, 'ORDER': None, 'choice': u'Basia Bulat'}
370
# FormSets with ordering + deletion ###########################################
372
Let's try throwing ordering and deletion into the same form.
374
>>> ChoiceFormSet = formset_factory(Choice, can_order=True, can_delete=True)
377
... {'choice': u'Calexico', 'votes': 100},
378
... {'choice': u'Fergie', 'votes': 900},
379
... {'choice': u'The Decemberists', 'votes': 500},
381
>>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
382
>>> for form in formset.forms:
383
... print form.as_ul()
384
<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
385
<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
386
<li>Order: <input type="text" name="choices-0-ORDER" value="1" /></li>
387
<li>Delete: <input type="checkbox" name="choices-0-DELETE" /></li>
388
<li>Choice: <input type="text" name="choices-1-choice" value="Fergie" /></li>
389
<li>Votes: <input type="text" name="choices-1-votes" value="900" /></li>
390
<li>Order: <input type="text" name="choices-1-ORDER" value="2" /></li>
391
<li>Delete: <input type="checkbox" name="choices-1-DELETE" /></li>
392
<li>Choice: <input type="text" name="choices-2-choice" value="The Decemberists" /></li>
393
<li>Votes: <input type="text" name="choices-2-votes" value="500" /></li>
394
<li>Order: <input type="text" name="choices-2-ORDER" value="3" /></li>
395
<li>Delete: <input type="checkbox" name="choices-2-DELETE" /></li>
396
<li>Choice: <input type="text" name="choices-3-choice" /></li>
397
<li>Votes: <input type="text" name="choices-3-votes" /></li>
398
<li>Order: <input type="text" name="choices-3-ORDER" /></li>
399
<li>Delete: <input type="checkbox" name="choices-3-DELETE" /></li>
401
Let's delete Fergie, and put The Decemberists ahead of Calexico.
404
... 'choices-TOTAL_FORMS': '4', # the number of forms rendered
405
... 'choices-INITIAL_FORMS': '3', # the number of forms with initial data
406
... 'choices-0-choice': 'Calexico',
407
... 'choices-0-votes': '100',
408
... 'choices-0-ORDER': '1',
409
... 'choices-0-DELETE': '',
410
... 'choices-1-choice': 'Fergie',
411
... 'choices-1-votes': '900',
412
... 'choices-1-ORDER': '2',
413
... 'choices-1-DELETE': 'on',
414
... 'choices-2-choice': 'The Decemberists',
415
... 'choices-2-votes': '500',
416
... 'choices-2-ORDER': '0',
417
... 'choices-2-DELETE': '',
418
... 'choices-3-choice': '',
419
... 'choices-3-votes': '',
420
... 'choices-3-ORDER': '',
421
... 'choices-3-DELETE': '',
424
>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
425
>>> formset.is_valid()
427
>>> for form in formset.ordered_forms:
428
... print form.cleaned_data
429
{'votes': 500, 'DELETE': False, 'ORDER': 0, 'choice': u'The Decemberists'}
430
{'votes': 100, 'DELETE': False, 'ORDER': 1, 'choice': u'Calexico'}
431
>>> [form.cleaned_data for form in formset.deleted_forms]
432
[{'votes': 900, 'DELETE': True, 'ORDER': 2, 'choice': u'Fergie'}]
435
# FormSet clean hook ##########################################################
437
FormSets have a hook for doing extra validation that shouldn't be tied to any
438
particular form. It follows the same pattern as the clean hook on Forms.
440
Let's define a FormSet that takes a list of favorite drinks, but raises am
441
error if there are any duplicates.
443
>>> class FavoriteDrinkForm(Form):
444
... name = CharField()
447
>>> class BaseFavoriteDrinksFormSet(BaseFormSet):
450
... for drink in self.cleaned_data:
451
... if drink['name'] in seen_drinks:
452
... raise ValidationError('You may only specify a drink once.')
453
... seen_drinks.append(drink['name'])
456
>>> FavoriteDrinksFormSet = formset_factory(FavoriteDrinkForm,
457
... formset=BaseFavoriteDrinksFormSet, extra=3)
459
We start out with a some duplicate data.
462
... 'drinks-TOTAL_FORMS': '2', # the number of forms rendered
463
... 'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
464
... 'drinks-0-name': 'Gin and Tonic',
465
... 'drinks-1-name': 'Gin and Tonic',
468
>>> formset = FavoriteDrinksFormSet(data, prefix='drinks')
469
>>> formset.is_valid()
472
Any errors raised by formset.clean() are available via the
473
formset.non_form_errors() method.
475
>>> for error in formset.non_form_errors():
477
You may only specify a drink once.
480
Make sure we didn't break the valid case.
483
... 'drinks-TOTAL_FORMS': '2', # the number of forms rendered
484
... 'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
485
... 'drinks-0-name': 'Gin and Tonic',
486
... 'drinks-1-name': 'Bloody Mary',
489
>>> formset = FavoriteDrinksFormSet(data, prefix='drinks')
490
>>> formset.is_valid()
492
>>> for error in formset.non_form_errors():
495
# Limiting the maximum number of forms ########################################
497
# Base case for max_num.
499
>>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=5, max_num=2)
500
>>> formset = LimitedFavoriteDrinkFormSet()
501
>>> for form in formset.forms:
503
<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" id="id_form-0-name" /></td></tr>
504
<tr><th><label for="id_form-1-name">Name:</label></th><td><input type="text" name="form-1-name" id="id_form-1-name" /></td></tr>
506
# Ensure the that max_num has no affect when extra is less than max_forms.
508
>>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=2)
509
>>> formset = LimitedFavoriteDrinkFormSet()
510
>>> for form in formset.forms:
512
<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" id="id_form-0-name" /></td></tr>
514
# max_num with initial data
516
# More initial forms than max_num will result in only the first max_num of
517
# them to be displayed with no extra forms.
520
... {'name': 'Gin Tonic'},
521
... {'name': 'Bloody Mary'},
522
... {'name': 'Jack and Coke'},
524
>>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=2)
525
>>> formset = LimitedFavoriteDrinkFormSet(initial=initial)
526
>>> for form in formset.forms:
528
<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" value="Gin Tonic" id="id_form-0-name" /></td></tr>
529
<tr><th><label for="id_form-1-name">Name:</label></th><td><input type="text" name="form-1-name" value="Bloody Mary" id="id_form-1-name" /></td></tr>
531
# One form from initial and extra=3 with max_num=2 should result in the one
532
# initial form and one extra.
535
... {'name': 'Gin Tonic'},
537
>>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=3, max_num=2)
538
>>> formset = LimitedFavoriteDrinkFormSet(initial=initial)
539
>>> for form in formset.forms:
541
<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" value="Gin Tonic" id="id_form-0-name" /></td></tr>
542
<tr><th><label for="id_form-1-name">Name:</label></th><td><input type="text" name="form-1-name" id="id_form-1-name" /></td></tr>
545
# Regression test for #6926 ##################################################
547
Make sure the management form has the correct prefix.
549
>>> formset = FavoriteDrinksFormSet()
550
>>> formset.management_form.prefix
553
>>> formset = FavoriteDrinksFormSet(data={})
554
>>> formset.management_form.prefix
557
>>> formset = FavoriteDrinksFormSet(initial={})
558
>>> formset.management_form.prefix