mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	git-svn-id: http://code.djangoproject.com/svn/django/trunk@12771 bcc190cf-cafb-0310-a4f2-bffc1f526a37
		
			
				
	
	
		
			681 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			681 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # -*- coding: utf-8 -*-
 | |
| tests = """
 | |
| # Basic FormSet creation and usage ############################################
 | |
| 
 | |
| FormSet allows us to use multiple instance of the same form on 1 page. For now,
 | |
| the best way to create a FormSet is by using the formset_factory function.
 | |
| 
 | |
| >>> from django.forms import Form, CharField, IntegerField, ValidationError
 | |
| >>> from django.forms.formsets import formset_factory, BaseFormSet
 | |
| 
 | |
| >>> class Choice(Form):
 | |
| ...     choice = CharField()
 | |
| ...     votes = IntegerField()
 | |
| 
 | |
| >>> ChoiceFormSet = formset_factory(Choice)
 | |
| 
 | |
| A FormSet constructor takes the same arguments as Form. Let's create a FormSet
 | |
| for adding data. By default, it displays 1 blank form. It can display more,
 | |
| but we'll look at how to do so later.
 | |
| 
 | |
| >>> formset = ChoiceFormSet(auto_id=False, prefix='choices')
 | |
| >>> print 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="0" />
 | |
| <tr><th>Choice:</th><td><input type="text" name="choices-0-choice" /></td></tr>
 | |
| <tr><th>Votes:</th><td><input type="text" name="choices-0-votes" /></td></tr>
 | |
| 
 | |
| 
 | |
| On thing to note is that there needs to be a special value in the data. This
 | |
| value tells the FormSet how many forms were displayed so it can tell how
 | |
| many forms it needs to clean and validate. You could use javascript to create
 | |
| new forms on the client side, but they won't get validated unless you increment
 | |
| the TOTAL_FORMS field appropriately.
 | |
| 
 | |
| >>> data = {
 | |
| ...     'choices-TOTAL_FORMS': '1', # the number of forms rendered
 | |
| ...     'choices-INITIAL_FORMS': '0', # the number of forms with initial data
 | |
| ...     'choices-MAX_NUM_FORMS': '0', # max number of forms
 | |
| ...     'choices-0-choice': 'Calexico',
 | |
| ...     'choices-0-votes': '100',
 | |
| ... }
 | |
| 
 | |
| We treat FormSet pretty much like we would treat a normal Form. FormSet has an
 | |
| is_valid method, and a cleaned_data or errors attribute depending on whether all
 | |
| the forms passed validation. However, unlike a Form instance, cleaned_data and
 | |
| errors will be a list of dicts rather than just a single dict.
 | |
| 
 | |
| >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
 | |
| >>> formset.is_valid()
 | |
| True
 | |
| >>> [form.cleaned_data for form in formset.forms]
 | |
| [{'votes': 100, 'choice': u'Calexico'}]
 | |
| 
 | |
| If a FormSet was not passed any data, its is_valid method should return False.
 | |
| >>> formset = ChoiceFormSet()
 | |
| >>> formset.is_valid()
 | |
| False
 | |
| 
 | |
| FormSet instances can also have an error attribute if validation failed for
 | |
| any of the forms.
 | |
| 
 | |
| >>> data = {
 | |
| ...     'choices-TOTAL_FORMS': '1', # the number of forms rendered
 | |
| ...     'choices-INITIAL_FORMS': '0', # the number of forms with initial data
 | |
| ...     'choices-MAX_NUM_FORMS': '0', # max number of forms
 | |
| ...     'choices-0-choice': 'Calexico',
 | |
| ...     'choices-0-votes': '',
 | |
| ... }
 | |
| 
 | |
| >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
 | |
| >>> formset.is_valid()
 | |
| False
 | |
| >>> formset.errors
 | |
| [{'votes': [u'This field is required.']}]
 | |
| 
 | |
| 
 | |
| We can also prefill a FormSet with existing data by providing an ``initial``
 | |
| argument to the constructor. ``initial`` should be a list of dicts. By default,
 | |
| an extra blank form is included.
 | |
| 
 | |
| >>> initial = [{'choice': u'Calexico', 'votes': 100}]
 | |
| >>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
 | |
| >>> for form in formset.forms:
 | |
| ...    print form.as_ul()
 | |
| <li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
 | |
| <li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
 | |
| <li>Choice: <input type="text" name="choices-1-choice" /></li>
 | |
| <li>Votes: <input type="text" name="choices-1-votes" /></li>
 | |
| 
 | |
| 
 | |
| Let's simulate what would happen if we submitted this form.
 | |
| 
 | |
| >>> data = {
 | |
| ...     'choices-TOTAL_FORMS': '2', # the number of forms rendered
 | |
| ...     'choices-INITIAL_FORMS': '1', # the number of forms with initial data
 | |
| ...     'choices-MAX_NUM_FORMS': '0', # max number of forms
 | |
| ...     'choices-0-choice': 'Calexico',
 | |
| ...     'choices-0-votes': '100',
 | |
| ...     'choices-1-choice': '',
 | |
| ...     'choices-1-votes': '',
 | |
| ... }
 | |
| 
 | |
| >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
 | |
| >>> formset.is_valid()
 | |
| True
 | |
| >>> [form.cleaned_data for form in formset.forms]
 | |
| [{'votes': 100, 'choice': u'Calexico'}, {}]
 | |
| 
 | |
| But the second form was blank! Shouldn't we get some errors? No. If we display
 | |
| a form as blank, it's ok for it to be submitted as blank. If we fill out even
 | |
| one of the fields of a blank form though, it will be validated. We may want to
 | |
| required that at least x number of forms are completed, but we'll show how to
 | |
| handle that later.
 | |
| 
 | |
| >>> data = {
 | |
| ...     'choices-TOTAL_FORMS': '2', # the number of forms rendered
 | |
| ...     'choices-INITIAL_FORMS': '1', # the number of forms with initial data
 | |
| ...     'choices-MAX_NUM_FORMS': '0', # max number of forms
 | |
| ...     'choices-0-choice': 'Calexico',
 | |
| ...     'choices-0-votes': '100',
 | |
| ...     'choices-1-choice': 'The Decemberists',
 | |
| ...     'choices-1-votes': '', # missing value
 | |
| ... }
 | |
| 
 | |
| >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
 | |
| >>> formset.is_valid()
 | |
| False
 | |
| >>> formset.errors
 | |
| [{}, {'votes': [u'This field is required.']}]
 | |
| 
 | |
| If we delete data that was pre-filled, we should get an error. Simply removing
 | |
| data from form fields isn't the proper way to delete it. We'll see how to
 | |
| handle that case later.
 | |
| 
 | |
| >>> data = {
 | |
| ...     'choices-TOTAL_FORMS': '2', # the number of forms rendered
 | |
| ...     'choices-INITIAL_FORMS': '1', # the number of forms with initial data
 | |
| ...     'choices-MAX_NUM_FORMS': '0', # max number of forms
 | |
| ...     'choices-0-choice': '', # deleted value
 | |
| ...     'choices-0-votes': '', # deleted value
 | |
| ...     'choices-1-choice': '',
 | |
| ...     'choices-1-votes': '',
 | |
| ... }
 | |
| 
 | |
| >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
 | |
| >>> formset.is_valid()
 | |
| False
 | |
| >>> formset.errors
 | |
| [{'votes': [u'This field is required.'], 'choice': [u'This field is required.']}, {}]
 | |
| 
 | |
| 
 | |
| # Displaying more than 1 blank form ###########################################
 | |
| 
 | |
| We can also display more than 1 empty form at a time. To do so, pass a
 | |
| extra argument to formset_factory.
 | |
| 
 | |
| >>> ChoiceFormSet = formset_factory(Choice, extra=3)
 | |
| 
 | |
| >>> formset = ChoiceFormSet(auto_id=False, prefix='choices')
 | |
| >>> for form in formset.forms:
 | |
| ...    print form.as_ul()
 | |
| <li>Choice: <input type="text" name="choices-0-choice" /></li>
 | |
| <li>Votes: <input type="text" name="choices-0-votes" /></li>
 | |
| <li>Choice: <input type="text" name="choices-1-choice" /></li>
 | |
| <li>Votes: <input type="text" name="choices-1-votes" /></li>
 | |
| <li>Choice: <input type="text" name="choices-2-choice" /></li>
 | |
| <li>Votes: <input type="text" name="choices-2-votes" /></li>
 | |
| 
 | |
| Since we displayed every form as blank, we will also accept them back as blank.
 | |
| This may seem a little strange, but later we will show how to require a minimum
 | |
| number of forms to be completed.
 | |
| 
 | |
| >>> data = {
 | |
| ...     'choices-TOTAL_FORMS': '3', # the number of forms rendered
 | |
| ...     'choices-INITIAL_FORMS': '0', # the number of forms with initial data
 | |
| ...     'choices-MAX_NUM_FORMS': '0', # max number of forms
 | |
| ...     'choices-0-choice': '',
 | |
| ...     'choices-0-votes': '',
 | |
| ...     'choices-1-choice': '',
 | |
| ...     'choices-1-votes': '',
 | |
| ...     'choices-2-choice': '',
 | |
| ...     'choices-2-votes': '',
 | |
| ... }
 | |
| 
 | |
| >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
 | |
| >>> formset.is_valid()
 | |
| True
 | |
| >>> [form.cleaned_data for form in formset.forms]
 | |
| [{}, {}, {}]
 | |
| 
 | |
| 
 | |
| We can just fill out one of the forms.
 | |
| 
 | |
| >>> data = {
 | |
| ...     'choices-TOTAL_FORMS': '3', # the number of forms rendered
 | |
| ...     'choices-INITIAL_FORMS': '0', # the number of forms with initial data
 | |
| ...     'choices-MAX_NUM_FORMS': '0', # max number of forms
 | |
| ...     'choices-0-choice': 'Calexico',
 | |
| ...     'choices-0-votes': '100',
 | |
| ...     'choices-1-choice': '',
 | |
| ...     'choices-1-votes': '',
 | |
| ...     'choices-2-choice': '',
 | |
| ...     'choices-2-votes': '',
 | |
| ... }
 | |
| 
 | |
| >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
 | |
| >>> formset.is_valid()
 | |
| True
 | |
| >>> [form.cleaned_data for form in formset.forms]
 | |
| [{'votes': 100, 'choice': u'Calexico'}, {}, {}]
 | |
| 
 | |
| 
 | |
| And once again, if we try to partially complete a form, validation will fail.
 | |
| 
 | |
| >>> data = {
 | |
| ...     'choices-TOTAL_FORMS': '3', # the number of forms rendered
 | |
| ...     'choices-INITIAL_FORMS': '0', # the number of forms with initial data
 | |
| ...     'choices-MAX_NUM_FORMS': '0', # max number of forms
 | |
| ...     'choices-0-choice': 'Calexico',
 | |
| ...     'choices-0-votes': '100',
 | |
| ...     'choices-1-choice': 'The Decemberists',
 | |
| ...     'choices-1-votes': '', # missing value
 | |
| ...     'choices-2-choice': '',
 | |
| ...     'choices-2-votes': '',
 | |
| ... }
 | |
| 
 | |
| >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
 | |
| >>> formset.is_valid()
 | |
| False
 | |
| >>> formset.errors
 | |
| [{}, {'votes': [u'This field is required.']}, {}]
 | |
| 
 | |
| 
 | |
| The extra argument also works when the formset is pre-filled with initial
 | |
| data.
 | |
| 
 | |
| >>> initial = [{'choice': u'Calexico', 'votes': 100}]
 | |
| >>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
 | |
| >>> for form in formset.forms:
 | |
| ...    print form.as_ul()
 | |
| <li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
 | |
| <li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
 | |
| <li>Choice: <input type="text" name="choices-1-choice" /></li>
 | |
| <li>Votes: <input type="text" name="choices-1-votes" /></li>
 | |
| <li>Choice: <input type="text" name="choices-2-choice" /></li>
 | |
| <li>Votes: <input type="text" name="choices-2-votes" /></li>
 | |
| <li>Choice: <input type="text" name="choices-3-choice" /></li>
 | |
| <li>Votes: <input type="text" name="choices-3-votes" /></li>
 | |
| 
 | |
| Make sure retrieving an empty form works, and it shows up in the form list
 | |
| 
 | |
| >>> formset.empty_form.empty_permitted
 | |
| True
 | |
| >>> print formset.empty_form.as_ul()
 | |
| <li>Choice: <input type="text" name="choices-__prefix__-choice" /></li>
 | |
| <li>Votes: <input type="text" name="choices-__prefix__-votes" /></li>
 | |
| 
 | |
| # FormSets with deletion ######################################################
 | |
| 
 | |
| We can easily add deletion ability to a FormSet with an argument to
 | |
| formset_factory. This will add a boolean field to each form instance. When
 | |
| that boolean field is True, the form will be in formset.deleted_forms
 | |
| 
 | |
| >>> ChoiceFormSet = formset_factory(Choice, can_delete=True)
 | |
| 
 | |
| >>> initial = [{'choice': u'Calexico', 'votes': 100}, {'choice': u'Fergie', 'votes': 900}]
 | |
| >>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
 | |
| >>> for form in formset.forms:
 | |
| ...    print form.as_ul()
 | |
| <li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
 | |
| <li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
 | |
| <li>Delete: <input type="checkbox" name="choices-0-DELETE" /></li>
 | |
| <li>Choice: <input type="text" name="choices-1-choice" value="Fergie" /></li>
 | |
| <li>Votes: <input type="text" name="choices-1-votes" value="900" /></li>
 | |
| <li>Delete: <input type="checkbox" name="choices-1-DELETE" /></li>
 | |
| <li>Choice: <input type="text" name="choices-2-choice" /></li>
 | |
| <li>Votes: <input type="text" name="choices-2-votes" /></li>
 | |
| <li>Delete: <input type="checkbox" name="choices-2-DELETE" /></li>
 | |
| 
 | |
| To delete something, we just need to set that form's special delete field to
 | |
| 'on'. Let's go ahead and delete Fergie.
 | |
| 
 | |
| >>> data = {
 | |
| ...     'choices-TOTAL_FORMS': '3', # the number of forms rendered
 | |
| ...     'choices-INITIAL_FORMS': '2', # the number of forms with initial data
 | |
| ...     'choices-MAX_NUM_FORMS': '0', # max number of forms
 | |
| ...     'choices-0-choice': 'Calexico',
 | |
| ...     'choices-0-votes': '100',
 | |
| ...     'choices-0-DELETE': '',
 | |
| ...     'choices-1-choice': 'Fergie',
 | |
| ...     'choices-1-votes': '900',
 | |
| ...     'choices-1-DELETE': 'on',
 | |
| ...     'choices-2-choice': '',
 | |
| ...     'choices-2-votes': '',
 | |
| ...     'choices-2-DELETE': '',
 | |
| ... }
 | |
| 
 | |
| >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
 | |
| >>> formset.is_valid()
 | |
| True
 | |
| >>> [form.cleaned_data for form in formset.forms]
 | |
| [{'votes': 100, 'DELETE': False, 'choice': u'Calexico'}, {'votes': 900, 'DELETE': True, 'choice': u'Fergie'}, {}]
 | |
| >>> [form.cleaned_data for form in formset.deleted_forms]
 | |
| [{'votes': 900, 'DELETE': True, 'choice': u'Fergie'}]
 | |
| 
 | |
| If we fill a form with something and then we check the can_delete checkbox for
 | |
| that form, that form's errors should not make the entire formset invalid since
 | |
| it's going to be deleted.
 | |
| 
 | |
| >>> class CheckForm(Form):
 | |
| ...    field = IntegerField(min_value=100)
 | |
| 
 | |
| >>> data = {
 | |
| ...     'check-TOTAL_FORMS': '3', # the number of forms rendered
 | |
| ...     'check-INITIAL_FORMS': '2', # the number of forms with initial data
 | |
| ...     'check-MAX_NUM_FORMS': '0', # max number of forms
 | |
| ...     'check-0-field': '200',
 | |
| ...     'check-0-DELETE': '',
 | |
| ...     'check-1-field': '50',
 | |
| ...     'check-1-DELETE': 'on',
 | |
| ...     'check-2-field': '',
 | |
| ...     'check-2-DELETE': '',
 | |
| ... }
 | |
| >>> CheckFormSet = formset_factory(CheckForm, can_delete=True)
 | |
| >>> formset = CheckFormSet(data, prefix='check')
 | |
| >>> formset.is_valid()
 | |
| True
 | |
| 
 | |
| If we remove the deletion flag now we will have our validation back.
 | |
| 
 | |
| >>> data['check-1-DELETE'] = ''
 | |
| >>> formset = CheckFormSet(data, prefix='check')
 | |
| >>> formset.is_valid()
 | |
| False
 | |
| 
 | |
| Should be able to get deleted_forms from a valid formset even if a
 | |
| deleted form would have been invalid.
 | |
| 
 | |
| >>> class Person(Form):
 | |
| ...     name = CharField()
 | |
| 
 | |
| >>> PeopleForm = formset_factory(
 | |
| ...     form=Person,
 | |
| ...     can_delete=True)
 | |
| 
 | |
| >>> p = PeopleForm(
 | |
| ...     {'form-0-name': u'', 'form-0-DELETE': u'on', # no name!
 | |
| ...      'form-TOTAL_FORMS': 1, 'form-INITIAL_FORMS': 1,
 | |
| ...      'form-MAX_NUM_FORMS': 1})
 | |
| 
 | |
| >>> p.is_valid()
 | |
| True
 | |
| >>> len(p.deleted_forms)
 | |
| 1
 | |
| 
 | |
| # FormSets with ordering ######################################################
 | |
| 
 | |
| We can also add ordering ability to a FormSet with an agrument to
 | |
| formset_factory. This will add a integer field to each form instance. When
 | |
| form validation succeeds, [form.cleaned_data for form in formset.forms] will have the data in the correct
 | |
| order specified by the ordering fields. If a number is duplicated in the set
 | |
| of ordering fields, for instance form 0 and form 3 are both marked as 1, then
 | |
| the form index used as a secondary ordering criteria. In order to put
 | |
| something at the front of the list, you'd need to set it's order to 0.
 | |
| 
 | |
| >>> ChoiceFormSet = formset_factory(Choice, can_order=True)
 | |
| 
 | |
| >>> initial = [{'choice': u'Calexico', 'votes': 100}, {'choice': u'Fergie', 'votes': 900}]
 | |
| >>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
 | |
| >>> for form in formset.forms:
 | |
| ...    print form.as_ul()
 | |
| <li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
 | |
| <li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
 | |
| <li>Order: <input type="text" name="choices-0-ORDER" value="1" /></li>
 | |
| <li>Choice: <input type="text" name="choices-1-choice" value="Fergie" /></li>
 | |
| <li>Votes: <input type="text" name="choices-1-votes" value="900" /></li>
 | |
| <li>Order: <input type="text" name="choices-1-ORDER" value="2" /></li>
 | |
| <li>Choice: <input type="text" name="choices-2-choice" /></li>
 | |
| <li>Votes: <input type="text" name="choices-2-votes" /></li>
 | |
| <li>Order: <input type="text" name="choices-2-ORDER" /></li>
 | |
| 
 | |
| >>> data = {
 | |
| ...     'choices-TOTAL_FORMS': '3', # the number of forms rendered
 | |
| ...     'choices-INITIAL_FORMS': '2', # the number of forms with initial data
 | |
| ...     'choices-MAX_NUM_FORMS': '0', # max number of forms
 | |
| ...     'choices-0-choice': 'Calexico',
 | |
| ...     'choices-0-votes': '100',
 | |
| ...     'choices-0-ORDER': '1',
 | |
| ...     'choices-1-choice': 'Fergie',
 | |
| ...     'choices-1-votes': '900',
 | |
| ...     'choices-1-ORDER': '2',
 | |
| ...     'choices-2-choice': 'The Decemberists',
 | |
| ...     'choices-2-votes': '500',
 | |
| ...     'choices-2-ORDER': '0',
 | |
| ... }
 | |
| 
 | |
| >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
 | |
| >>> formset.is_valid()
 | |
| True
 | |
| >>> for form in formset.ordered_forms:
 | |
| ...    print form.cleaned_data
 | |
| {'votes': 500, 'ORDER': 0, 'choice': u'The Decemberists'}
 | |
| {'votes': 100, 'ORDER': 1, 'choice': u'Calexico'}
 | |
| {'votes': 900, 'ORDER': 2, 'choice': u'Fergie'}
 | |
| 
 | |
| Ordering fields are allowed to be left blank, and if they *are* left blank,
 | |
| they will be sorted below everything else.
 | |
| 
 | |
| >>> data = {
 | |
| ...     'choices-TOTAL_FORMS': '4', # the number of forms rendered
 | |
| ...     'choices-INITIAL_FORMS': '3', # the number of forms with initial data
 | |
| ...     'choices-MAX_NUM_FORMS': '0', # max number of forms
 | |
| ...     'choices-0-choice': 'Calexico',
 | |
| ...     'choices-0-votes': '100',
 | |
| ...     'choices-0-ORDER': '1',
 | |
| ...     'choices-1-choice': 'Fergie',
 | |
| ...     'choices-1-votes': '900',
 | |
| ...     'choices-1-ORDER': '2',
 | |
| ...     'choices-2-choice': 'The Decemberists',
 | |
| ...     'choices-2-votes': '500',
 | |
| ...     'choices-2-ORDER': '',
 | |
| ...     'choices-3-choice': 'Basia Bulat',
 | |
| ...     'choices-3-votes': '50',
 | |
| ...     'choices-3-ORDER': '',
 | |
| ... }
 | |
| 
 | |
| >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
 | |
| >>> formset.is_valid()
 | |
| True
 | |
| >>> for form in formset.ordered_forms:
 | |
| ...    print form.cleaned_data
 | |
| {'votes': 100, 'ORDER': 1, 'choice': u'Calexico'}
 | |
| {'votes': 900, 'ORDER': 2, 'choice': u'Fergie'}
 | |
| {'votes': 500, 'ORDER': None, 'choice': u'The Decemberists'}
 | |
| {'votes': 50, 'ORDER': None, 'choice': u'Basia Bulat'}
 | |
| 
 | |
| Ordering should work with blank fieldsets.
 | |
| 
 | |
| >>> data = {
 | |
| ...     'choices-TOTAL_FORMS': '3', # the number of forms rendered
 | |
| ...     'choices-INITIAL_FORMS': '0', # the number of forms with initial data
 | |
| ...     'choices-MAX_NUM_FORMS': '0', # max number of forms
 | |
| ... }
 | |
| 
 | |
| >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
 | |
| >>> formset.is_valid()
 | |
| True
 | |
| >>> for form in formset.ordered_forms:
 | |
| ...    print form.cleaned_data
 | |
| 
 | |
| # FormSets with ordering + deletion ###########################################
 | |
| 
 | |
| Let's try throwing ordering and deletion into the same form.
 | |
| 
 | |
| >>> ChoiceFormSet = formset_factory(Choice, can_order=True, can_delete=True)
 | |
| 
 | |
| >>> initial = [
 | |
| ...     {'choice': u'Calexico', 'votes': 100},
 | |
| ...     {'choice': u'Fergie', 'votes': 900},
 | |
| ...     {'choice': u'The Decemberists', 'votes': 500},
 | |
| ... ]
 | |
| >>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
 | |
| >>> for form in formset.forms:
 | |
| ...    print form.as_ul()
 | |
| <li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
 | |
| <li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
 | |
| <li>Order: <input type="text" name="choices-0-ORDER" value="1" /></li>
 | |
| <li>Delete: <input type="checkbox" name="choices-0-DELETE" /></li>
 | |
| <li>Choice: <input type="text" name="choices-1-choice" value="Fergie" /></li>
 | |
| <li>Votes: <input type="text" name="choices-1-votes" value="900" /></li>
 | |
| <li>Order: <input type="text" name="choices-1-ORDER" value="2" /></li>
 | |
| <li>Delete: <input type="checkbox" name="choices-1-DELETE" /></li>
 | |
| <li>Choice: <input type="text" name="choices-2-choice" value="The Decemberists" /></li>
 | |
| <li>Votes: <input type="text" name="choices-2-votes" value="500" /></li>
 | |
| <li>Order: <input type="text" name="choices-2-ORDER" value="3" /></li>
 | |
| <li>Delete: <input type="checkbox" name="choices-2-DELETE" /></li>
 | |
| <li>Choice: <input type="text" name="choices-3-choice" /></li>
 | |
| <li>Votes: <input type="text" name="choices-3-votes" /></li>
 | |
| <li>Order: <input type="text" name="choices-3-ORDER" /></li>
 | |
| <li>Delete: <input type="checkbox" name="choices-3-DELETE" /></li>
 | |
| 
 | |
| Let's delete Fergie, and put The Decemberists ahead of Calexico.
 | |
| 
 | |
| >>> data = {
 | |
| ...     'choices-TOTAL_FORMS': '4', # the number of forms rendered
 | |
| ...     'choices-INITIAL_FORMS': '3', # the number of forms with initial data
 | |
| ...     'choices-MAX_NUM_FORMS': '0', # max number of forms
 | |
| ...     'choices-0-choice': 'Calexico',
 | |
| ...     'choices-0-votes': '100',
 | |
| ...     'choices-0-ORDER': '1',
 | |
| ...     'choices-0-DELETE': '',
 | |
| ...     'choices-1-choice': 'Fergie',
 | |
| ...     'choices-1-votes': '900',
 | |
| ...     'choices-1-ORDER': '2',
 | |
| ...     'choices-1-DELETE': 'on',
 | |
| ...     'choices-2-choice': 'The Decemberists',
 | |
| ...     'choices-2-votes': '500',
 | |
| ...     'choices-2-ORDER': '0',
 | |
| ...     'choices-2-DELETE': '',
 | |
| ...     'choices-3-choice': '',
 | |
| ...     'choices-3-votes': '',
 | |
| ...     'choices-3-ORDER': '',
 | |
| ...     'choices-3-DELETE': '',
 | |
| ... }
 | |
| 
 | |
| >>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
 | |
| >>> formset.is_valid()
 | |
| True
 | |
| >>> for form in formset.ordered_forms:
 | |
| ...    print form.cleaned_data
 | |
| {'votes': 500, 'DELETE': False, 'ORDER': 0, 'choice': u'The Decemberists'}
 | |
| {'votes': 100, 'DELETE': False, 'ORDER': 1, 'choice': u'Calexico'}
 | |
| >>> [form.cleaned_data for form in formset.deleted_forms]
 | |
| [{'votes': 900, 'DELETE': True, 'ORDER': 2, 'choice': u'Fergie'}]
 | |
| 
 | |
| Should be able to get ordered forms from a valid formset even if a
 | |
| deleted form would have been invalid.
 | |
| 
 | |
| >>> class Person(Form):
 | |
| ...     name = CharField()
 | |
| 
 | |
| >>> PeopleForm = formset_factory(
 | |
| ...     form=Person,
 | |
| ...     can_delete=True,
 | |
| ...     can_order=True)
 | |
| 
 | |
| >>> p = PeopleForm(
 | |
| ...     {'form-0-name': u'', 'form-0-DELETE': u'on', # no name!
 | |
| ...      'form-TOTAL_FORMS': 1, 'form-INITIAL_FORMS': 1,
 | |
| ...      'form-MAX_NUM_FORMS': 1})
 | |
| 
 | |
| >>> p.is_valid()
 | |
| True
 | |
| >>> p.ordered_forms
 | |
| []
 | |
| 
 | |
| # FormSet clean hook ##########################################################
 | |
| 
 | |
| FormSets have a hook for doing extra validation that shouldn't be tied to any
 | |
| particular form. It follows the same pattern as the clean hook on Forms.
 | |
| 
 | |
| Let's define a FormSet that takes a list of favorite drinks, but raises am
 | |
| error if there are any duplicates.
 | |
| 
 | |
| >>> class FavoriteDrinkForm(Form):
 | |
| ...     name = CharField()
 | |
| ...
 | |
| 
 | |
| >>> class BaseFavoriteDrinksFormSet(BaseFormSet):
 | |
| ...     def clean(self):
 | |
| ...         seen_drinks = []
 | |
| ...         for drink in self.cleaned_data:
 | |
| ...             if drink['name'] in seen_drinks:
 | |
| ...                 raise ValidationError('You may only specify a drink once.')
 | |
| ...             seen_drinks.append(drink['name'])
 | |
| ...
 | |
| 
 | |
| >>> FavoriteDrinksFormSet = formset_factory(FavoriteDrinkForm,
 | |
| ...     formset=BaseFavoriteDrinksFormSet, extra=3)
 | |
| 
 | |
| We start out with a some duplicate data.
 | |
| 
 | |
| >>> data = {
 | |
| ...     'drinks-TOTAL_FORMS': '2', # the number of forms rendered
 | |
| ...     'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
 | |
| ...     'drinks-MAX_NUM_FORMS': '0', # max number of forms
 | |
| ...     'drinks-0-name': 'Gin and Tonic',
 | |
| ...     'drinks-1-name': 'Gin and Tonic',
 | |
| ... }
 | |
| 
 | |
| >>> formset = FavoriteDrinksFormSet(data, prefix='drinks')
 | |
| >>> formset.is_valid()
 | |
| False
 | |
| 
 | |
| Any errors raised by formset.clean() are available via the
 | |
| formset.non_form_errors() method.
 | |
| 
 | |
| >>> for error in formset.non_form_errors():
 | |
| ...     print error
 | |
| You may only specify a drink once.
 | |
| 
 | |
| 
 | |
| Make sure we didn't break the valid case.
 | |
| 
 | |
| >>> data = {
 | |
| ...     'drinks-TOTAL_FORMS': '2', # the number of forms rendered
 | |
| ...     'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
 | |
| ...     'drinks-MAX_NUM_FORMS': '0', # max number of forms
 | |
| ...     'drinks-0-name': 'Gin and Tonic',
 | |
| ...     'drinks-1-name': 'Bloody Mary',
 | |
| ... }
 | |
| 
 | |
| >>> formset = FavoriteDrinksFormSet(data, prefix='drinks')
 | |
| >>> formset.is_valid()
 | |
| True
 | |
| >>> for error in formset.non_form_errors():
 | |
| ...     print error
 | |
| 
 | |
| # Limiting the maximum number of forms ########################################
 | |
| 
 | |
| # Base case for max_num.
 | |
| 
 | |
| >>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=5, max_num=2)
 | |
| >>> formset = LimitedFavoriteDrinkFormSet()
 | |
| >>> for form in formset.forms:
 | |
| ...     print form
 | |
| <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>
 | |
| <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>
 | |
| 
 | |
| # Ensure the that max_num has no affect when extra is less than max_forms.
 | |
| 
 | |
| >>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=2)
 | |
| >>> formset = LimitedFavoriteDrinkFormSet()
 | |
| >>> for form in formset.forms:
 | |
| ...     print form
 | |
| <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>
 | |
| 
 | |
| # max_num with initial data
 | |
| 
 | |
| # More initial forms than max_num will result in only the first max_num of
 | |
| # them to be displayed with no extra forms.
 | |
| 
 | |
| >>> initial = [
 | |
| ...     {'name': 'Gin Tonic'},
 | |
| ...     {'name': 'Bloody Mary'},
 | |
| ...     {'name': 'Jack and Coke'},
 | |
| ... ]
 | |
| >>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=2)
 | |
| >>> formset = LimitedFavoriteDrinkFormSet(initial=initial)
 | |
| >>> for form in formset.forms:
 | |
| ...     print form
 | |
| <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>
 | |
| <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>
 | |
| 
 | |
| # One form from initial and extra=3 with max_num=2 should result in the one
 | |
| # initial form and one extra.
 | |
| 
 | |
| >>> initial = [
 | |
| ...     {'name': 'Gin Tonic'},
 | |
| ... ]
 | |
| >>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=3, max_num=2)
 | |
| >>> formset = LimitedFavoriteDrinkFormSet(initial=initial)
 | |
| >>> for form in formset.forms:
 | |
| ...     print form
 | |
| <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>
 | |
| <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>
 | |
| 
 | |
| 
 | |
| # Regression test for #6926 ##################################################
 | |
| 
 | |
| Make sure the management form has the correct prefix.
 | |
| 
 | |
| >>> formset = FavoriteDrinksFormSet()
 | |
| >>> formset.management_form.prefix
 | |
| 'form'
 | |
| 
 | |
| >>> formset = FavoriteDrinksFormSet(data={})
 | |
| >>> formset.management_form.prefix
 | |
| 'form'
 | |
| 
 | |
| >>> formset = FavoriteDrinksFormSet(initial={})
 | |
| >>> formset.management_form.prefix
 | |
| 'form'
 | |
| 
 | |
| # Regression test for #12878 #################################################
 | |
| 
 | |
| >>> data = {
 | |
| ...     'drinks-TOTAL_FORMS': '2', # the number of forms rendered
 | |
| ...     'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
 | |
| ...     'drinks-MAX_NUM_FORMS': '0', # max number of forms
 | |
| ...     'drinks-0-name': 'Gin and Tonic',
 | |
| ...     'drinks-1-name': 'Gin and Tonic',
 | |
| ... }
 | |
| 
 | |
| >>> formset = FavoriteDrinksFormSet(data, prefix='drinks')
 | |
| >>> formset.is_valid()
 | |
| False
 | |
| >>> print formset.non_form_errors()
 | |
| <ul class="errorlist"><li>You may only specify a drink once.</li></ul>
 | |
| 
 | |
| """
 |