mirror of
https://github.com/django/django.git
synced 2025-07-04 01:39:20 +00:00
newforms-admin: Fixed #5353. Added FormSet validation hook. Separated a few things out from the original patch and added more tests. Thanks, Honza Kral.
git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@6419 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
9687447e31
commit
b40f9b63bb
@ -1,6 +1,7 @@
|
|||||||
from forms import Form, ValidationError
|
from forms import Form
|
||||||
from fields import IntegerField, BooleanField
|
from fields import IntegerField, BooleanField
|
||||||
from widgets import HiddenInput, Media
|
from widgets import HiddenInput, Media
|
||||||
|
from util import ErrorList, ValidationError
|
||||||
|
|
||||||
__all__ = ('BaseFormSet', 'formset_for_form', 'all_valid')
|
__all__ = ('BaseFormSet', 'formset_for_form', 'all_valid')
|
||||||
|
|
||||||
@ -22,13 +23,15 @@ class ManagementForm(Form):
|
|||||||
class BaseFormSet(object):
|
class BaseFormSet(object):
|
||||||
"""A collection of instances of the same Form class."""
|
"""A collection of instances of the same Form class."""
|
||||||
|
|
||||||
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, initial=None):
|
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
|
||||||
|
initial=None, error_class=ErrorList):
|
||||||
self.is_bound = data is not None or files is not None
|
self.is_bound = data is not None or files is not None
|
||||||
self.prefix = prefix or 'form'
|
self.prefix = prefix or 'form'
|
||||||
self.auto_id = auto_id
|
self.auto_id = auto_id
|
||||||
self.data = data
|
self.data = data
|
||||||
self.files = files
|
self.files = files
|
||||||
self.initial = initial
|
self.initial = initial
|
||||||
|
self.error_class = error_class
|
||||||
# initialization is different depending on whether we recieved data, initial, or nothing
|
# initialization is different depending on whether we recieved data, initial, or nothing
|
||||||
if data or files:
|
if data or files:
|
||||||
self.management_form = ManagementForm(data, files, auto_id=self.auto_id, prefix=self.prefix)
|
self.management_form = ManagementForm(data, files, auto_id=self.auto_id, prefix=self.prefix)
|
||||||
@ -92,55 +95,78 @@ class BaseFormSet(object):
|
|||||||
return self.change_forms + self.add_forms
|
return self.change_forms + self.add_forms
|
||||||
forms = property(_forms)
|
forms = property(_forms)
|
||||||
|
|
||||||
|
def non_form_errors(self):
|
||||||
|
"""
|
||||||
|
Returns an ErrorList of errors that aren't associated with a particular
|
||||||
|
form -- i.e., from formset.clean(). Returns an empty ErrorList if there
|
||||||
|
are none.
|
||||||
|
"""
|
||||||
|
if hasattr(self, '_non_form_errors'):
|
||||||
|
return self._non_form_errors
|
||||||
|
return self.error_class()
|
||||||
|
|
||||||
def full_clean(self):
|
def full_clean(self):
|
||||||
"""Cleans all of self.data and populates self.__errors and self.cleaned_data."""
|
"""Cleans all of self.data and populates self.__errors and self.cleaned_data."""
|
||||||
is_valid = True
|
self._is_valid = True # Assume the formset is valid until proven otherwise.
|
||||||
errors = []
|
errors = []
|
||||||
if not self.is_bound: # Stop further processing.
|
if not self.is_bound: # Stop further processing.
|
||||||
self.__errors = errors
|
self.__errors = errors
|
||||||
return
|
return
|
||||||
cleaned_data = []
|
self.cleaned_data = []
|
||||||
deleted_data = []
|
self.deleted_data = []
|
||||||
|
|
||||||
# Process change forms
|
# Process change forms
|
||||||
for form in self.change_forms:
|
for form in self.change_forms:
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]:
|
if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]:
|
||||||
deleted_data.append(form.cleaned_data)
|
self.deleted_data.append(form.cleaned_data)
|
||||||
else:
|
else:
|
||||||
cleaned_data.append(form.cleaned_data)
|
self.cleaned_data.append(form.cleaned_data)
|
||||||
else:
|
else:
|
||||||
is_valid = False
|
self._is_valid = False
|
||||||
errors.append(form.errors)
|
errors.append(form.errors)
|
||||||
|
|
||||||
# Process add forms in reverse so we can easily tell when the remaining
|
# Process add forms in reverse so we can easily tell when the remaining
|
||||||
# ones should be required.
|
# ones should be required.
|
||||||
required = False
|
reamining_forms_required = False
|
||||||
add_errors = []
|
add_errors = []
|
||||||
for i in range(len(self.add_forms)-1, -1, -1):
|
for i in range(len(self.add_forms)-1, -1, -1):
|
||||||
form = self.add_forms[i]
|
form = self.add_forms[i]
|
||||||
# If an add form is empty, reset it so it won't have any errors
|
# If an add form is empty, reset it so it won't have any errors
|
||||||
if form.is_empty([ORDERING_FIELD_NAME]) and not required:
|
if form.is_empty([ORDERING_FIELD_NAME]) and not reamining_forms_required:
|
||||||
form.reset()
|
form.reset()
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
required = True
|
reamining_forms_required = True
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
cleaned_data.append(form.cleaned_data)
|
self.cleaned_data.append(form.cleaned_data)
|
||||||
else:
|
else:
|
||||||
is_valid = False
|
self._is_valid = False
|
||||||
add_errors.append(form.errors)
|
add_errors.append(form.errors)
|
||||||
add_errors.reverse()
|
add_errors.reverse()
|
||||||
errors.extend(add_errors)
|
errors.extend(add_errors)
|
||||||
|
# Sort cleaned_data if the formset is orderable.
|
||||||
if self.orderable:
|
if self.orderable:
|
||||||
cleaned_data.sort(lambda x,y: x[ORDERING_FIELD_NAME] - y[ORDERING_FIELD_NAME])
|
self.cleaned_data.sort(lambda x,y: x[ORDERING_FIELD_NAME] - y[ORDERING_FIELD_NAME])
|
||||||
|
# Give self.clean() a chance to do validation
|
||||||
if is_valid:
|
try:
|
||||||
self.cleaned_data = cleaned_data
|
self.cleaned_data = self.clean()
|
||||||
self.deleted_data = deleted_data
|
except ValidationError, e:
|
||||||
|
self._non_form_errors = e.messages
|
||||||
|
self._is_valid = False
|
||||||
self.errors = errors
|
self.errors = errors
|
||||||
self._is_valid = is_valid
|
# If there were errors, be consistent with forms and remove the
|
||||||
|
# cleaned_data and deleted_data attributes.
|
||||||
|
if not self._is_valid:
|
||||||
|
delattr(self, 'cleaned_data')
|
||||||
|
delattr(self, 'deleted_data')
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
"""
|
||||||
|
Hook for doing any extra formset-wide cleaning after Form.clean() has
|
||||||
|
been called on every form. Any ValidationError raised by this method
|
||||||
|
will not be associated with a particular form; it will be accesible
|
||||||
|
via formset.non_form_errors()
|
||||||
|
"""
|
||||||
|
return self.cleaned_data
|
||||||
|
|
||||||
def add_fields(self, form, index):
|
def add_fields(self, form, index):
|
||||||
"""A hook for adding extra fields on to each form instance."""
|
"""A hook for adding extra fields on to each form instance."""
|
||||||
|
@ -5,8 +5,8 @@ formset_tests = """
|
|||||||
FormSet allows us to use multiple instance of the same form on 1 page. For now,
|
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.
|
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 import Form, CharField, IntegerField, ValidationError
|
||||||
>>> from django.newforms.formsets import formset_for_form
|
>>> from django.newforms.formsets import formset_for_form, BaseFormSet
|
||||||
|
|
||||||
>>> class Choice(Form):
|
>>> class Choice(Form):
|
||||||
... choice = CharField()
|
... choice = CharField()
|
||||||
@ -420,4 +420,65 @@ True
|
|||||||
[{'votes': 900, 'DELETE': True, 'ORDER': 2, 'choice': u'Fergie'}]
|
[{'votes': 900, 'DELETE': True, 'ORDER': 2, 'choice': u'Fergie'}]
|
||||||
|
|
||||||
|
|
||||||
|
# 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 FavoriteDrinksFormSet(BaseFormSet):
|
||||||
|
... form_class = FavoriteDrinkForm
|
||||||
|
... num_extra = 2
|
||||||
|
... orderable = False
|
||||||
|
... deletable = False
|
||||||
|
...
|
||||||
|
... 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'])
|
||||||
|
... return self.cleaned_data
|
||||||
|
...
|
||||||
|
|
||||||
|
We start out with a some duplicate data.
|
||||||
|
|
||||||
|
>>> data = {
|
||||||
|
... 'drinks-COUNT': '2',
|
||||||
|
... '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-COUNT': '2',
|
||||||
|
... '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
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user