diff --git a/django/forms/formsets.py b/django/forms/formsets.py index 955b41a437..3421b56e8c 100644 --- a/django/forms/formsets.py +++ b/django/forms/formsets.py @@ -4,7 +4,7 @@ from django.utils.safestring import mark_safe from django.utils.translation import ugettext as _ from fields import IntegerField, BooleanField from widgets import Media, HiddenInput -from util import ErrorList, ValidationError +from util import ErrorList, ErrorDict, ValidationError __all__ = ('BaseFormSet', 'all_valid') @@ -152,7 +152,7 @@ class BaseFormSet(StrAndUnicode): def _get_ordered_forms(self): """ Returns a list of form in the order specified by the incoming data. - Raises an AttributeError if deletion is not allowed. + Raises an AttributeError if ordering is not allowed. """ if not self.is_valid() or not self.can_order: raise AttributeError("'%s' object has no attribute 'ordered_forms'" % self.__class__.__name__) @@ -221,8 +221,21 @@ class BaseFormSet(StrAndUnicode): # We loop over every form.errors here rather than short circuiting on the # first failure to make sure validation gets triggered for every form. forms_valid = True - for errors in self.errors: - if bool(errors): + for i in range(0, self.total_form_count()): + form = self.forms[i] + if self.can_delete: + # The way we lookup the value of the deletion field here takes + # more code than we'd like, but the form's cleaned_data will + # not exist if the form is invalid. + field = form.fields[DELETION_FIELD_NAME] + prefix = form.add_prefix(DELETION_FIELD_NAME) + value = field.widget.value_from_datadict(self.data, self.files, prefix) + should_delete = field.clean(value) + if should_delete: + # This form is going to be deleted so any of its errors + # should not cause the entire formset to be invalid. + continue + if bool(self.errors[i]): forms_valid = False return forms_valid and not bool(self.non_form_errors()) diff --git a/tests/regressiontests/forms/formsets.py b/tests/regressiontests/forms/formsets.py index 4fd0c17032..c3c89d078d 100644 --- a/tests/regressiontests/forms/formsets.py +++ b/tests/regressiontests/forms/formsets.py @@ -241,7 +241,7 @@ data. # FormSets with deletion ###################################################### -We can easily add deletion ability to a FormSet with an agrument to +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 @@ -286,6 +286,34 @@ True >>> [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-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 # FormSets with ordering ######################################################