Fixed #5524 -- Do not remove cleaned_data when a form fails validation
cleaned_data is no longer deleted when form validation fails but only contains the data that did validate. Thanks to the various contributors to this patch (see ticket).
This commit is contained in:
parent
10f979fd92
commit
121fd109de
|
@ -317,7 +317,7 @@ class WizardTests(TestCase):
|
||||||
|
|
||||||
class WizardWithProcessStep(TestWizardClass):
|
class WizardWithProcessStep(TestWizardClass):
|
||||||
def process_step(self, request, form, step):
|
def process_step(self, request, form, step):
|
||||||
that.assertTrue(hasattr(form, 'cleaned_data'))
|
that.assertTrue(form.is_valid())
|
||||||
reached[0] = True
|
reached[0] = True
|
||||||
|
|
||||||
wizard = WizardWithProcessStep([WizardPageOneForm,
|
wizard = WizardWithProcessStep([WizardPageOneForm,
|
||||||
|
|
|
@ -271,8 +271,6 @@ class BaseForm(StrAndUnicode):
|
||||||
self._clean_fields()
|
self._clean_fields()
|
||||||
self._clean_form()
|
self._clean_form()
|
||||||
self._post_clean()
|
self._post_clean()
|
||||||
if self._errors:
|
|
||||||
del self.cleaned_data
|
|
||||||
|
|
||||||
def _clean_fields(self):
|
def _clean_fields(self):
|
||||||
for name, field in self.fields.items():
|
for name, field in self.fields.items():
|
||||||
|
|
|
@ -506,7 +506,7 @@ class BaseModelFormSet(BaseFormSet):
|
||||||
all_unique_checks = set()
|
all_unique_checks = set()
|
||||||
all_date_checks = set()
|
all_date_checks = set()
|
||||||
for form in self.forms:
|
for form in self.forms:
|
||||||
if not hasattr(form, 'cleaned_data'):
|
if not form.is_valid():
|
||||||
continue
|
continue
|
||||||
exclude = form._get_validation_exclusions()
|
exclude = form._get_validation_exclusions()
|
||||||
unique_checks, date_checks = form.instance._get_unique_checks(exclude=exclude)
|
unique_checks, date_checks = form.instance._get_unique_checks(exclude=exclude)
|
||||||
|
@ -518,21 +518,21 @@ class BaseModelFormSet(BaseFormSet):
|
||||||
for uclass, unique_check in all_unique_checks:
|
for uclass, unique_check in all_unique_checks:
|
||||||
seen_data = set()
|
seen_data = set()
|
||||||
for form in self.forms:
|
for form in self.forms:
|
||||||
# if the form doesn't have cleaned_data then we ignore it,
|
if not form.is_valid():
|
||||||
# it's already invalid
|
|
||||||
if not hasattr(form, "cleaned_data"):
|
|
||||||
continue
|
continue
|
||||||
# get data for each field of each of unique_check
|
# get data for each field of each of unique_check
|
||||||
row_data = tuple([form.cleaned_data[field] for field in unique_check if field in form.cleaned_data])
|
row_data = tuple([form.cleaned_data[field] for field in unique_check if field in form.cleaned_data])
|
||||||
if row_data and not None in row_data:
|
if row_data and not None in row_data:
|
||||||
# if we've aready seen it then we have a uniqueness failure
|
# if we've already seen it then we have a uniqueness failure
|
||||||
if row_data in seen_data:
|
if row_data in seen_data:
|
||||||
# poke error messages into the right places and mark
|
# poke error messages into the right places and mark
|
||||||
# the form as invalid
|
# the form as invalid
|
||||||
errors.append(self.get_unique_error_message(unique_check))
|
errors.append(self.get_unique_error_message(unique_check))
|
||||||
form._errors[NON_FIELD_ERRORS] = self.error_class([self.get_form_error()])
|
form._errors[NON_FIELD_ERRORS] = self.error_class([self.get_form_error()])
|
||||||
del form.cleaned_data
|
# remove the data from the cleaned_data dict since it was invalid
|
||||||
break
|
for field in unique_check:
|
||||||
|
if field in form.cleaned_data:
|
||||||
|
del form.cleaned_data[field]
|
||||||
# mark the data as seen
|
# mark the data as seen
|
||||||
seen_data.add(row_data)
|
seen_data.add(row_data)
|
||||||
# iterate over each of the date checks now
|
# iterate over each of the date checks now
|
||||||
|
@ -540,9 +540,7 @@ class BaseModelFormSet(BaseFormSet):
|
||||||
seen_data = set()
|
seen_data = set()
|
||||||
uclass, lookup, field, unique_for = date_check
|
uclass, lookup, field, unique_for = date_check
|
||||||
for form in self.forms:
|
for form in self.forms:
|
||||||
# if the form doesn't have cleaned_data then we ignore it,
|
if not form.is_valid():
|
||||||
# it's already invalid
|
|
||||||
if not hasattr(self, 'cleaned_data'):
|
|
||||||
continue
|
continue
|
||||||
# see if we have data for both fields
|
# see if we have data for both fields
|
||||||
if (form.cleaned_data and form.cleaned_data[field] is not None
|
if (form.cleaned_data and form.cleaned_data[field] is not None
|
||||||
|
@ -556,14 +554,15 @@ class BaseModelFormSet(BaseFormSet):
|
||||||
else:
|
else:
|
||||||
date_data = (getattr(form.cleaned_data[unique_for], lookup),)
|
date_data = (getattr(form.cleaned_data[unique_for], lookup),)
|
||||||
data = (form.cleaned_data[field],) + date_data
|
data = (form.cleaned_data[field],) + date_data
|
||||||
# if we've aready seen it then we have a uniqueness failure
|
# if we've already seen it then we have a uniqueness failure
|
||||||
if data in seen_data:
|
if data in seen_data:
|
||||||
# poke error messages into the right places and mark
|
# poke error messages into the right places and mark
|
||||||
# the form as invalid
|
# the form as invalid
|
||||||
errors.append(self.get_date_error_message(date_check))
|
errors.append(self.get_date_error_message(date_check))
|
||||||
form._errors[NON_FIELD_ERRORS] = self.error_class([self.get_form_error()])
|
form._errors[NON_FIELD_ERRORS] = self.error_class([self.get_form_error()])
|
||||||
del form.cleaned_data
|
# remove the data from the cleaned_data dict since it was invalid
|
||||||
break
|
del form.cleaned_data[field]
|
||||||
|
# mark the data as seen
|
||||||
seen_data.add(data)
|
seen_data.add(data)
|
||||||
if errors:
|
if errors:
|
||||||
raise ValidationError(errors)
|
raise ValidationError(errors)
|
||||||
|
|
|
@ -199,8 +199,8 @@ Note that any text-based field -- such as ``CharField`` or ``EmailField`` --
|
||||||
always cleans the input into a Unicode string. We'll cover the encoding
|
always cleans the input into a Unicode string. We'll cover the encoding
|
||||||
implications later in this document.
|
implications later in this document.
|
||||||
|
|
||||||
If your data does *not* validate, your ``Form`` instance will not have a
|
If your data does *not* validate, the ``cleaned_data`` dictionary contains
|
||||||
``cleaned_data`` attribute::
|
only the valid fields::
|
||||||
|
|
||||||
>>> data = {'subject': '',
|
>>> data = {'subject': '',
|
||||||
... 'message': 'Hi there',
|
... 'message': 'Hi there',
|
||||||
|
@ -210,9 +210,12 @@ If your data does *not* validate, your ``Form`` instance will not have a
|
||||||
>>> f.is_valid()
|
>>> f.is_valid()
|
||||||
False
|
False
|
||||||
>>> f.cleaned_data
|
>>> f.cleaned_data
|
||||||
Traceback (most recent call last):
|
{'cc_myself': True, 'message': u'Hi there'}
|
||||||
...
|
|
||||||
AttributeError: 'ContactForm' object has no attribute 'cleaned_data'
|
.. versionchanged:: 1.5
|
||||||
|
|
||||||
|
Until Django 1.5, the ``cleaned_data`` attribute wasn't defined at all when
|
||||||
|
the ``Form`` didn't validate.
|
||||||
|
|
||||||
``cleaned_data`` will always *only* contain a key for fields defined in the
|
``cleaned_data`` will always *only* contain a key for fields defined in the
|
||||||
``Form``, even if you pass extra data when you define the ``Form``. In this
|
``Form``, even if you pass extra data when you define the ``Form``. In this
|
||||||
|
@ -232,9 +235,9 @@ but ``cleaned_data`` contains only the form's fields::
|
||||||
>>> f.cleaned_data # Doesn't contain extra_field_1, etc.
|
>>> f.cleaned_data # Doesn't contain extra_field_1, etc.
|
||||||
{'cc_myself': True, 'message': u'Hi there', 'sender': u'foo@example.com', 'subject': u'hello'}
|
{'cc_myself': True, 'message': u'Hi there', 'sender': u'foo@example.com', 'subject': u'hello'}
|
||||||
|
|
||||||
``cleaned_data`` will include a key and value for *all* fields defined in the
|
When the ``Form`` is valid, ``cleaned_data`` will include a key and value for
|
||||||
``Form``, even if the data didn't include a value for fields that are not
|
*all* its fields, even if the data didn't include a value for some optional
|
||||||
required. In this example, the data dictionary doesn't include a value for the
|
fields. In this example, the data dictionary doesn't include a value for the
|
||||||
``nick_name`` field, but ``cleaned_data`` includes it, with an empty value::
|
``nick_name`` field, but ``cleaned_data`` includes it, with an empty value::
|
||||||
|
|
||||||
>>> class OptionalPersonForm(Form):
|
>>> class OptionalPersonForm(Form):
|
||||||
|
|
|
@ -362,7 +362,9 @@ Secondly, once we have decided that the combined data in the two fields we are
|
||||||
considering aren't valid, we must remember to remove them from the
|
considering aren't valid, we must remember to remove them from the
|
||||||
``cleaned_data``.
|
``cleaned_data``.
|
||||||
|
|
||||||
In fact, Django will currently completely wipe out the ``cleaned_data``
|
.. versionchanged:: 1.5
|
||||||
dictionary if there are any errors in the form. However, this behavior may
|
|
||||||
change in the future, so it's not a bad idea to clean up after yourself in the
|
Django used to remove the ``cleaned_data`` attribute entirely if there were
|
||||||
first place.
|
any errors in the form. Since version 1.5, ``cleaned_data`` is present even if
|
||||||
|
the form doesn't validate, but it contains only field values that did
|
||||||
|
validate.
|
||||||
|
|
|
@ -239,6 +239,16 @@ database state behind or unit tests that rely on some form of state being
|
||||||
preserved after the execution of other tests. Such tests are already very
|
preserved after the execution of other tests. Such tests are already very
|
||||||
fragile, and must now be changed to be able to run independently.
|
fragile, and must now be changed to be able to run independently.
|
||||||
|
|
||||||
|
`cleaned_data` dictionary kept for invalid forms
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The :attr:`~django.forms.Form.cleaned_data` dictionary is now always present
|
||||||
|
after form validation. When the form doesn't validate, it contains only the
|
||||||
|
fields that passed validation. You should test the success of the validation
|
||||||
|
with the :meth:`~django.forms.Form.is_valid()` method and not with the
|
||||||
|
presence or absence of the :attr:`~django.forms.Form.cleaned_data` attribute
|
||||||
|
on the form.
|
||||||
|
|
||||||
Miscellaneous
|
Miscellaneous
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -638,8 +638,7 @@ class OldFormForXTests(TestCase):
|
||||||
f = BaseCategoryForm({'name': '', 'slug': 'not a slug!', 'url': 'foo'})
|
f = BaseCategoryForm({'name': '', 'slug': 'not a slug!', 'url': 'foo'})
|
||||||
self.assertEqual(f.errors['name'], ['This field is required.'])
|
self.assertEqual(f.errors['name'], ['This field is required.'])
|
||||||
self.assertEqual(f.errors['slug'], ["Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."])
|
self.assertEqual(f.errors['slug'], ["Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."])
|
||||||
with self.assertRaises(AttributeError):
|
self.assertEqual(f.cleaned_data, {'url': 'foo'})
|
||||||
f.cleaned_data
|
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
f.save()
|
f.save()
|
||||||
f = BaseCategoryForm({'name': '', 'slug': '', 'url': 'foo'})
|
f = BaseCategoryForm({'name': '', 'slug': '', 'url': 'foo'})
|
||||||
|
|
|
@ -82,11 +82,7 @@ class FormsTestCase(TestCase):
|
||||||
self.assertEqual(p.errors['last_name'], ['This field is required.'])
|
self.assertEqual(p.errors['last_name'], ['This field is required.'])
|
||||||
self.assertEqual(p.errors['birthday'], ['This field is required.'])
|
self.assertEqual(p.errors['birthday'], ['This field is required.'])
|
||||||
self.assertFalse(p.is_valid())
|
self.assertFalse(p.is_valid())
|
||||||
try:
|
self.assertEqual(p.cleaned_data, {})
|
||||||
p.cleaned_data
|
|
||||||
self.fail('Attempts to access cleaned_data when validation fails should fail.')
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
self.assertHTMLEqual(str(p), """<tr><th><label for="id_first_name">First name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="first_name" id="id_first_name" /></td></tr>
|
self.assertHTMLEqual(str(p), """<tr><th><label for="id_first_name">First name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="first_name" id="id_first_name" /></td></tr>
|
||||||
<tr><th><label for="id_last_name">Last name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="last_name" id="id_last_name" /></td></tr>
|
<tr><th><label for="id_last_name">Last name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="last_name" id="id_last_name" /></td></tr>
|
||||||
<tr><th><label for="id_birthday">Birthday:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="birthday" id="id_birthday" /></td></tr>""")
|
<tr><th><label for="id_birthday">Birthday:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="birthday" id="id_birthday" /></td></tr>""")
|
||||||
|
@ -145,11 +141,7 @@ class FormsTestCase(TestCase):
|
||||||
* This field is required.
|
* This field is required.
|
||||||
* birthday
|
* birthday
|
||||||
* This field is required.""")
|
* This field is required.""")
|
||||||
try:
|
self.assertEqual(p.cleaned_data, {'last_name': 'Lennon'})
|
||||||
p.cleaned_data
|
|
||||||
self.fail('Attempts to access cleaned_data when validation fails should fail.')
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
self.assertEqual(p['first_name'].errors, ['This field is required.'])
|
self.assertEqual(p['first_name'].errors, ['This field is required.'])
|
||||||
self.assertHTMLEqual(p['first_name'].errors.as_ul(), '<ul class="errorlist"><li>This field is required.</li></ul>')
|
self.assertHTMLEqual(p['first_name'].errors.as_ul(), '<ul class="errorlist"><li>This field is required.</li></ul>')
|
||||||
self.assertEqual(p['first_name'].errors.as_text(), '* This field is required.')
|
self.assertEqual(p['first_name'].errors.as_text(), '* This field is required.')
|
||||||
|
@ -1678,11 +1670,7 @@ class FormsTestCase(TestCase):
|
||||||
form = SongForm(data, empty_permitted=False)
|
form = SongForm(data, empty_permitted=False)
|
||||||
self.assertFalse(form.is_valid())
|
self.assertFalse(form.is_valid())
|
||||||
self.assertEqual(form.errors, {'name': ['This field is required.'], 'artist': ['This field is required.']})
|
self.assertEqual(form.errors, {'name': ['This field is required.'], 'artist': ['This field is required.']})
|
||||||
try:
|
self.assertEqual(form.cleaned_data, {})
|
||||||
form.cleaned_data
|
|
||||||
self.fail('Attempts to access cleaned_data when validation fails should fail.')
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Now let's show what happens when empty_permitted=True and the form is empty.
|
# Now let's show what happens when empty_permitted=True and the form is empty.
|
||||||
form = SongForm(data, empty_permitted=True)
|
form = SongForm(data, empty_permitted=True)
|
||||||
|
@ -1696,11 +1684,7 @@ class FormsTestCase(TestCase):
|
||||||
form = SongForm(data, empty_permitted=False)
|
form = SongForm(data, empty_permitted=False)
|
||||||
self.assertFalse(form.is_valid())
|
self.assertFalse(form.is_valid())
|
||||||
self.assertEqual(form.errors, {'name': ['This field is required.']})
|
self.assertEqual(form.errors, {'name': ['This field is required.']})
|
||||||
try:
|
self.assertEqual(form.cleaned_data, {'artist': 'The Doors'})
|
||||||
form.cleaned_data
|
|
||||||
self.fail('Attempts to access cleaned_data when validation fails should fail.')
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# If a field is not given in the data then None is returned for its data. Lets
|
# If a field is not given in the data then None is returned for its data. Lets
|
||||||
# make sure that when checking for empty_permitted that None is treated
|
# make sure that when checking for empty_permitted that None is treated
|
||||||
|
|
Loading…
Reference in New Issue