diff --git a/django/forms/forms.py b/django/forms/forms.py
index d3f2046..5d58e7f 100644
|
a
|
b
|
class BaseForm(StrAndUnicode):
|
| 270 | 270 | self._clean_fields() |
| 271 | 271 | self._clean_form() |
| 272 | 272 | self._post_clean() |
| 273 | | if self._errors: |
| 274 | | del self.cleaned_data |
| 275 | 273 | |
| 276 | 274 | def _clean_fields(self): |
| 277 | 275 | for name, field in self.fields.items(): |
diff --git a/django/forms/models.py b/django/forms/models.py
index b65f067..3f0deb7 100644
|
a
|
b
|
class BaseModelFormSet(BaseFormSet):
|
| 497 | 497 | all_unique_checks = set() |
| 498 | 498 | all_date_checks = set() |
| 499 | 499 | for form in self.forms: |
| 500 | | if not hasattr(form, 'cleaned_data'): |
| | 500 | if not form.is_valid(): |
| 501 | 501 | continue |
| 502 | 502 | exclude = form._get_validation_exclusions() |
| 503 | 503 | unique_checks, date_checks = form.instance._get_unique_checks(exclude=exclude) |
| … |
… |
class BaseModelFormSet(BaseFormSet):
|
| 509 | 509 | for uclass, unique_check in all_unique_checks: |
| 510 | 510 | seen_data = set() |
| 511 | 511 | for form in self.forms: |
| 512 | | # if the form doesn't have cleaned_data then we ignore it, |
| 513 | | # it's already invalid |
| 514 | | if not hasattr(form, "cleaned_data"): |
| | 512 | if not form.is_valid(): |
| 515 | 513 | continue |
| 516 | 514 | # get data for each field of each of unique_check |
| 517 | 515 | row_data = tuple([form.cleaned_data[field] for field in unique_check if field in form.cleaned_data]) |
| … |
… |
class BaseModelFormSet(BaseFormSet):
|
| 531 | 529 | seen_data = set() |
| 532 | 530 | uclass, lookup, field, unique_for = date_check |
| 533 | 531 | for form in self.forms: |
| 534 | | # if the form doesn't have cleaned_data then we ignore it, |
| 535 | | # it's already invalid |
| 536 | | if not hasattr(self, 'cleaned_data'): |
| | 532 | if not form.is_valid(): |
| 537 | 533 | continue |
| 538 | 534 | # see if we have data for both fields |
| 539 | 535 | if (form.cleaned_data and form.cleaned_data[field] is not None |
diff --git a/docs/ref/forms/validation.txt b/docs/ref/forms/validation.txt
index 7657353..0f7e57f 100644
|
a
|
b
|
Secondly, once we have decided that the combined data in the two fields we are
|
| 360 | 360 | considering aren't valid, we must remember to remove them from the |
| 361 | 361 | ``cleaned_data``. |
| 362 | 362 | |
| 363 | | In fact, Django will currently completely wipe out the ``cleaned_data`` |
| 364 | | dictionary if there are any errors in the form. However, this behavior may |
| 365 | | change in the future, so it's not a bad idea to clean up after yourself in the |
| 366 | | first place. |
| | 363 | Before 1.4, Django used to completely wipe out the ``cleaned_data`` |
| | 364 | dictionary if there were any errors in the form. |
| | 365 | From 1.4, the ``cleaned_data`` is present even if the form doesn't validate, |
| | 366 | but it contains only field values that did validate (empty dictionary if none |
| | 367 | validated). |
diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt
index 7ffc1aa..e905631 100644
|
a
|
b
|
whose primary use is to load fixtures consisting of simple objects. Even though
|
| 840 | 840 | fixtures are trusted data, for additional security, the YAML deserializer now |
| 841 | 841 | uses ``yaml.safe_load``. |
| 842 | 842 | |
| | 843 | `cleaned_data` dictionary not wiped any more for invalid forms |
| | 844 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| | 845 | |
| | 846 | The ``cleaned_data`` dictionary for forms is now always present after |
| | 847 | validation, regardless of the success or the failure of the validation process. |
| | 848 | You should always test the success of the validation with the ``is_valid()`` |
| | 849 | method, and not with the presence or absence of the ``cleaned_data`` property on |
| | 850 | the form. |
| | 851 | |
| 843 | 852 | .. _deprecated-features-1.4: |
| 844 | 853 | |
| 845 | 854 | Features deprecated in 1.4 |
diff --git a/tests/modeltests/model_forms/tests.py b/tests/modeltests/model_forms/tests.py
index c4abdbb..f95d365 100644
|
a
|
b
|
class OldFormForXTests(TestCase):
|
| 637 | 637 | f = BaseCategoryForm({'name': '', 'slug': 'not a slug!', 'url': 'foo'}) |
| 638 | 638 | self.assertEqual(f.errors['name'], [u'This field is required.']) |
| 639 | 639 | self.assertEqual(f.errors['slug'], [u"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."]) |
| 640 | | with self.assertRaises(AttributeError): |
| 641 | | f.cleaned_data |
| | 640 | self.assertEqual(f.cleaned_data, {'url': u'foo'}) |
| 642 | 641 | with self.assertRaises(ValueError): |
| 643 | 642 | f.save() |
| 644 | 643 | f = BaseCategoryForm({'name': '', 'slug': '', 'url': 'foo'}) |
diff --git a/tests/regressiontests/forms/tests/forms.py b/tests/regressiontests/forms/tests/forms.py
index 4e9cc16..4c14ca6 100644
|
a
|
b
|
class FormsTestCase(TestCase):
|
| 79 | 79 | self.assertEqual(p.errors['last_name'], [u'This field is required.']) |
| 80 | 80 | self.assertEqual(p.errors['birthday'], [u'This field is required.']) |
| 81 | 81 | self.assertFalse(p.is_valid()) |
| 82 | | try: |
| 83 | | p.cleaned_data |
| 84 | | self.fail('Attempts to access cleaned_data when validation fails should fail.') |
| 85 | | except AttributeError: |
| 86 | | pass |
| | 82 | self.assertEqual(p.cleaned_data, {}) |
| 87 | 83 | self.assertEqual(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> |
| 88 | 84 | <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> |
| 89 | 85 | <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>""") |
| … |
… |
class FormsTestCase(TestCase):
|
| 142 | 138 | * This field is required. |
| 143 | 139 | * birthday |
| 144 | 140 | * This field is required.""") |
| 145 | | try: |
| 146 | | p.cleaned_data |
| 147 | | self.fail('Attempts to access cleaned_data when validation fails should fail.') |
| 148 | | except AttributeError: |
| 149 | | pass |
| | 141 | self.assertEqual(p.cleaned_data, {'last_name': u'Lennon'}) |
| 150 | 142 | self.assertEqual(p['first_name'].errors, [u'This field is required.']) |
| 151 | 143 | self.assertEqual(p['first_name'].errors.as_ul(), u'<ul class="errorlist"><li>This field is required.</li></ul>') |
| 152 | 144 | self.assertEqual(p['first_name'].errors.as_text(), u'* This field is required.') |
| … |
… |
class FormsTestCase(TestCase):
|
| 1672 | 1664 | form = SongForm(data, empty_permitted=False) |
| 1673 | 1665 | self.assertFalse(form.is_valid()) |
| 1674 | 1666 | self.assertEqual(form.errors, {'name': [u'This field is required.'], 'artist': [u'This field is required.']}) |
| 1675 | | try: |
| 1676 | | form.cleaned_data |
| 1677 | | self.fail('Attempts to access cleaned_data when validation fails should fail.') |
| 1678 | | except AttributeError: |
| 1679 | | pass |
| | 1667 | self.assertEqual(form.cleaned_data, {}) |
| 1680 | 1668 | |
| 1681 | 1669 | # Now let's show what happens when empty_permitted=True and the form is empty. |
| 1682 | 1670 | form = SongForm(data, empty_permitted=True) |
| … |
… |
class FormsTestCase(TestCase):
|
| 1690 | 1678 | form = SongForm(data, empty_permitted=False) |
| 1691 | 1679 | self.assertFalse(form.is_valid()) |
| 1692 | 1680 | self.assertEqual(form.errors, {'name': [u'This field is required.']}) |
| 1693 | | try: |
| 1694 | | form.cleaned_data |
| 1695 | | self.fail('Attempts to access cleaned_data when validation fails should fail.') |
| 1696 | | except AttributeError: |
| 1697 | | pass |
| | 1681 | self.assertEqual(form.cleaned_data, {'artist': u'The Doors'}) |
| 1698 | 1682 | |
| 1699 | 1683 | # If a field is not given in the data then None is returned for its data. Lets |
| 1700 | 1684 | # make sure that when checking for empty_permitted that None is treated |