6
6
from django.forms import (CharField, DateField, FileField, Form, IntegerField,
7
7
SplitDateTimeField, ValidationError, formsets)
8
8
from django.forms.formsets import BaseFormSet, formset_factory
9
from django.forms.util import ErrorList
9
from django.forms.utils import ErrorList
10
10
from django.test import TestCase
57
57
class FormsFormsetTestCase(TestCase):
59
59
def make_choiceformset(self, formset_data=None, formset_class=ChoiceFormSet,
60
total_forms=None, initial_forms=0, max_num_forms=0, **kwargs):
60
total_forms=None, initial_forms=0, max_num_forms=0, min_num_forms=0, **kwargs):
62
62
Make a ChoiceFormset from the given formset_data.
63
63
The data should be given as a list of (choice, votes) tuples.
79
79
prefixed('TOTAL_FORMS'): str(total_forms),
80
80
prefixed('INITIAL_FORMS'): str(initial_forms),
81
81
prefixed('MAX_NUM_FORMS'): str(max_num_forms),
82
prefixed('MIN_NUM_FORMS'): str(min_num_forms),
83
84
for i, (choice, votes) in enumerate(formset_data):
84
85
data[prefixed(str(i), 'choice')] = choice
91
92
# for adding data. By default, it displays 1 blank form. It can display more,
92
93
# but we'll look at how to do so later.
93
94
formset = self.make_choiceformset()
95
self.assertHTMLEqual(str(formset), """<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="1000" />
95
self.assertHTMLEqual(str(formset), """<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MIN_NUM_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="1000" />
96
96
<tr><th>Choice:</th><td><input type="text" name="choices-0-choice" /></td></tr>
97
97
<tr><th>Votes:</th><td><input type="number" name="choices-0-votes" /></td></tr>""")
198
198
# number of forms to be completed.
201
'choices-TOTAL_FORMS': '3', # the number of forms rendered
202
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
203
'choices-MAX_NUM_FORMS': '0', # max number of forms
201
'choices-TOTAL_FORMS': '3', # the number of forms rendered
202
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
203
'choices-MIN_NUM_FORMS': '0', # min number of forms
204
'choices-MAX_NUM_FORMS': '0', # max number of forms
204
205
'choices-0-choice': '',
205
206
'choices-0-votes': '',
206
207
'choices-1-choice': '',
213
214
self.assertTrue(formset.is_valid())
214
215
self.assertEqual([form.cleaned_data for form in formset.forms], [{}, {}, {}])
217
def test_min_num_displaying_more_than_one_blank_form(self):
218
# We can also display more than 1 empty form passing min_num argument
219
# to formset_factory. It will (essentially) increment the extra argument
220
ChoiceFormSet = formset_factory(Choice, extra=1, min_num=1)
222
formset = ChoiceFormSet(auto_id=False, prefix='choices')
225
for form in formset.forms:
226
form_output.append(form.as_ul())
228
# Min_num forms are required; extra forms can be empty.
229
self.assertFalse(formset.forms[0].empty_permitted)
230
self.assertTrue(formset.forms[1].empty_permitted)
232
self.assertHTMLEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" /></li>
233
<li>Votes: <input type="number" name="choices-0-votes" /></li>
234
<li>Choice: <input type="text" name="choices-1-choice" /></li>
235
<li>Votes: <input type="number" name="choices-1-votes" /></li>""")
237
def test_min_num_displaying_more_than_one_blank_form_with_zero_extra(self):
238
# We can also display more than 1 empty form passing min_num argument
239
ChoiceFormSet = formset_factory(Choice, extra=0, min_num=3)
241
formset = ChoiceFormSet(auto_id=False, prefix='choices')
244
for form in formset.forms:
245
form_output.append(form.as_ul())
247
self.assertHTMLEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" /></li>
248
<li>Votes: <input type="number" name="choices-0-votes" /></li>
249
<li>Choice: <input type="text" name="choices-1-choice" /></li>
250
<li>Votes: <input type="number" name="choices-1-votes" /></li>
251
<li>Choice: <input type="text" name="choices-2-choice" /></li>
252
<li>Votes: <input type="number" name="choices-2-votes" /></li>""")
216
254
def test_single_form_completed(self):
217
255
# We can just fill out one of the forms.
220
'choices-TOTAL_FORMS': '3', # the number of forms rendered
221
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
222
'choices-MAX_NUM_FORMS': '0', # max number of forms
258
'choices-TOTAL_FORMS': '3', # the number of forms rendered
259
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
260
'choices-MIN_NUM_FORMS': '0', # min number of forms
261
'choices-MAX_NUM_FORMS': '0', # max number of forms
223
262
'choices-0-choice': 'Calexico',
224
263
'choices-0-votes': '100',
225
264
'choices-1-choice': '',
240
279
# value in the returned data is not checked)
243
'choices-TOTAL_FORMS': '2', # the number of forms rendered
244
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
245
'choices-MAX_NUM_FORMS': '2', # max number of forms - should be ignored
282
'choices-TOTAL_FORMS': '2', # the number of forms rendered
283
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
284
'choices-MIN_NUM_FORMS': '0', # min number of forms
285
'choices-MAX_NUM_FORMS': '2', # max number of forms - should be ignored
246
286
'choices-0-choice': 'Zero',
247
287
'choices-0-votes': '0',
248
288
'choices-1-choice': 'One',
254
294
self.assertFalse(formset.is_valid())
255
295
self.assertEqual(formset.non_form_errors(), ['Please submit 1 or fewer forms.'])
297
def test_formset_validate_min_flag(self):
298
# If validate_min is set and min_num is more than TOTAL_FORMS in the
299
# data, then throw an exception. MIN_NUM_FORMS in the data is
300
# irrelevant here (it's output as a hint for the client but its
301
# value in the returned data is not checked)
304
'choices-TOTAL_FORMS': '2', # the number of forms rendered
305
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
306
'choices-MIN_NUM_FORMS': '0', # min number of forms
307
'choices-MAX_NUM_FORMS': '0', # max number of forms - should be ignored
308
'choices-0-choice': 'Zero',
309
'choices-0-votes': '0',
310
'choices-1-choice': 'One',
311
'choices-1-votes': '1',
314
ChoiceFormSet = formset_factory(Choice, extra=1, min_num=3, validate_min=True)
315
formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
316
self.assertFalse(formset.is_valid())
317
self.assertEqual(formset.non_form_errors(), ['Please submit 3 or more forms.'])
257
319
def test_second_form_partially_filled_2(self):
258
320
# And once again, if we try to partially complete a form, validation will fail.
261
'choices-TOTAL_FORMS': '3', # the number of forms rendered
262
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
263
'choices-MAX_NUM_FORMS': '0', # max number of forms
323
'choices-TOTAL_FORMS': '3', # the number of forms rendered
324
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
325
'choices-MIN_NUM_FORMS': '0', # min number of forms
326
'choices-MAX_NUM_FORMS': '0', # max number of forms
264
327
'choices-0-choice': 'Calexico',
265
328
'choices-0-votes': '100',
266
329
'choices-1-choice': 'The Decemberists',
267
'choices-1-votes': '', # missing value
330
'choices-1-votes': '', # missing value
268
331
'choices-2-choice': '',
269
332
'choices-2-votes': '',
277
340
def test_more_initial_data(self):
278
341
# The extra argument also works when the formset is pre-filled with initial
282
'choices-TOTAL_FORMS': '3', # the number of forms rendered
283
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
284
'choices-MAX_NUM_FORMS': '0', # max number of forms
285
'choices-0-choice': 'Calexico',
286
'choices-0-votes': '100',
287
'choices-1-choice': '',
288
'choices-1-votes': '', # missing value
289
'choices-2-choice': '',
290
'choices-2-votes': '',
293
343
initial = [{'choice': 'Calexico', 'votes': 100}]
294
344
ChoiceFormSet = formset_factory(Choice, extra=3)
295
345
formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
342
392
# 'on'. Let's go ahead and delete Fergie.
345
'choices-TOTAL_FORMS': '3', # the number of forms rendered
346
'choices-INITIAL_FORMS': '2', # the number of forms with initial data
347
'choices-MAX_NUM_FORMS': '0', # max number of forms
395
'choices-TOTAL_FORMS': '3', # the number of forms rendered
396
'choices-INITIAL_FORMS': '2', # the number of forms with initial data
397
'choices-MIN_NUM_FORMS': '0', # min number of forms
398
'choices-MAX_NUM_FORMS': '0', # max number of forms
348
399
'choices-0-choice': 'Calexico',
349
400
'choices-0-votes': '100',
350
401
'choices-0-DELETE': '',
366
417
# it's going to be deleted.
368
419
class CheckForm(Form):
369
field = IntegerField(min_value=100)
420
field = IntegerField(min_value=100)
372
'check-TOTAL_FORMS': '3', # the number of forms rendered
373
'check-INITIAL_FORMS': '2', # the number of forms with initial data
374
'check-MAX_NUM_FORMS': '0', # max number of forms
423
'check-TOTAL_FORMS': '3', # the number of forms rendered
424
'check-INITIAL_FORMS': '2', # the number of forms with initial data
425
'choices-MIN_NUM_FORMS': '0', # min number of forms
426
'check-MAX_NUM_FORMS': '0', # max number of forms
375
427
'check-0-field': '200',
376
428
'check-0-DELETE': '',
377
429
'check-1-field': '50',
402
{'form-0-name': '', 'form-0-DELETE': 'on', # no name!
454
{'form-0-name': '', 'form-0-DELETE': 'on', # no name!
403
455
'form-TOTAL_FORMS': 1, 'form-INITIAL_FORMS': 1,
404
'form-MAX_NUM_FORMS': 1})
456
'form-MIN_NUM_FORMS': 0, 'form-MAX_NUM_FORMS': 1})
406
458
self.assertTrue(p.is_valid())
407
459
self.assertEqual(len(p.deleted_forms), 1)
409
461
def test_formsets_with_ordering(self):
410
462
# FormSets with ordering ######################################################
411
463
# We can also add ordering ability to a FormSet with an argument to
412
# formset_factory. This will add a integer field to each form instance. When
464
# formset_factory. This will add an integer field to each form instance. When
413
465
# form validation succeeds, [form.cleaned_data for form in formset.forms] will have the data in the correct
414
466
# order specified by the ordering fields. If a number is duplicated in the set
415
467
# of ordering fields, for instance form 0 and form 3 are both marked as 1, then
436
488
<li>Order: <input type="number" name="choices-2-ORDER" /></li>""")
439
'choices-TOTAL_FORMS': '3', # the number of forms rendered
440
'choices-INITIAL_FORMS': '2', # the number of forms with initial data
441
'choices-MAX_NUM_FORMS': '0', # max number of forms
491
'choices-TOTAL_FORMS': '3', # the number of forms rendered
492
'choices-INITIAL_FORMS': '2', # the number of forms with initial data
493
'choices-MIN_NUM_FORMS': '0', # min number of forms
494
'choices-MAX_NUM_FORMS': '0', # max number of forms
442
495
'choices-0-choice': 'Calexico',
443
496
'choices-0-votes': '100',
444
497
'choices-0-ORDER': '1',
468
521
# they will be sorted below everything else.
471
'choices-TOTAL_FORMS': '4', # the number of forms rendered
472
'choices-INITIAL_FORMS': '3', # the number of forms with initial data
473
'choices-MAX_NUM_FORMS': '0', # max number of forms
524
'choices-TOTAL_FORMS': '4', # the number of forms rendered
525
'choices-INITIAL_FORMS': '3', # the number of forms with initial data
526
'choices-MIN_NUM_FORMS': '0', # min number of forms
527
'choices-MAX_NUM_FORMS': '0', # max number of forms
474
528
'choices-0-choice': 'Calexico',
475
529
'choices-0-votes': '100',
476
530
'choices-0-ORDER': '1',
504
558
# Ordering should work with blank fieldsets.
507
'choices-TOTAL_FORMS': '3', # the number of forms rendered
508
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
509
'choices-MAX_NUM_FORMS': '0', # max number of forms
561
'choices-TOTAL_FORMS': '3', # the number of forms rendered
562
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
563
'choices-MIN_NUM_FORMS': '0', # min number of forms
564
'choices-MAX_NUM_FORMS': '0', # max number of forms
512
567
ChoiceFormSet = formset_factory(Choice, can_order=True)
556
611
# Let's delete Fergie, and put The Decemberists ahead of Calexico.
559
'choices-TOTAL_FORMS': '4', # the number of forms rendered
560
'choices-INITIAL_FORMS': '3', # the number of forms with initial data
561
'choices-MAX_NUM_FORMS': '0', # max number of forms
614
'choices-TOTAL_FORMS': '4', # the number of forms rendered
615
'choices-INITIAL_FORMS': '3', # the number of forms with initial data
616
'choices-MIN_NUM_FORMS': '0', # min number of forms
617
'choices-MAX_NUM_FORMS': '0', # max number of forms
562
618
'choices-0-choice': 'Calexico',
563
619
'choices-0-votes': '100',
564
620
'choices-0-ORDER': '1',
618
675
# We start out with a some duplicate data.
621
'drinks-TOTAL_FORMS': '2', # the number of forms rendered
622
'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
623
'drinks-MAX_NUM_FORMS': '0', # max number of forms
678
'drinks-TOTAL_FORMS': '2', # the number of forms rendered
679
'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
680
'drinks-MIN_NUM_FORMS': '0', # min number of forms
681
'drinks-MAX_NUM_FORMS': '0', # max number of forms
624
682
'drinks-0-name': 'Gin and Tonic',
625
683
'drinks-1-name': 'Gin and Tonic',
637
695
# Make sure we didn't break the valid case.
640
'drinks-TOTAL_FORMS': '2', # the number of forms rendered
641
'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
642
'drinks-MAX_NUM_FORMS': '0', # max number of forms
698
'drinks-TOTAL_FORMS': '2', # the number of forms rendered
699
'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
700
'drinks-MIN_NUM_FORMS': '0', # min number of forms
701
'drinks-MAX_NUM_FORMS': '0', # max number of forms
643
702
'drinks-0-name': 'Gin and Tonic',
644
703
'drinks-1-name': 'Bloody Mary',
803
863
# Regression test for #12878 #################################################
806
'drinks-TOTAL_FORMS': '2', # the number of forms rendered
807
'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
808
'drinks-MAX_NUM_FORMS': '0', # max number of forms
866
'drinks-TOTAL_FORMS': '2', # the number of forms rendered
867
'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
868
'drinks-MIN_NUM_FORMS': '0', # min number of forms
869
'drinks-MAX_NUM_FORMS': '0', # max number of forms
809
870
'drinks-0-name': 'Gin and Tonic',
810
871
'drinks-1-name': 'Gin and Tonic',
895
956
'choices-TOTAL_FORMS': '1', # number of forms rendered
896
957
'choices-INITIAL_FORMS': '0', # number of forms with initial data
958
'choices-MIN_NUM_FORMS': '0', # min number of forms
897
959
'choices-MAX_NUM_FORMS': '0', # max number of forms
898
960
'choices-0-choice': 'Calexico',
899
961
'choices-0-votes': '100',
901
963
formset = AnotherChoiceFormSet(data, auto_id=False, prefix='choices')
902
964
self.assertTrue(formset.is_valid())
903
self.assertTrue(all([form.is_valid_called for form in formset.forms]))
965
self.assertTrue(all(form.is_valid_called for form in formset.forms))
905
967
def test_hard_limit_on_instantiated_forms(self):
906
968
"""A formset has a hard limit on the number of forms instantiated."""
915
977
'choices-TOTAL_FORMS': '4',
916
978
'choices-INITIAL_FORMS': '0',
979
'choices-MIN_NUM_FORMS': '0', # min number of forms
917
980
'choices-MAX_NUM_FORMS': '4',
918
981
'choices-0-choice': 'Zero',
919
982
'choices-0-votes': '0',
946
1009
'choices-TOTAL_FORMS': '4',
947
1010
'choices-INITIAL_FORMS': '0',
1011
'choices-MIN_NUM_FORMS': '0', # min number of forms
948
1012
'choices-MAX_NUM_FORMS': '4',
949
1013
'choices-0-choice': 'Zero',
950
1014
'choices-0-votes': '0',
994
1058
formset = CheckFormSet(data, prefix='check')
995
1059
self.assertTrue(formset.is_valid())
998
1061
def test_formset_total_error_count(self):
999
1062
"""A valid formset should have 0 total errors."""
1000
data = [ # formset_data, expected error count
1063
data = [ # formset_data, expected error count
1001
1064
([('Calexico', '100')], 0),
1002
1065
([('Calexico', '')], 1),
1003
1066
([('', 'invalid')], 2),
1004
1067
([('Calexico', '100'), ('Calexico', '')], 1),
1005
1068
([('Calexico', ''), ('Calexico', '')], 2),
1008
1071
for formset_data, expected_error_count in data:
1009
1072
formset = self.make_choiceformset(formset_data)
1010
1073
self.assertEqual(formset.total_error_count(), expected_error_count)
1012
1075
def test_formset_total_error_count_with_non_form_errors(self):
1014
'choices-TOTAL_FORMS': '2', # the number of forms rendered
1015
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
1016
'choices-MAX_NUM_FORMS': '2', # max number of forms - should be ignored
1077
'choices-TOTAL_FORMS': '2', # the number of forms rendered
1078
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
1079
'choices-MAX_NUM_FORMS': '2', # max number of forms - should be ignored
1017
1080
'choices-0-choice': 'Zero',
1018
1081
'choices-0-votes': '0',
1019
1082
'choices-1-choice': 'One',
1033
'choices-TOTAL_FORMS': '1', # the number of forms rendered
1034
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
1035
'choices-MAX_NUM_FORMS': '0', # max number of forms
1096
'choices-TOTAL_FORMS': '1', # the number of forms rendered
1097
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
1098
'choices-MIN_NUM_FORMS': '0', # min number of forms
1099
'choices-MAX_NUM_FORMS': '0', # max number of forms
1036
1100
'choices-0-choice': 'Calexico',
1037
1101
'choices-0-votes': '100',
1040
1105
class Choice(Form):
1041
1106
choice = CharField()
1042
1107
votes = IntegerField()
1044
1109
ChoiceFormSet = formset_factory(Choice)
1046
1112
class FormsetAsFooTests(TestCase):
1047
1113
def test_as_table(self):
1048
1114
formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
1049
self.assertHTMLEqual(formset.as_table(),"""<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="0" />
1115
self.assertHTMLEqual(formset.as_table(), """<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MIN_NUM_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="0" />
1050
1116
<tr><th>Choice:</th><td><input type="text" name="choices-0-choice" value="Calexico" /></td></tr>
1051
1117
<tr><th>Votes:</th><td><input type="number" name="choices-0-votes" value="100" /></td></tr>""")
1053
1119
def test_as_p(self):
1054
1120
formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
1055
self.assertHTMLEqual(formset.as_p(),"""<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="0" />
1121
self.assertHTMLEqual(formset.as_p(), """<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MIN_NUM_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="0" />
1056
1122
<p>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></p>
1057
1123
<p>Votes: <input type="number" name="choices-0-votes" value="100" /></p>""")
1059
1125
def test_as_ul(self):
1060
1126
formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
1061
self.assertHTMLEqual(formset.as_ul(),"""<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="0" />
1127
self.assertHTMLEqual(formset.as_ul(), """<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MIN_NUM_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="0" />
1062
1128
<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
1063
1129
<li>Votes: <input type="number" name="choices-0-votes" value="100" /></li>""")
1125
1191
# The empty forms should be equal.
1126
1192
self.assertHTMLEqual(empty_forms[0].as_p(), empty_forms[1].as_p())
1128
1195
class TestEmptyFormSet(TestCase):
1129
1196
def test_empty_formset_is_valid(self):
1130
1197
"""Test that an empty formset still calls clean()"""
1131
1198
EmptyFsetWontValidateFormset = formset_factory(FavoriteDrinkForm, extra=0, formset=EmptyFsetWontValidate)
1132
formset = EmptyFsetWontValidateFormset(data={'form-INITIAL_FORMS':'0', 'form-TOTAL_FORMS':'0'},prefix="form")
1133
formset2 = EmptyFsetWontValidateFormset(data={'form-INITIAL_FORMS':'0', 'form-TOTAL_FORMS':'1', 'form-0-name':'bah' },prefix="form")
1199
formset = EmptyFsetWontValidateFormset(data={'form-INITIAL_FORMS': '0', 'form-TOTAL_FORMS': '0'}, prefix="form")
1200
formset2 = EmptyFsetWontValidateFormset(data={'form-INITIAL_FORMS': '0', 'form-TOTAL_FORMS': '1', 'form-0-name': 'bah'}, prefix="form")
1134
1201
self.assertFalse(formset.is_valid())
1135
1202
self.assertFalse(formset2.is_valid())