diff --git a/django/newforms/formsets.py b/django/newforms/formsets.py
index c2649640a0..330fb088b0 100644
--- a/django/newforms/formsets.py
+++ b/django/newforms/formsets.py
@@ -5,6 +5,11 @@ FORM_COUNT_FIELD_NAME = 'COUNT'
ORDERING_FIELD_NAME = 'ORDER'
DELETION_FIELD_NAME = 'DELETE'
+def formset_for_form(form, num_extra=1, orderable=False, deletable=False):
+ """Return a FormSet for the given form class."""
+ attrs = {'form_class': form, 'num_extra': num_extra, 'orderable': orderable, 'deletable': deletable}
+ return type(form.__name__ + 'FormSet', (BaseFormSet,), attrs)
+
class ManagementForm(forms.Form):
"""
``ManagementForm`` is used to keep track of how many form instances
@@ -15,140 +20,130 @@ class ManagementForm(forms.Form):
self.base_fields[FORM_COUNT_FIELD_NAME] = forms.IntegerField(widget=forms.HiddenInput)
super(ManagementForm, self).__init__(*args, **kwargs)
-class FormSet(object):
+class BaseFormSet(object):
"""A collection of instances of the same Form class."""
- def __init__(self, form_class, data=None, auto_id='id_%s', prefix=None, initial=None):
- self.form_class = form_class
+ def __init__(self, data=None, auto_id='id_%s', prefix=None, initial=None):
+ self.is_bound = data is not None
self.prefix = prefix or 'form'
self.auto_id = auto_id
+ self.data = data
+ self.initial = initial
# initialization is different depending on whether we recieved data, initial, or nothing
if data:
self.management_form = ManagementForm(data, auto_id=self.auto_id, prefix=self.prefix)
if self.management_form.is_valid():
- form_count = self.management_form.clean_data[FORM_COUNT_FIELD_NAME]
+ self.total_forms = self.management_form.clean_data[FORM_COUNT_FIELD_NAME]
+ self.required_forms = self.total_forms - self.num_extra
else:
# not sure that ValidationError is the best thing to raise here
raise forms.ValidationError('ManagementForm data is missing or has been tampered with')
- self.form_list = self._forms_for_data(data, form_count=form_count)
elif initial:
- form_count = len(initial)
- self.management_form = ManagementForm(initial={FORM_COUNT_FIELD_NAME: form_count+1}, auto_id=self.auto_id, prefix=self.prefix)
- self.form_list = self._forms_for_initial(initial, form_count=form_count)
+ self.required_forms = len(initial)
+ self.total_forms = self.required_forms + self.num_extra
+ self.management_form = ManagementForm(initial={FORM_COUNT_FIELD_NAME: self.total_forms}, auto_id=self.auto_id, prefix=self.prefix)
else:
- self.management_form = ManagementForm(initial={FORM_COUNT_FIELD_NAME: 1}, auto_id=self.auto_id, prefix=self.prefix)
- self.form_list = self._empty_forms(form_count=1)
+ self.required_forms = 0
+ self.total_forms = self.num_extra
+ self.management_form = ManagementForm(initial={FORM_COUNT_FIELD_NAME: self.total_forms}, auto_id=self.auto_id, prefix=self.prefix)
- # TODO: initialization needs some cleanup and some restructuring
- # TODO: allow more than 1 extra blank form to be displayed
+ def _get_form_list(self):
+ """Return a list of Form instances."""
+ if not hasattr(self, '_form_list'):
+ self._form_list = []
+ for i in range(0, self.total_forms):
+ kwargs = {'data': self.data, 'auto_id': self.auto_id, 'prefix': self.add_prefix(i)}
+ if self.initial and i < self.required_forms:
+ kwargs['initial'] = self.initial[i]
+ form_instance = self.form_class(**kwargs)
+ # HACK: if the form was not completed, replace it with a blank one
+ if self.data and i >= self.required_forms and form_instance.is_empty():
+ form_instance = self.form_class(auto_id=self.auto_id, prefix=self.add_prefix(i))
+ self.add_fields(form_instance, i)
+ self._form_list.append(form_instance)
+ return self._form_list
- def _forms_for_data(self, data, form_count):
- form_list = []
- for i in range(0, form_count-1):
- form_instance = self.form_class(data, auto_id=self.auto_id, prefix=self.add_prefix(i))
- self.add_fields(form_instance, i)
- form_list.append(form_instance)
- # hackish, but if the last form stayed empty, replace it with a
- # blank one. no 'data' or 'initial' arguments
- form_instance = self.form_class(data, auto_id=self.auto_id, prefix=self.add_prefix(form_count-1))
- if form_instance.is_empty():
- form_instance = self.form_class(auto_id=self.auto_id, prefix=self.add_prefix(form_count-1))
- self.add_fields(form_instance, form_count-1)
- form_list.append(form_instance)
- return form_list
+ form_list = property(_get_form_list)
- def _forms_for_initial(self, initial, form_count):
- form_list = []
- # generate a form for each item in initial, plus one empty one
- for i in range(0, form_count):
- form_instance = self.form_class(initial=initial[i], auto_id=self.auto_id, prefix=self.add_prefix(i))
- self.add_fields(form_instance, i)
- form_list.append(form_instance)
- # add 1 empty form
- form_instance = self.form_class(auto_id=self.auto_id, prefix=self.add_prefix(i+1))
- self.add_fields(form_instance, i+1)
- form_list.append(form_instance)
- return form_list
+ def full_clean(self):
+ """
+ Cleans all of self.data and populates self.__errors and self.clean_data.
+ """
+ is_valid = True
+
+ errors = []
+ if not self.is_bound: # Stop further processing.
+ self.__errors = errors
+ return
+ clean_data = []
+ deleted_data = []
+
+ self._form_list = []
+ # step backwards through the forms so when we hit the first filled one
+ # we can easily require the rest without backtracking
+ required = False
+ for i in range(self.total_forms-1, -1, -1):
+ kwargs = {'data': self.data, 'auto_id': self.auto_id, 'prefix': self.add_prefix(i)}
+
+ # prep initial data if there is some
+ if self.initial and i < self.required_forms:
+ kwargs['initial'] = self.initial[i]
+
+ # create the form instance
+ form = self.form_class(**kwargs)
+ self.add_fields(form, i)
+
+ if self.data and (i < self.required_forms or not form.is_empty(exceptions=[ORDERING_FIELD_NAME])):
+ required = True # forms cannot be empty anymore
+
+ # HACK: if the form is empty and not required, replace it with a blank one
+ # this is necessary to keep form.errors empty
+ if not required and self.data and form.is_empty(exceptions=[ORDERING_FIELD_NAME]):
+ form = self.form_class(auto_id=self.auto_id, prefix=self.add_prefix(i))
+ self.add_fields(form, i)
+ else:
+ # if the formset is still vaild overall and this form instance
+ # is valid, keep appending to clean_data
+ if is_valid and form.is_valid():
+ if self.deletable and form.clean_data[DELETION_FIELD_NAME]:
+ deleted_data.append(form.clean_data)
+ else:
+ clean_data.append(form.clean_data)
+ else:
+ is_valid = False
+ # append to errors regardless
+ errors.append(form.errors)
+ self._form_list.append(form)
- def _empty_forms(self, form_count):
- form_list = []
- # we only need one form, there's no inital data and no post data
- form_instance = self.form_class(auto_id=self.auto_id, prefix=self.add_prefix(0))
- form_list.append(form_instance)
- return form_list
-
- def get_forms(self):
- return self.form_list
+ deleted_data.reverse()
+ if self.orderable:
+ clean_data.sort(lambda x,y: x[ORDERING_FIELD_NAME] - y[ORDERING_FIELD_NAME])
+ else:
+ clean_data.reverse()
+ errors.reverse()
+ self._form_list.reverse()
+
+ if is_valid:
+ self.clean_data = clean_data
+ self.deleted_data = deleted_data
+ self.errors = errors
+ self._is_valid = is_valid
+ # TODO: user defined formset validation.
+
def add_fields(self, form, index):
"""A hook for adding extra fields on to each form instance."""
- pass
+ if self.orderable:
+ form.fields[ORDERING_FIELD_NAME] = forms.IntegerField(label='Order', initial=index+1)
+ if self.deletable:
+ form.fields[DELETION_FIELD_NAME] = forms.BooleanField(label='Delete', required=False)
def add_prefix(self, index):
return '%s-%s' % (self.prefix, index)
- def _get_clean_data(self):
- return self.get_clean_data()
-
- def get_clean_data(self):
- clean_data_list = []
- for form in self.get_non_empty_forms():
- clean_data_list.append(form.clean_data)
- return clean_data_list
-
- clean_data = property(_get_clean_data)
-
def is_valid(self):
- for form in self.get_non_empty_forms():
- if not form.is_valid():
- return False
- return True
-
- def get_non_empty_forms(self):
- """Return all forms that aren't empty."""
- return [form for form in self.form_list if not form.is_empty()]
-
-class FormSetWithDeletion(FormSet):
- """A ``FormSet`` that handles deletion of forms."""
-
- def add_fields(self, form, index):
- """Add a delete checkbox to each form."""
- form.fields[DELETION_FIELD_NAME] = forms.BooleanField(label='Delete', required=False)
-
- def get_clean_data(self):
- self.deleted_data = []
- clean_data_list = []
- for form in self.get_non_empty_forms():
- if form.clean_data[DELETION_FIELD_NAME]:
- # stick data marked for deletetion in self.deleted_data
- self.deleted_data.append(form.clean_data)
- else:
- clean_data_list.append(form.clean_data)
- return clean_data_list
-
-class FormSetWithOrdering(FormSet):
- """A ``FormSet`` that handles re-ordering of forms."""
-
- def get_non_empty_forms(self):
- return [form for form in self.form_list if not form.is_empty(exceptions=[ORDERING_FIELD_NAME])]
-
- def add_fields(self, form, index):
- """Add an ordering field to each form."""
- form.fields[ORDERING_FIELD_NAME] = forms.IntegerField(label='Order', initial=index+1)
-
- def get_clean_data(self):
- clean_data_list = []
- for form in self.get_non_empty_forms():
- clean_data_list.append(form.clean_data)
- # sort clean_data by the 'ORDER' field
- clean_data_list.sort(lambda x,y: x[ORDERING_FIELD_NAME] - y[ORDERING_FIELD_NAME])
- return clean_data_list
-
- def is_valid(self):
- for form in self.get_non_empty_forms():
- if not form.is_valid():
- return False
- return True
+ self.full_clean()
+ return self._is_valid
# TODO: handle deletion and ordering in the same FormSet
# TODO: model integration: form_for_instance and form_for_model type functions
diff --git a/tests/regressiontests/forms/formsets.py b/tests/regressiontests/forms/formsets.py
new file mode 100644
index 0000000000..8d0e3b8d7c
--- /dev/null
+++ b/tests/regressiontests/forms/formsets.py
@@ -0,0 +1,419 @@
+# -*- coding: utf-8 -*-
+formset_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_for_form function.
+
+>>> from django.newforms import Form, CharField, IntegerField
+>>> from django.newforms.formsets import formset_for_form
+
+>>> class Choice(Form):
+... choice = CharField()
+... votes = IntegerField()
+
+>>> ChoiceFormSet = formset_for_form(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')
+>>> for form in formset.form_list:
+... print form.as_ul()
+
Choice:
+
Votes:
+
+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 COUNT field appropriately.
+
+>>> data = {
+... 'choices-COUNT': '1', # the number of forms rendered
+... '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 clean_data or errors attribute depending on whether all
+the forms passed validation. However, unlike a Form instance, clean_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
+>>> formset.clean_data
+[{'votes': 100, 'choice': u'Calexico'}]
+
+
+FormSet instances can also have an error attribute if validation failed for
+any of the forms.
+
+>>> data = {
+... 'choices-COUNT': '1', # the number of forms rendered
+... '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.']}]
+
+Like a Form instance, clean_data won't exist if the formset wasn't validated.
+
+>>> formset.clean_data
+Traceback (most recent call last):
+...
+AttributeError: 'ChoiceFormSet' object has no attribute 'clean_data'
+
+
+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.form_list:
+... print form.as_ul()
+
Choice:
+
Votes:
+
Choice:
+
Votes:
+
+
+Let's simulate what would happen if we submitted this form.
+
+>>> data = {
+... 'choices-COUNT': '2', # the number of forms rendered
+... '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
+>>> formset.clean_data
+[{'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-COUNT': '2', # the number of forms rendered
+... '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-COUNT': '2', # the number of forms rendered
+... '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
+num_extra argument to formset_for_form.
+
+>>> ChoiceFormSet = formset_for_form(Choice, num_extra=3)
+
+>>> formset = ChoiceFormSet(auto_id=False, prefix='choices')
+>>> for form in formset.form_list:
+... print form.as_ul()
+
Choice:
+
Votes:
+
Choice:
+
Votes:
+
Choice:
+
Votes:
+
+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-COUNT': '3', # the number of forms rendered
+... '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
+>>> formset.clean_data
+[]
+
+
+We can just fill out one of the forms.
+
+>>> data = {
+... 'choices-COUNT': '3', # the number of forms rendered
+... '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
+>>> formset.clean_data
+[{'votes': 100, 'choice': u'Calexico'}]
+
+
+And once again, if we try to partially complete a form, validation will fail.
+
+>>> data = {
+... 'choices-COUNT': '3', # the number of forms rendered
+... '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 num_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.form_list:
+... print form.as_ul()
+
Choice:
+
Votes:
+
Choice:
+
Votes:
+
Choice:
+
Votes:
+
Choice:
+
Votes:
+
+
+If we try to skip a form, even if it was initially displayed as blank, we will
+get an error.
+
+>>> data = {
+... 'choices-COUNT': '4', # the number of forms rendered
+... 'choices-0-choice': 'Calexico',
+... 'choices-0-votes': '100',
+... 'choices-1-choice': '',
+... 'choices-1-votes': '',
+... 'choices-2-choice': 'The Decemberists',
+... 'choices-2-votes': '12',
+... 'choices-3-choice': '',
+... 'choices-3-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.']}, {}]
+
+
+# FormSets with deletion ######################################################
+
+We can easily add deletion ability to a FormSet with an agrument to
+formset_for_form. This will add a boolean field to each form instance. When
+that boolean field is True, the cleaned data will be in formset.deleted_data
+rather than formset.clean_data
+
+>>> ChoiceFormSet = formset_for_form(Choice, deletable=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.form_list:
+... print form.as_ul()
+
Choice:
+
Votes:
+
Delete:
+
Choice:
+
Votes:
+
Delete:
+
Choice:
+
Votes:
+
Delete:
+
+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-COUNT': '3', # the number of forms rendered
+... '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
+>>> formset.clean_data
+[{'votes': 100, 'DELETE': False, 'choice': u'Calexico'}]
+>>> formset.deleted_data
+[{'votes': 900, 'DELETE': True, 'choice': u'Fergie'}]
+
+# FormSets with ordering ######################################################
+
+We can also add ordering ability to a FormSet with an agrument to
+formset_for_form. This will add a integer field to each form instance. When
+form validation succeeds, formset.clean_data 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_for_form(Choice, orderable=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.form_list:
+... print form.as_ul()
+
Choice:
+
Votes:
+
Order:
+
Choice:
+
Votes:
+
Order:
+
Choice:
+
Votes:
+
Order:
+
+>>> data = {
+... 'choices-COUNT': '3', # the number of forms rendered
+... '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 clean_data in formset.clean_data:
+... print clean_data
+{'votes': 500, 'ORDER': 0, 'choice': u'The Decemberists'}
+{'votes': 100, 'ORDER': 1, 'choice': u'Calexico'}
+{'votes': 900, 'ORDER': 2, 'choice': u'Fergie'}
+
+# FormSets with ordering + deletion ###########################################
+
+Let's try throwing ordering and deletion into the same form.
+
+>>> ChoiceFormSet = formset_for_form(Choice, orderable=True, deletable=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.form_list:
+... print form.as_ul()
+
Choice:
+
Votes:
+
Order:
+
Delete:
+
Choice:
+
Votes:
+
Order:
+
Delete:
+
Choice:
+
Votes:
+
Order:
+
Delete:
+
Choice:
+
Votes:
+
Order:
+
Delete:
+
+Let's delete Fergie, and put The Decemberists ahead of Calexico.
+
+>>> data = {
+... 'choices-COUNT': '4', # the number of forms rendered
+... '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': '4',
+... 'choices-3-DELETE': '',
+... }
+
+>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+>>> formset.is_valid()
+True
+>>> for clean_data in formset.clean_data:
+... print clean_data
+{'votes': 500, 'DELETE': False, 'ORDER': 0, 'choice': u'The Decemberists'}
+{'votes': 100, 'DELETE': False, 'ORDER': 1, 'choice': u'Calexico'}
+>>> formset.deleted_data
+[{'votes': 900, 'DELETE': True, 'ORDER': 2, 'choice': u'Fergie'}]
+
+
+"""
diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py
index 839b8fc5d9..afe8b204c6 100644
--- a/tests/regressiontests/forms/tests.py
+++ b/tests/regressiontests/forms/tests.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
from localflavor import localflavor_tests
from regressions import regression_tests
+from formsets import formset_tests
form_tests = r"""
>>> from django.newforms import *
@@ -2898,158 +2899,6 @@ True
>>> p.clean_data
{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)}
-# FormSets ####################################################################
-
-FormSets allow you to create a bunch of instances of the same form class and
-get back clean data as a list of dicts.
-
->>> from django.newforms import formsets
-
->>> class ChoiceForm(Form):
-... choice = CharField()
-... votes = IntegerField()
-
-
-Create an empty form set
-
->>> form_set = formsets.FormSet(ChoiceForm, prefix='choices', auto_id=False)
->>> for form in form_set.get_forms():
-... print form.as_ul()
-
-
-
-Tests for dealing with POSTed data
-
->>> data = {
-... 'choices-COUNT': u'3', # the number of forms rendered
-... 'choices-0-choice': u'The Doors',
-... 'choices-0-votes': u'50',
-... 'choices-1-choice': u'The Beatles',
-... 'choices-1-votes': u'51',
-... 'choices-2-choice': u'',
-... 'choices-2-votes': u'',
-... }
-
-
->>> form_set = formsets.FormSet(ChoiceForm, data, auto_id=False, prefix='choices')
->>> print form_set.is_valid()
-True
->>> for data in form_set.clean_data:
-... print data
-{'votes': 50, 'choice': u'The Doors'}
-{'votes': 51, 'choice': u'The Beatles'}
-
-
-FormSet with deletion fields
-
->>> form_set = formsets.FormSetWithDeletion(ChoiceForm, initial=initial_data, auto_id=False, prefix='choices')
->>> for form in form_set.get_forms(): # print pre-filled forms
-... print form.as_ul()
-
Choice:
-
Votes:
-
Delete:
-
Choice:
-
Votes:
-
Delete:
-
Choice:
-
Votes:
-
Delete:
-
->>> data = {
-... 'choices-COUNT': u'3', # the number of forms rendered
-... 'choices-0-choice': u'Fergie',
-... 'choices-0-votes': u'1000',
-... 'choices-0-DELETE': u'on', # Delete this choice.
-... 'choices-1-choice': u'The Decemberists',
-... 'choices-1-votes': u'150',
-... 'choices-2-choice': u'Calexico',
-... 'choices-2-votes': u'90',
-... }
-
->>> form_set = formsets.FormSetWithDeletion(ChoiceForm, data, auto_id=False, prefix='choices')
->>> print form_set.is_valid()
-True
-
-When we access form_set.clean_data, items marked for deletion won't be there,
-but they *will* be in form_set.deleted_data
-
->>> for data in form_set.clean_data:
-... print data
-{'votes': 150, 'DELETE': False, 'choice': u'The Decemberists'}
-{'votes': 90, 'DELETE': False, 'choice': u'Calexico'}
-
->>> for data in form_set.deleted_data:
-... print data
-{'votes': 1000, 'DELETE': True, 'choice': u'Fergie'}
-
-
-FormSet with Ordering
-
->>> form_set = formsets.FormSetWithOrdering(ChoiceForm, initial=initial_data, auto_id=False, prefix='choices')
->>> for form in form_set.get_forms(): # print pre-filled forms
-... print form.as_ul()
-
Choice:
-
Votes:
-
Order:
-
Choice:
-
Votes:
-
Order:
-
Choice:
-
Votes:
-
Order:
-
->>> data = {
-... 'choices-COUNT': u'4', # the number of forms rendered
-... 'choices-0-choice': u'Fergie',
-... 'choices-0-votes': u'1000',
-... 'choices-0-ORDER': u'3',
-... 'choices-1-choice': u'The Decemberists',
-... 'choices-1-votes': u'150',
-... 'choices-1-ORDER': u'1',
-... 'choices-2-choice': u'Calexico',
-... 'choices-2-votes': u'90',
-... 'choices-2-ORDER': u'2',
-... 'choices-3-choice': u'',
-... 'choices-3-votes': u'',
-... 'choices-3-ORDER': u'4',
-... }
-
->>> form_set = formsets.FormSetWithOrdering(ChoiceForm, data, auto_id=False, prefix='choices')
->>> print form_set.is_valid()
-True
-
-The form_set.clean_data will be in the correct order as specified by the
-ORDER field from each form.
-
->>> for data in form_set.clean_data:
-... print data
-{'votes': 150, 'ORDER': 1, 'choice': u'The Decemberists'}
-{'votes': 90, 'ORDER': 2, 'choice': u'Calexico'}
-{'votes': 1000, 'ORDER': 3, 'choice': u'Fergie'}
-
-
# Forms with NullBooleanFields ################################################
NullBooleanField is a bit of a special case because its presentation (widget)
@@ -3451,6 +3300,7 @@ __test__ = {
'form_tests': form_tests,
'localflavor': localflavor_tests,
'regressions': regression_tests,
+ 'formset_tests': formset_tests,
}
if __name__ == "__main__":