diff --git a/django/forms/forms.py b/django/forms/forms.py
index ad398c4..33ce291 100644
--- a/django/forms/forms.py
+++ b/django/forms/forms.py
@@ -267,8 +267,6 @@ class BaseForm(StrAndUnicode):
         self._clean_fields()
         self._clean_form()
         self._post_clean()
-        if self._errors:
-            del self.cleaned_data
 
     def _clean_fields(self):
         for name, field in self.fields.items():
diff --git a/django/forms/models.py b/django/forms/models.py
index 254cca3..38b8b1b 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -496,7 +496,7 @@ class BaseModelFormSet(BaseFormSet):
         all_unique_checks = set()
         all_date_checks = set()
         for form in self.forms:
-            if not hasattr(form, 'cleaned_data'):
+            if not form.is_valid():
                 continue
             exclude = form._get_validation_exclusions()
             unique_checks, date_checks = form.instance._get_unique_checks(exclude=exclude)
@@ -508,9 +508,7 @@ class BaseModelFormSet(BaseFormSet):
         for uclass, unique_check in all_unique_checks:
             seen_data = set()
             for form in self.forms:
-                # if the form doesn't have cleaned_data then we ignore it,
-                # it's already invalid
-                if not hasattr(form, "cleaned_data"):
+                if not form.is_valid():
                     continue
                 # 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])
@@ -530,9 +528,7 @@ class BaseModelFormSet(BaseFormSet):
             seen_data = set()
             uclass, lookup, field, unique_for = date_check
             for form in self.forms:
-                # if the form doesn't have cleaned_data then we ignore it,
-                # it's already invalid
-                if not hasattr(self, 'cleaned_data'):
+                if not form.is_valid():
                     continue
                 # see if we have data for both fields
                 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 5264279..e5a741c 100644
--- a/docs/ref/forms/validation.txt
+++ b/docs/ref/forms/validation.txt
@@ -360,7 +360,8 @@ 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
 ``cleaned_data``.
 
-In fact, Django will currently completely wipe out the ``cleaned_data``
-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
-first place.
+Before 1.4, Django used to completely wipe out the ``cleaned_data``
+dictionary if there were any errors in the form.
+From 1.4, the ``cleaned_data`` is present even if the form doesn't validate,
+but it contains only field values that did validate (empty dictionary if none 
+validated).
diff --git a/tests/modeltests/model_forms/tests.py b/tests/modeltests/model_forms/tests.py
index 09a9d3f..45e15d3 100644
--- a/tests/modeltests/model_forms/tests.py
+++ b/tests/modeltests/model_forms/tests.py
@@ -637,8 +637,7 @@ class OldFormForXTests(TestCase):
         f = BaseCategoryForm({'name': '', 'slug': 'not a slug!', 'url': 'foo'})
         self.assertEqual(f.errors['name'], [u'This field is required.'])
         self.assertEqual(f.errors['slug'], [u"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."])
-        with self.assertRaises(AttributeError):
-            f.cleaned_data
+        self.assertEqual(f.cleaned_data, {'url': u'foo'})
         with self.assertRaises(ValueError):
             f.save()
         f = BaseCategoryForm({'name': '', 'slug': '', 'url': 'foo'})
diff --git a/tests/regressiontests/forms/tests/forms.py b/tests/regressiontests/forms/tests/forms.py
index ed783af..6bbc54e 100644
--- a/tests/regressiontests/forms/tests/forms.py
+++ b/tests/regressiontests/forms/tests/forms.py
@@ -78,11 +78,7 @@ class FormsTestCase(TestCase):
         self.assertEqual(p.errors['last_name'], [u'This field is required.'])
         self.assertEqual(p.errors['birthday'], [u'This field is required.'])
         self.assertFalse(p.is_valid())
-        try:
-            p.cleaned_data
-            self.fail('Attempts to access cleaned_data when validation fails should fail.')
-        except AttributeError:
-            pass
+        self.assertEqual(p.cleaned_data, {})
         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>
 <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>""")
@@ -141,11 +137,7 @@ class FormsTestCase(TestCase):
   * This field is required.
 * birthday
   * This field is required.""")
-        try:
-            p.cleaned_data
-            self.fail('Attempts to access cleaned_data when validation fails should fail.')
-        except AttributeError:
-            pass
+        self.assertEqual(p.cleaned_data, {'last_name': u'Lennon'})
         self.assertEqual(p['first_name'].errors, [u'This field is required.'])
         self.assertEqual(p['first_name'].errors.as_ul(), u'<ul class="errorlist"><li>This field is required.</li></ul>')
         self.assertEqual(p['first_name'].errors.as_text(), u'* This field is required.')
@@ -1636,11 +1628,7 @@ class FormsTestCase(TestCase):
         form = SongForm(data, empty_permitted=False)
         self.assertFalse(form.is_valid())
         self.assertEqual(form.errors, {'name': [u'This field is required.'], 'artist': [u'This field is required.']})
-        try:
-            form.cleaned_data
-            self.fail('Attempts to access cleaned_data when validation fails should fail.')
-        except AttributeError:
-            pass
+        self.assertEqual(form.cleaned_data, {})
 
         # Now let's show what happens when empty_permitted=True and the form is empty.
         form = SongForm(data, empty_permitted=True)
@@ -1654,11 +1642,7 @@ class FormsTestCase(TestCase):
         form = SongForm(data, empty_permitted=False)
         self.assertFalse(form.is_valid())
         self.assertEqual(form.errors, {'name': [u'This field is required.']})
-        try:
-            form.cleaned_data
-            self.fail('Attempts to access cleaned_data when validation fails should fail.')
-        except AttributeError:
-            pass
+        self.assertEqual(form.cleaned_data, {'artist': u'The Doors'})
 
         # 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
