diff -r d0dc9aaff6af django/forms/forms.py
--- a/django/forms/forms.py	Sat Dec 12 22:04:04 2009 +0000
+++ b/django/forms/forms.py	Sun Dec 13 15:37:52 2009 -0500
@@ -286,8 +286,6 @@
             self.cleaned_data = self.clean()
         except ValidationError, e:
             self._errors[NON_FIELD_ERRORS] = self.error_class(e.messages)
-        if self._errors:
-            delattr(self, 'cleaned_data')
 
     def clean(self):
         """
diff -r d0dc9aaff6af django/forms/formsets.py
--- a/django/forms/formsets.py	Sat Dec 12 22:04:04 2009 +0000
+++ b/django/forms/formsets.py	Sun Dec 13 15:37:52 2009 -0500
@@ -144,7 +144,10 @@
                 # if this is an extra form and hasn't changed, don't consider it
                 if i >= self.initial_form_count() and not form.has_changed():
                     continue
-                if form.cleaned_data[DELETION_FIELD_NAME]:
+                field = form.fields[DELETION_FIELD_NAME]
+                raw_value = form._raw_value(DELETION_FIELD_NAME)
+                should_delete = field.clean(raw_value)
+                if should_delete:
                     self._deleted_form_indexes.append(i)
         return [self.forms[i] for i in self._deleted_form_indexes]
     deleted_forms = property(_get_deleted_forms)
@@ -231,6 +234,8 @@
                 raw_value = form._raw_value(DELETION_FIELD_NAME)
                 should_delete = field.clean(raw_value)
                 if should_delete:
+                    # clean form anyways to populate cleaned_data
+                    form.full_clean()
                     # This form is going to be deleted so any of its errors
                     # should not cause the entire formset to be invalid.
                     continue
diff -r d0dc9aaff6af tests/modeltests/model_forms/models.py
--- a/tests/modeltests/model_forms/models.py	Sat Dec 12 22:04:04 2009 +0000
+++ b/tests/modeltests/model_forms/models.py	Sun Dec 13 15:37:52 2009 -0500
@@ -453,9 +453,7 @@
 >>> f.errors['slug']
 [u"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."]
 >>> f.cleaned_data
-Traceback (most recent call last):
-...
-AttributeError: 'CategoryForm' object has no attribute 'cleaned_data'
+{'url': u'foo'}
 >>> f.save()
 Traceback (most recent call last):
 ...
diff -r d0dc9aaff6af tests/regressiontests/forms/forms.py
--- a/tests/regressiontests/forms/forms.py	Sat Dec 12 22:04:04 2009 +0000
+++ b/tests/regressiontests/forms/forms.py	Sun Dec 13 15:37:52 2009 -0500
@@ -78,9 +78,7 @@
 >>> p.is_valid()
 False
 >>> p.cleaned_data
-Traceback (most recent call last):
-...
-AttributeError: 'Person' object has no attribute 'cleaned_data'
+{}
 >>> print 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>
@@ -156,9 +154,7 @@
 * birthday
   * This field is required.
 >>> p.cleaned_data
-Traceback (most recent call last):
-...
-AttributeError: 'Person' object has no attribute 'cleaned_data'
+{'last_name': u'Lennon'}
 >>> p['first_name'].errors
 [u'This field is required.']
 >>> p['first_name'].errors.as_ul()
@@ -1745,10 +1741,7 @@
 >>> form.errors
 {'name': [u'This field is required.'], 'artist': [u'This field is required.']}
 >>> form.cleaned_data
-Traceback (most recent call last):
-...
-AttributeError: 'SongForm' object has no attribute 'cleaned_data'
-
+{}
 
 Now let's show what happens when empty_permitted=True and the form is empty.
 
@@ -1770,9 +1763,7 @@
 >>> form.errors
 {'name': [u'This field is required.']}
 >>> form.cleaned_data
-Traceback (most recent call last):
-...
-AttributeError: 'SongForm' object has no attribute '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
diff -r d0dc9aaff6af tests/regressiontests/forms/formsets.py
--- a/tests/regressiontests/forms/formsets.py	Sat Dec 12 22:04:04 2009 +0000
+++ b/tests/regressiontests/forms/formsets.py	Sun Dec 13 15:37:52 2009 -0500
@@ -308,6 +308,24 @@
 >>> formset.is_valid()
 True
 
+The formset is valid (due to short circuiting forms marked for deletion), but
+not all forms are valid:
+
+>>> [form.is_valid() for form in formset.forms]
+[True, False, True]
+
+But we can still access the cleaned_data attribute for each form:
+
+>>> [form.cleaned_data for form in formset.forms]
+[{'field': 200, 'DELETE': False}, {'DELETE': True}, {}]
+
+And access the deleted form:
+
+>>> len(formset.deleted_forms)
+1
+>>> formset._deleted_form_indexes
+[1]
+
 If we remove the deletion flag now we will have our validation back.
 
 >>> data['check-1-DELETE'] = ''
