Index: newforms/formsets.py
===================================================================
--- newforms/formsets.py	(revision 6718)
+++ newforms/formsets.py	(working copy)
@@ -116,13 +116,13 @@
         self.deleted_data = []
         # Process change forms
         for form in self.change_forms:
-            if form.is_valid():
-                if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]:
-                    self.deleted_data.append(form.cleaned_data)
+            if form.is_valid() or self.deletable:
+                if self.clean_field(form, DELETION_FIELD_NAME):
+                    self.deleted_data.append({DELETION_FIELD_NAME: True})
+                elif form.is_valid():
+                    self.cleaned_data.append(form.cleaned_data)
                 else:
-                    self.cleaned_data.append(form.cleaned_data)
-            else:
-                self._is_valid = False
+                    self._is_valid = False
             errors.append(form.errors)
         # Process add forms in reverse so we can easily tell when the remaining
         # ones should be required.
@@ -168,6 +168,20 @@
         """
         return self.cleaned_data
 
+    def clean_field(self, form, f):
+        if f in form.fields:
+            field = form.fields.get(f)
+            value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(form.prefix))
+            try:
+                value = field.clean(value)
+                if hasattr(self, 'clean_%s' % f):
+                    value = getattr(self, 'clean_%s' % f)()
+            except ValidationError, e:
+                return False
+            return value
+        else:
+            return False
+                
     def add_fields(self, form, index):
         """A hook for adding extra fields on to each form instance."""
         if self.orderable:
Index: newforms/forms.py
===================================================================
--- newforms/forms.py	(revision 6718)
+++ newforms/forms.py	(working copy)
@@ -189,12 +189,11 @@
         for name, field in self.fields.items():
             if name in exceptions:
                 continue
-            # value_from_datadict() gets the data from the dictionary.
+            # value_from_datadict() gets the data from the data dictionaries.
             # Each widget type knows how to retrieve its own data, because some
             # widgets split data over several HTML fields.
             value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
-            # HACK: ['', ''] and [None, None] deal with SplitDateTimeWidget. This should be more robust.
-            if value not in (None, '', ['', ''], [None, None]):
+            if not field.widget.is_empty(value):
                 return False
         return True
 
Index: newforms/widgets.py
===================================================================
--- newforms/widgets.py	(revision 6718)
+++ newforms/widgets.py	(working copy)
@@ -164,6 +164,15 @@
         of this widget. Returns None if it's not provided.
         """
         return data.get(name, None)
+    
+    def is_empty(self, value):
+        """
+        Given a dictionary of data and this widget's name, return True if the
+        widget data is empty or False when not empty.
+        """
+        if value not in (None, ''):
+            return False
+        return True
 
     def id_for_label(self, id_):
         """
@@ -294,6 +303,11 @@
             # send results for unselected checkboxes.
             return False
         return super(CheckboxInput, self).value_from_datadict(data, files, name)
+    
+    def is_empty(self, value):
+        # this widget will always either be True or False, so always return the
+        # opposite value so False values will make the form empty
+        return not value
 
 class Select(Widget):
     def __init__(self, attrs=None, choices=()):
@@ -333,6 +347,12 @@
     def value_from_datadict(self, data, files, name):
         value = data.get(name, None)
         return {u'2': True, u'3': False, True: True, False: False}.get(value, None)
+    
+    def is_empty(self, value):
+        # this widget will always either be True, False or None, so always
+        # return the opposite value so False and None values will make the
+        # form empty.
+        return not value
 
 class SelectMultiple(Widget):
     def __init__(self, attrs=None, choices=()):
@@ -521,6 +541,12 @@
 
     def value_from_datadict(self, data, files, name):
         return [widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)]
+    
+    def is_empty(self, value):
+        for widget, val in zip(self.widgets, value):
+            if not widget.is_empty(val):
+                return False
+        return True
 
     def format_output(self, rendered_widgets):
         """
