PageRenderTime 273ms CodeModel.GetById 61ms app.highlight 168ms RepoModel.GetById 32ms app.codeStats 1ms

/tests/regressiontests/forms/tests/formsets.py

https://code.google.com/p/mango-py/
Python | 911 lines | 886 code | 16 blank | 9 comment | 5 complexity | 405025b1a4dba096029ef748e44c6361 MD5 | raw file
  1# -*- coding: utf-8 -*-
  2from django.forms import Form, CharField, IntegerField, ValidationError, DateField
  3from django.forms.formsets import formset_factory, BaseFormSet
  4from django.utils.unittest import TestCase
  5
  6
  7class Choice(Form):
  8    choice = CharField()
  9    votes = IntegerField()
 10
 11
 12# FormSet allows us to use multiple instance of the same form on 1 page. For now,
 13# the best way to create a FormSet is by using the formset_factory function.
 14ChoiceFormSet = formset_factory(Choice)
 15
 16
 17class FavoriteDrinkForm(Form):
 18    name = CharField()
 19
 20
 21class BaseFavoriteDrinksFormSet(BaseFormSet):
 22    def clean(self):
 23        seen_drinks = []
 24
 25        for drink in self.cleaned_data:
 26            if drink['name'] in seen_drinks:
 27                raise ValidationError('You may only specify a drink once.')
 28
 29            seen_drinks.append(drink['name'])
 30
 31
 32class EmptyFsetWontValidate(BaseFormSet):
 33    def clean(self):
 34        raise ValidationError("Clean method called")
 35
 36
 37# Let's define a FormSet that takes a list of favorite drinks, but raises an
 38# error if there are any duplicates. Used in ``test_clean_hook``,
 39# ``test_regression_6926`` & ``test_regression_12878``.
 40FavoriteDrinksFormSet = formset_factory(FavoriteDrinkForm,
 41    formset=BaseFavoriteDrinksFormSet, extra=3)
 42
 43
 44class FormsFormsetTestCase(TestCase):
 45    def test_basic_formset(self):
 46        # A FormSet constructor takes the same arguments as Form. Let's create a FormSet
 47        # for adding data. By default, it displays 1 blank form. It can display more,
 48        # but we'll look at how to do so later.
 49        formset = ChoiceFormSet(auto_id=False, prefix='choices')
 50        self.assertEqual(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" />
 51<tr><th>Choice:</th><td><input type="text" name="choices-0-choice" /></td></tr>
 52<tr><th>Votes:</th><td><input type="text" name="choices-0-votes" /></td></tr>""")
 53
 54        # On thing to note is that there needs to be a special value in the data. This
 55        # value tells the FormSet how many forms were displayed so it can tell how
 56        # many forms it needs to clean and validate. You could use javascript to create
 57        # new forms on the client side, but they won't get validated unless you increment
 58        # the TOTAL_FORMS field appropriately.
 59
 60        data = {
 61            'choices-TOTAL_FORMS': '1', # the number of forms rendered
 62            'choices-INITIAL_FORMS': '0', # the number of forms with initial data
 63            'choices-MAX_NUM_FORMS': '0', # max number of forms
 64            'choices-0-choice': 'Calexico',
 65            'choices-0-votes': '100',
 66        }
 67        # We treat FormSet pretty much like we would treat a normal Form. FormSet has an
 68        # is_valid method, and a cleaned_data or errors attribute depending on whether all
 69        # the forms passed validation. However, unlike a Form instance, cleaned_data and
 70        # errors will be a list of dicts rather than just a single dict.
 71
 72        formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
 73        self.assertTrue(formset.is_valid())
 74        self.assertEqual([form.cleaned_data for form in formset.forms], [{'votes': 100, 'choice': u'Calexico'}])
 75
 76        # If a FormSet was not passed any data, its is_valid method should return False.
 77        formset = ChoiceFormSet()
 78        self.assertFalse(formset.is_valid())
 79
 80    def test_formset_validation(self):
 81        # FormSet instances can also have an error attribute if validation failed for
 82        # any of the forms.
 83
 84        data = {
 85            'choices-TOTAL_FORMS': '1', # the number of forms rendered
 86            'choices-INITIAL_FORMS': '0', # the number of forms with initial data
 87            'choices-MAX_NUM_FORMS': '0', # max number of forms
 88            'choices-0-choice': 'Calexico',
 89            'choices-0-votes': '',
 90        }
 91
 92        formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
 93        self.assertFalse(formset.is_valid())
 94        self.assertEqual(formset.errors, [{'votes': [u'This field is required.']}])
 95
 96    def test_formset_initial_data(self):
 97        # We can also prefill a FormSet with existing data by providing an ``initial``
 98        # argument to the constructor. ``initial`` should be a list of dicts. By default,
 99        # an extra blank form is included.
100
101        initial = [{'choice': u'Calexico', 'votes': 100}]
102        formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
103        form_output = []
104
105        for form in formset.forms:
106            form_output.append(form.as_ul())
107
108        self.assertEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
109<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
110<li>Choice: <input type="text" name="choices-1-choice" /></li>
111<li>Votes: <input type="text" name="choices-1-votes" /></li>""")
112
113        # Let's simulate what would happen if we submitted this form.
114
115        data = {
116            'choices-TOTAL_FORMS': '2', # the number of forms rendered
117            'choices-INITIAL_FORMS': '1', # the number of forms with initial data
118            'choices-MAX_NUM_FORMS': '0', # max number of forms
119            'choices-0-choice': 'Calexico',
120            'choices-0-votes': '100',
121            'choices-1-choice': '',
122            'choices-1-votes': '',
123        }
124
125        formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
126        self.assertTrue(formset.is_valid())
127        self.assertEqual([form.cleaned_data for form in formset.forms], [{'votes': 100, 'choice': u'Calexico'}, {}])
128
129    def test_second_form_partially_filled(self):
130        # But the second form was blank! Shouldn't we get some errors? No. If we display
131        # a form as blank, it's ok for it to be submitted as blank. If we fill out even
132        # one of the fields of a blank form though, it will be validated. We may want to
133        # required that at least x number of forms are completed, but we'll show how to
134        # handle that later.
135
136        data = {
137            'choices-TOTAL_FORMS': '2', # the number of forms rendered
138            'choices-INITIAL_FORMS': '1', # the number of forms with initial data
139            'choices-MAX_NUM_FORMS': '0', # max number of forms
140            'choices-0-choice': 'Calexico',
141            'choices-0-votes': '100',
142            'choices-1-choice': 'The Decemberists',
143            'choices-1-votes': '', # missing value
144        }
145
146        formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
147        self.assertFalse(formset.is_valid())
148        self.assertEqual(formset.errors, [{}, {'votes': [u'This field is required.']}])
149
150    def test_delete_prefilled_data(self):
151        # If we delete data that was pre-filled, we should get an error. Simply removing
152        # data from form fields isn't the proper way to delete it. We'll see how to
153        # handle that case later.
154
155        data = {
156            'choices-TOTAL_FORMS': '2', # the number of forms rendered
157            'choices-INITIAL_FORMS': '1', # the number of forms with initial data
158            'choices-MAX_NUM_FORMS': '0', # max number of forms
159            'choices-0-choice': '', # deleted value
160            'choices-0-votes': '', # deleted value
161            'choices-1-choice': '',
162            'choices-1-votes': '',
163        }
164
165        formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
166        self.assertFalse(formset.is_valid())
167        self.assertEqual(formset.errors, [{'votes': [u'This field is required.'], 'choice': [u'This field is required.']}, {}])
168
169    def test_displaying_more_than_one_blank_form(self):
170        # Displaying more than 1 blank form ###########################################
171        # We can also display more than 1 empty form at a time. To do so, pass a
172        # extra argument to formset_factory.
173        ChoiceFormSet = formset_factory(Choice, extra=3)
174
175        formset = ChoiceFormSet(auto_id=False, prefix='choices')
176        form_output = []
177
178        for form in formset.forms:
179            form_output.append(form.as_ul())
180
181        self.assertEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" /></li>
182<li>Votes: <input type="text" name="choices-0-votes" /></li>
183<li>Choice: <input type="text" name="choices-1-choice" /></li>
184<li>Votes: <input type="text" name="choices-1-votes" /></li>
185<li>Choice: <input type="text" name="choices-2-choice" /></li>
186<li>Votes: <input type="text" name="choices-2-votes" /></li>""")
187
188        # Since we displayed every form as blank, we will also accept them back as blank.
189        # This may seem a little strange, but later we will show how to require a minimum
190        # number of forms to be completed.
191
192        data = {
193            'choices-TOTAL_FORMS': '3', # the number of forms rendered
194            'choices-INITIAL_FORMS': '0', # the number of forms with initial data
195            'choices-MAX_NUM_FORMS': '0', # max number of forms
196            'choices-0-choice': '',
197            'choices-0-votes': '',
198            'choices-1-choice': '',
199            'choices-1-votes': '',
200            'choices-2-choice': '',
201            'choices-2-votes': '',
202        }
203
204        formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
205        self.assertTrue(formset.is_valid())
206        self.assertEqual([form.cleaned_data for form in formset.forms], [{}, {}, {}])
207
208    def test_single_form_completed(self):
209        # We can just fill out one of the forms.
210
211        data = {
212            'choices-TOTAL_FORMS': '3', # the number of forms rendered
213            'choices-INITIAL_FORMS': '0', # the number of forms with initial data
214            'choices-MAX_NUM_FORMS': '0', # max number of forms
215            'choices-0-choice': 'Calexico',
216            'choices-0-votes': '100',
217            'choices-1-choice': '',
218            'choices-1-votes': '',
219            'choices-2-choice': '',
220            'choices-2-votes': '',
221        }
222
223        ChoiceFormSet = formset_factory(Choice, extra=3)
224        formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
225        self.assertTrue(formset.is_valid())
226        self.assertEqual([form.cleaned_data for form in formset.forms], [{'votes': 100, 'choice': u'Calexico'}, {}, {}])
227
228    def test_second_form_partially_filled_2(self):
229        # And once again, if we try to partially complete a form, validation will fail.
230
231        data = {
232            'choices-TOTAL_FORMS': '3', # the number of forms rendered
233            'choices-INITIAL_FORMS': '0', # the number of forms with initial data
234            'choices-MAX_NUM_FORMS': '0', # max number of forms
235            'choices-0-choice': 'Calexico',
236            'choices-0-votes': '100',
237            'choices-1-choice': 'The Decemberists',
238            'choices-1-votes': '', # missing value
239            'choices-2-choice': '',
240            'choices-2-votes': '',
241        }
242
243        ChoiceFormSet = formset_factory(Choice, extra=3)
244        formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
245        self.assertFalse(formset.is_valid())
246        self.assertEqual(formset.errors, [{}, {'votes': [u'This field is required.']}, {}])
247
248    def test_more_initial_data(self):
249        # The extra argument also works when the formset is pre-filled with initial
250        # data.
251
252        data = {
253            'choices-TOTAL_FORMS': '3', # the number of forms rendered
254            'choices-INITIAL_FORMS': '0', # the number of forms with initial data
255            'choices-MAX_NUM_FORMS': '0', # max number of forms
256            'choices-0-choice': 'Calexico',
257            'choices-0-votes': '100',
258            'choices-1-choice': '',
259            'choices-1-votes': '', # missing value
260            'choices-2-choice': '',
261            'choices-2-votes': '',
262        }
263
264        initial = [{'choice': u'Calexico', 'votes': 100}]
265        ChoiceFormSet = formset_factory(Choice, extra=3)
266        formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
267        form_output = []
268
269        for form in formset.forms:
270            form_output.append(form.as_ul())
271
272        self.assertEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
273<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
274<li>Choice: <input type="text" name="choices-1-choice" /></li>
275<li>Votes: <input type="text" name="choices-1-votes" /></li>
276<li>Choice: <input type="text" name="choices-2-choice" /></li>
277<li>Votes: <input type="text" name="choices-2-votes" /></li>
278<li>Choice: <input type="text" name="choices-3-choice" /></li>
279<li>Votes: <input type="text" name="choices-3-votes" /></li>""")
280
281        # Make sure retrieving an empty form works, and it shows up in the form list
282
283        self.assertTrue(formset.empty_form.empty_permitted)
284        self.assertEqual(formset.empty_form.as_ul(), """<li>Choice: <input type="text" name="choices-__prefix__-choice" /></li>
285<li>Votes: <input type="text" name="choices-__prefix__-votes" /></li>""")
286
287    def test_formset_with_deletion(self):
288        # FormSets with deletion ######################################################
289        # We can easily add deletion ability to a FormSet with an argument to
290        # formset_factory. This will add a boolean field to each form instance. When
291        # that boolean field is True, the form will be in formset.deleted_forms
292
293        ChoiceFormSet = formset_factory(Choice, can_delete=True)
294
295        initial = [{'choice': u'Calexico', 'votes': 100}, {'choice': u'Fergie', 'votes': 900}]
296        formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
297        form_output = []
298
299        for form in formset.forms:
300            form_output.append(form.as_ul())
301
302        self.assertEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
303<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
304<li>Delete: <input type="checkbox" name="choices-0-DELETE" /></li>
305<li>Choice: <input type="text" name="choices-1-choice" value="Fergie" /></li>
306<li>Votes: <input type="text" name="choices-1-votes" value="900" /></li>
307<li>Delete: <input type="checkbox" name="choices-1-DELETE" /></li>
308<li>Choice: <input type="text" name="choices-2-choice" /></li>
309<li>Votes: <input type="text" name="choices-2-votes" /></li>
310<li>Delete: <input type="checkbox" name="choices-2-DELETE" /></li>""")
311
312        # To delete something, we just need to set that form's special delete field to
313        # 'on'. Let's go ahead and delete Fergie.
314
315        data = {
316            'choices-TOTAL_FORMS': '3', # the number of forms rendered
317            'choices-INITIAL_FORMS': '2', # the number of forms with initial data
318            'choices-MAX_NUM_FORMS': '0', # max number of forms
319            'choices-0-choice': 'Calexico',
320            'choices-0-votes': '100',
321            'choices-0-DELETE': '',
322            'choices-1-choice': 'Fergie',
323            'choices-1-votes': '900',
324            'choices-1-DELETE': 'on',
325            'choices-2-choice': '',
326            'choices-2-votes': '',
327            'choices-2-DELETE': '',
328        }
329
330        formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
331        self.assertTrue(formset.is_valid())
332        self.assertEqual([form.cleaned_data for form in formset.forms], [{'votes': 100, 'DELETE': False, 'choice': u'Calexico'}, {'votes': 900, 'DELETE': True, 'choice': u'Fergie'}, {}])
333        self.assertEqual([form.cleaned_data for form in formset.deleted_forms], [{'votes': 900, 'DELETE': True, 'choice': u'Fergie'}])
334
335        # If we fill a form with something and then we check the can_delete checkbox for
336        # that form, that form's errors should not make the entire formset invalid since
337        # it's going to be deleted.
338
339        class CheckForm(Form):
340           field = IntegerField(min_value=100)
341
342        data = {
343            'check-TOTAL_FORMS': '3', # the number of forms rendered
344            'check-INITIAL_FORMS': '2', # the number of forms with initial data
345            'check-MAX_NUM_FORMS': '0', # max number of forms
346            'check-0-field': '200',
347            'check-0-DELETE': '',
348            'check-1-field': '50',
349            'check-1-DELETE': 'on',
350            'check-2-field': '',
351            'check-2-DELETE': '',
352        }
353        CheckFormSet = formset_factory(CheckForm, can_delete=True)
354        formset = CheckFormSet(data, prefix='check')
355        self.assertTrue(formset.is_valid())
356
357        # If we remove the deletion flag now we will have our validation back.
358        data['check-1-DELETE'] = ''
359        formset = CheckFormSet(data, prefix='check')
360        self.assertFalse(formset.is_valid())
361
362        # Should be able to get deleted_forms from a valid formset even if a
363        # deleted form would have been invalid.
364
365        class Person(Form):
366            name = CharField()
367
368        PeopleForm = formset_factory(
369            form=Person,
370            can_delete=True)
371
372        p = PeopleForm(
373            {'form-0-name': u'', 'form-0-DELETE': u'on', # no name!
374             'form-TOTAL_FORMS': 1, 'form-INITIAL_FORMS': 1,
375             'form-MAX_NUM_FORMS': 1})
376
377        self.assertTrue(p.is_valid())
378        self.assertEqual(len(p.deleted_forms), 1)
379
380    def test_formsets_with_ordering(self):
381        # FormSets with ordering ######################################################
382        # We can also add ordering ability to a FormSet with an argument to
383        # formset_factory. This will add a integer field to each form instance. When
384        # form validation succeeds, [form.cleaned_data for form in formset.forms] will have the data in the correct
385        # order specified by the ordering fields. If a number is duplicated in the set
386        # of ordering fields, for instance form 0 and form 3 are both marked as 1, then
387        # the form index used as a secondary ordering criteria. In order to put
388        # something at the front of the list, you'd need to set it's order to 0.
389
390        ChoiceFormSet = formset_factory(Choice, can_order=True)
391
392        initial = [{'choice': u'Calexico', 'votes': 100}, {'choice': u'Fergie', 'votes': 900}]
393        formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
394        form_output = []
395
396        for form in formset.forms:
397            form_output.append(form.as_ul())
398
399        self.assertEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
400<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
401<li>Order: <input type="text" name="choices-0-ORDER" value="1" /></li>
402<li>Choice: <input type="text" name="choices-1-choice" value="Fergie" /></li>
403<li>Votes: <input type="text" name="choices-1-votes" value="900" /></li>
404<li>Order: <input type="text" name="choices-1-ORDER" value="2" /></li>
405<li>Choice: <input type="text" name="choices-2-choice" /></li>
406<li>Votes: <input type="text" name="choices-2-votes" /></li>
407<li>Order: <input type="text" name="choices-2-ORDER" /></li>""")
408
409        data = {
410            'choices-TOTAL_FORMS': '3', # the number of forms rendered
411            'choices-INITIAL_FORMS': '2', # the number of forms with initial data
412            'choices-MAX_NUM_FORMS': '0', # max number of forms
413            'choices-0-choice': 'Calexico',
414            'choices-0-votes': '100',
415            'choices-0-ORDER': '1',
416            'choices-1-choice': 'Fergie',
417            'choices-1-votes': '900',
418            'choices-1-ORDER': '2',
419            'choices-2-choice': 'The Decemberists',
420            'choices-2-votes': '500',
421            'choices-2-ORDER': '0',
422        }
423
424        formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
425        self.assertTrue(formset.is_valid())
426        form_output = []
427
428        for form in formset.ordered_forms:
429            form_output.append(form.cleaned_data)
430
431        self.assertEqual(form_output, [
432            {'votes': 500, 'ORDER': 0, 'choice': u'The Decemberists'},
433            {'votes': 100, 'ORDER': 1, 'choice': u'Calexico'},
434            {'votes': 900, 'ORDER': 2, 'choice': u'Fergie'},
435        ])
436
437    def test_empty_ordered_fields(self):
438        # Ordering fields are allowed to be left blank, and if they *are* left blank,
439        # they will be sorted below everything else.
440
441        data = {
442            'choices-TOTAL_FORMS': '4', # the number of forms rendered
443            'choices-INITIAL_FORMS': '3', # the number of forms with initial data
444            'choices-MAX_NUM_FORMS': '0', # max number of forms
445            'choices-0-choice': 'Calexico',
446            'choices-0-votes': '100',
447            'choices-0-ORDER': '1',
448            'choices-1-choice': 'Fergie',
449            'choices-1-votes': '900',
450            'choices-1-ORDER': '2',
451            'choices-2-choice': 'The Decemberists',
452            'choices-2-votes': '500',
453            'choices-2-ORDER': '',
454            'choices-3-choice': 'Basia Bulat',
455            'choices-3-votes': '50',
456            'choices-3-ORDER': '',
457        }
458
459        ChoiceFormSet = formset_factory(Choice, can_order=True)
460        formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
461        self.assertTrue(formset.is_valid())
462        form_output = []
463
464        for form in formset.ordered_forms:
465            form_output.append(form.cleaned_data)
466
467        self.assertEqual(form_output, [
468            {'votes': 100, 'ORDER': 1, 'choice': u'Calexico'},
469            {'votes': 900, 'ORDER': 2, 'choice': u'Fergie'},
470            {'votes': 500, 'ORDER': None, 'choice': u'The Decemberists'},
471            {'votes': 50, 'ORDER': None, 'choice': u'Basia Bulat'},
472        ])
473
474    def test_ordering_blank_fieldsets(self):
475        # Ordering should work with blank fieldsets.
476
477        data = {
478            'choices-TOTAL_FORMS': '3', # the number of forms rendered
479            'choices-INITIAL_FORMS': '0', # the number of forms with initial data
480            'choices-MAX_NUM_FORMS': '0', # max number of forms
481        }
482
483        ChoiceFormSet = formset_factory(Choice, can_order=True)
484        formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
485        self.assertTrue(formset.is_valid())
486        form_output = []
487
488        for form in formset.ordered_forms:
489            form_output.append(form.cleaned_data)
490
491        self.assertEqual(form_output, [])
492
493    def test_formset_with_ordering_and_deletion(self):
494        # FormSets with ordering + deletion ###########################################
495        # Let's try throwing ordering and deletion into the same form.
496
497        ChoiceFormSet = formset_factory(Choice, can_order=True, can_delete=True)
498
499        initial = [
500            {'choice': u'Calexico', 'votes': 100},
501            {'choice': u'Fergie', 'votes': 900},
502            {'choice': u'The Decemberists', 'votes': 500},
503        ]
504        formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
505        form_output = []
506
507        for form in formset.forms:
508            form_output.append(form.as_ul())
509
510        self.assertEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
511<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
512<li>Order: <input type="text" name="choices-0-ORDER" value="1" /></li>
513<li>Delete: <input type="checkbox" name="choices-0-DELETE" /></li>
514<li>Choice: <input type="text" name="choices-1-choice" value="Fergie" /></li>
515<li>Votes: <input type="text" name="choices-1-votes" value="900" /></li>
516<li>Order: <input type="text" name="choices-1-ORDER" value="2" /></li>
517<li>Delete: <input type="checkbox" name="choices-1-DELETE" /></li>
518<li>Choice: <input type="text" name="choices-2-choice" value="The Decemberists" /></li>
519<li>Votes: <input type="text" name="choices-2-votes" value="500" /></li>
520<li>Order: <input type="text" name="choices-2-ORDER" value="3" /></li>
521<li>Delete: <input type="checkbox" name="choices-2-DELETE" /></li>
522<li>Choice: <input type="text" name="choices-3-choice" /></li>
523<li>Votes: <input type="text" name="choices-3-votes" /></li>
524<li>Order: <input type="text" name="choices-3-ORDER" /></li>
525<li>Delete: <input type="checkbox" name="choices-3-DELETE" /></li>""")
526
527        # Let's delete Fergie, and put The Decemberists ahead of Calexico.
528
529        data = {
530            'choices-TOTAL_FORMS': '4', # the number of forms rendered
531            'choices-INITIAL_FORMS': '3', # the number of forms with initial data
532            'choices-MAX_NUM_FORMS': '0', # max number of forms
533            'choices-0-choice': 'Calexico',
534            'choices-0-votes': '100',
535            'choices-0-ORDER': '1',
536            'choices-0-DELETE': '',
537            'choices-1-choice': 'Fergie',
538            'choices-1-votes': '900',
539            'choices-1-ORDER': '2',
540            'choices-1-DELETE': 'on',
541            'choices-2-choice': 'The Decemberists',
542            'choices-2-votes': '500',
543            'choices-2-ORDER': '0',
544            'choices-2-DELETE': '',
545            'choices-3-choice': '',
546            'choices-3-votes': '',
547            'choices-3-ORDER': '',
548            'choices-3-DELETE': '',
549        }
550
551        formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
552        self.assertTrue(formset.is_valid())
553        form_output = []
554
555        for form in formset.ordered_forms:
556            form_output.append(form.cleaned_data)
557
558        self.assertEqual(form_output, [
559            {'votes': 500, 'DELETE': False, 'ORDER': 0, 'choice': u'The Decemberists'},
560            {'votes': 100, 'DELETE': False, 'ORDER': 1, 'choice': u'Calexico'},
561        ])
562        self.assertEqual([form.cleaned_data for form in formset.deleted_forms], [{'votes': 900, 'DELETE': True, 'ORDER': 2, 'choice': u'Fergie'}])
563
564    def test_invalid_deleted_form_with_ordering(self):
565        # Should be able to get ordered forms from a valid formset even if a
566        # deleted form would have been invalid.
567
568        class Person(Form):
569            name = CharField()
570
571        PeopleForm = formset_factory(form=Person, can_delete=True, can_order=True)
572
573        p = PeopleForm({
574            'form-0-name': u'',
575            'form-0-DELETE': u'on', # no name!
576            'form-TOTAL_FORMS': 1,
577            'form-INITIAL_FORMS': 1,
578            'form-MAX_NUM_FORMS': 1
579        })
580
581        self.assertTrue(p.is_valid())
582        self.assertEqual(p.ordered_forms, [])
583
584    def test_clean_hook(self):
585        # FormSet clean hook ##########################################################
586        # FormSets have a hook for doing extra validation that shouldn't be tied to any
587        # particular form. It follows the same pattern as the clean hook on Forms.
588
589        # We start out with a some duplicate data.
590
591        data = {
592            'drinks-TOTAL_FORMS': '2', # the number of forms rendered
593            'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
594            'drinks-MAX_NUM_FORMS': '0', # max number of forms
595            'drinks-0-name': 'Gin and Tonic',
596            'drinks-1-name': 'Gin and Tonic',
597        }
598
599        formset = FavoriteDrinksFormSet(data, prefix='drinks')
600        self.assertFalse(formset.is_valid())
601
602        # Any errors raised by formset.clean() are available via the
603        # formset.non_form_errors() method.
604
605        for error in formset.non_form_errors():
606            self.assertEqual(str(error), 'You may only specify a drink once.')
607
608        # Make sure we didn't break the valid case.
609
610        data = {
611            'drinks-TOTAL_FORMS': '2', # the number of forms rendered
612            'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
613            'drinks-MAX_NUM_FORMS': '0', # max number of forms
614            'drinks-0-name': 'Gin and Tonic',
615            'drinks-1-name': 'Bloody Mary',
616        }
617
618        formset = FavoriteDrinksFormSet(data, prefix='drinks')
619        self.assertTrue(formset.is_valid())
620        self.assertEqual(formset.non_form_errors(), [])
621
622    def test_limiting_max_forms(self):
623        # Limiting the maximum number of forms ########################################
624        # Base case for max_num.
625
626        # When not passed, max_num will take its default value of None, i.e. unlimited
627        # number of forms, only controlled by the value of the extra parameter.
628
629        LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=3)
630        formset = LimitedFavoriteDrinkFormSet()
631        form_output = []
632
633        for form in formset.forms:
634            form_output.append(str(form))
635
636        self.assertEqual('\n'.join(form_output), """<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>
637<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>
638<tr><th><label for="id_form-2-name">Name:</label></th><td><input type="text" name="form-2-name" id="id_form-2-name" /></td></tr>""")
639
640        # If max_num is 0 then no form is rendered at all.
641        LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=3, max_num=0)
642        formset = LimitedFavoriteDrinkFormSet()
643        form_output = []
644
645        for form in formset.forms:
646            form_output.append(str(form))
647
648        self.assertEqual('\n'.join(form_output), "")
649
650        LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=5, max_num=2)
651        formset = LimitedFavoriteDrinkFormSet()
652        form_output = []
653
654        for form in formset.forms:
655            form_output.append(str(form))
656
657        self.assertEqual('\n'.join(form_output), """<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>
658<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>""")
659
660        # Ensure that max_num has no effect when extra is less than max_num.
661
662        LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=2)
663        formset = LimitedFavoriteDrinkFormSet()
664        form_output = []
665
666        for form in formset.forms:
667            form_output.append(str(form))
668
669        self.assertEqual('\n'.join(form_output), """<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>""")
670
671    def test_max_num_with_initial_data(self):
672        # max_num with initial data
673
674        # When not passed, max_num will take its default value of None, i.e. unlimited
675        # number of forms, only controlled by the values of the initial and extra
676        # parameters.
677
678        initial = [
679            {'name': 'Fernet and Coke'},
680        ]
681        LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1)
682        formset = LimitedFavoriteDrinkFormSet(initial=initial)
683        form_output = []
684
685        for form in formset.forms:
686            form_output.append(str(form))
687
688        self.assertEqual('\n'.join(form_output), """<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" value="Fernet and Coke" id="id_form-0-name" /></td></tr>
689<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>""")
690
691    def test_max_num_zero(self):
692        # If max_num is 0 then no form is rendered at all, even if extra and initial
693        # are specified.
694
695        initial = [
696            {'name': 'Fernet and Coke'},
697            {'name': 'Bloody Mary'},
698        ]
699        LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=0)
700        formset = LimitedFavoriteDrinkFormSet(initial=initial)
701        form_output = []
702
703        for form in formset.forms:
704            form_output.append(str(form))
705
706        self.assertEqual('\n'.join(form_output), "")
707
708    def test_more_initial_than_max_num(self):
709        # More initial forms than max_num will result in only the first max_num of
710        # them to be displayed with no extra forms.
711
712        initial = [
713            {'name': 'Gin Tonic'},
714            {'name': 'Bloody Mary'},
715            {'name': 'Jack and Coke'},
716        ]
717        LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=2)
718        formset = LimitedFavoriteDrinkFormSet(initial=initial)
719        form_output = []
720
721        for form in formset.forms:
722            form_output.append(str(form))
723
724        self.assertEqual('\n'.join(form_output), """<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>
725<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>""")
726
727        # One form from initial and extra=3 with max_num=2 should result in the one
728        # initial form and one extra.
729        initial = [
730            {'name': 'Gin Tonic'},
731        ]
732        LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=3, max_num=2)
733        formset = LimitedFavoriteDrinkFormSet(initial=initial)
734        form_output = []
735
736        for form in formset.forms:
737            form_output.append(str(form))
738
739        self.assertEqual('\n'.join(form_output), """<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>
740<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>""")
741
742    def test_regression_6926(self):
743        # Regression test for #6926 ##################################################
744        # Make sure the management form has the correct prefix.
745
746        formset = FavoriteDrinksFormSet()
747        self.assertEqual(formset.management_form.prefix, 'form')
748
749        data = {
750            'form-TOTAL_FORMS': '2',
751            'form-INITIAL_FORMS': '0',
752            'form-MAX_NUM_FORMS': '0',
753        }
754        formset = FavoriteDrinksFormSet(data=data)
755        self.assertEqual(formset.management_form.prefix, 'form')
756
757        formset = FavoriteDrinksFormSet(initial={})
758        self.assertEqual(formset.management_form.prefix, 'form')
759
760    def test_regression_12878(self):
761        # Regression test for #12878 #################################################
762
763        data = {
764            'drinks-TOTAL_FORMS': '2', # the number of forms rendered
765            'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
766            'drinks-MAX_NUM_FORMS': '0', # max number of forms
767            'drinks-0-name': 'Gin and Tonic',
768            'drinks-1-name': 'Gin and Tonic',
769        }
770
771        formset = FavoriteDrinksFormSet(data, prefix='drinks')
772        self.assertFalse(formset.is_valid())
773        self.assertEqual(formset.non_form_errors(), [u'You may only specify a drink once.'])
774
775    def test_formset_iteration(self):
776        # Regression tests for #16455 -- formset instances are iterable
777        ChoiceFormset = formset_factory(Choice, extra=3)
778        formset = ChoiceFormset()
779
780        # confirm iterated formset yields formset.forms
781        forms = list(formset)
782        self.assertEqual(forms, formset.forms)
783        self.assertEqual(len(formset), len(forms))
784
785        # confirm indexing of formset
786        self.assertEqual(formset[0], forms[0])
787        try:
788            formset[3]
789            self.fail('Requesting an invalid formset index should raise an exception')
790        except IndexError:
791            pass
792
793        # Formets can override the default iteration order
794        class BaseReverseFormSet(BaseFormSet):
795            def __iter__(self):
796                for form in reversed(self.forms):
797                    yield form
798
799        ReverseChoiceFormset = formset_factory(Choice, BaseReverseFormSet, extra=3)
800        reverse_formset = ReverseChoiceFormset()
801
802        # confirm that __iter__ modifies rendering order
803        # compare forms from "reverse" formset with forms from original formset
804        self.assertEqual(str(reverse_formset[0]), str(forms[-1]))
805        self.assertEqual(str(reverse_formset[1]), str(forms[-2]))
806        self.assertEqual(len(reverse_formset), len(forms))
807
808data = {
809    'choices-TOTAL_FORMS': '1', # the number of forms rendered
810    'choices-INITIAL_FORMS': '0', # the number of forms with initial data
811    'choices-MAX_NUM_FORMS': '0', # max number of forms
812    'choices-0-choice': 'Calexico',
813    'choices-0-votes': '100',
814}
815
816class Choice(Form):
817    choice = CharField()
818    votes = IntegerField()
819
820ChoiceFormSet = formset_factory(Choice)
821
822class FormsetAsFooTests(TestCase):
823    def test_as_table(self):
824        formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
825        self.assertEqual(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" />
826<tr><th>Choice:</th><td><input type="text" name="choices-0-choice" value="Calexico" /></td></tr>
827<tr><th>Votes:</th><td><input type="text" name="choices-0-votes" value="100" /></td></tr>""")
828
829    def test_as_p(self):
830        formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
831        self.assertEqual(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" />
832<p>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></p>
833<p>Votes: <input type="text" name="choices-0-votes" value="100" /></p>""")
834
835    def test_as_ul(self):
836        formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
837        self.assertEqual(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" />
838<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
839<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>""")
840
841
842# Regression test for #11418 #################################################
843class ArticleForm(Form):
844    title = CharField()
845    pub_date = DateField()
846
847ArticleFormSet = formset_factory(ArticleForm)
848
849class TestIsBoundBehavior(TestCase):
850    def test_no_data_raises_validation_error(self):
851        self.assertRaises(ValidationError, ArticleFormSet, {})
852
853    def test_with_management_data_attrs_work_fine(self):
854        data = {
855            'form-TOTAL_FORMS': u'1',
856            'form-INITIAL_FORMS': u'0',
857        }
858        formset = ArticleFormSet(data)
859        self.assertEqual(0, formset.initial_form_count())
860        self.assertEqual(1, formset.total_form_count())
861        self.assertTrue(formset.is_bound)
862        self.assertTrue(formset.forms[0].is_bound)
863        self.assertTrue(formset.is_valid())
864        self.assertTrue(formset.forms[0].is_valid())
865        self.assertEqual([{}], formset.cleaned_data)
866
867
868    def test_form_errors_are_cought_by_formset(self):
869        data = {
870            'form-TOTAL_FORMS': u'2',
871            'form-INITIAL_FORMS': u'0',
872            'form-0-title': u'Test',
873            'form-0-pub_date': u'1904-06-16',
874            'form-1-title': u'Test',
875            'form-1-pub_date': u'', # <-- this date is missing but required
876        }
877        formset = ArticleFormSet(data)
878        self.assertFalse(formset.is_valid())
879        self.assertEqual([{}, {'pub_date': [u'This field is required.']}], formset.errors)
880
881    def test_empty_forms_are_unbound(self):
882        data = {
883            'form-TOTAL_FORMS': u'1',
884            'form-INITIAL_FORMS': u'0',
885            'form-0-title': u'Test',
886            'form-0-pub_date': u'1904-06-16',
887        }
888        unbound_formset = ArticleFormSet()
889        bound_formset = ArticleFormSet(data)
890
891        empty_forms = []
892
893        empty_forms.append(unbound_formset.empty_form)
894        empty_forms.append(bound_formset.empty_form)
895
896        # Empty forms should be unbound
897        self.assertFalse(empty_forms[0].is_bound)
898        self.assertFalse(empty_forms[1].is_bound)
899
900        # The empty forms should be equal.
901        self.assertEqual(empty_forms[0].as_p(), empty_forms[1].as_p())
902
903class TestEmptyFormSet(TestCase): 
904    "Test that an empty formset still calls clean()"
905    def test_empty_formset_is_valid(self): 
906        EmptyFsetWontValidateFormset = formset_factory(FavoriteDrinkForm, extra=0, formset=EmptyFsetWontValidate) 
907        formset = EmptyFsetWontValidateFormset(data={'form-INITIAL_FORMS':'0', 'form-TOTAL_FORMS':'0'},prefix="form") 
908        formset2 = EmptyFsetWontValidateFormset(data={'form-INITIAL_FORMS':'0', 'form-TOTAL_FORMS':'1', 'form-0-name':'bah' },prefix="form") 
909        self.assertFalse(formset.is_valid()) 
910        self.assertFalse(formset2.is_valid()) 
911