Index: django/newforms/formsets.py
===================================================================
--- django/newforms/formsets.py (revision 6730)
+++ django/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.deletable and 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, '%s-%s' % (form.prefix, f))
+ 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: django/newforms/models.py
===================================================================
--- django/newforms/models.py (revision 6730)
+++ django/newforms/models.py (working copy)
@@ -294,10 +294,10 @@
existing_objects[obj._get_pk_val()] = obj
saved_instances = []
for form in self.change_forms:
- obj = existing_objects[form.cleaned_data[self.model._meta.pk.attname]]
- if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]:
+ obj = existing_objects[self.clean_field(form, self.model._meta.pk.attname)]
+ if self.deletable and self.clean_field(form, DELETION_FIELD_NAME):
obj.delete()
- else:
+ elif self.is_valid():
saved_instances.append(self.save_instance(form, obj, commit=commit))
return saved_instances
@@ -309,9 +309,10 @@
# If someone has marked an add form for deletion, don't save the
# object. At some point it would be nice if we didn't display
# the deletion widget for add forms.
- if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]:
+ if self.deletable and self.clean_field(form, DELETION_FIELD_NAME):
continue
- new_objects.append(self.save_new(form, commit=commit))
+ if self.is_valid():
+ new_objects.append(self.save_new(form, commit=commit))
return new_objects
def add_fields(self, form, index):
Index: django/newforms/forms.py
===================================================================
--- django/newforms/forms.py (revision 6730)
+++ django/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: django/newforms/widgets.py
===================================================================
--- django/newforms/widgets.py (revision 6730)
+++ django/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):
"""
Index: tests/regressiontests/forms/widgets.py
===================================================================
--- tests/regressiontests/forms/widgets.py (revision 6730)
+++ tests/regressiontests/forms/widgets.py (working copy)
@@ -282,6 +282,12 @@
>>> w.value_from_datadict({}, {}, 'testing')
False
+The CheckboxInput widget will always be empty when there is a False value
+>>> w.is_empty(False)
+True
+>>> w.is_empty(True)
+False
+
# Select Widget ###############################################################
>>> w = Select()
@@ -432,6 +438,15 @@
+The NullBooleanSelect widget will always be empty when Unknown or No is selected
+as its value. This is to stay compliant with the CheckboxInput behavior
+>>> w.is_empty(False)
+True
+>>> w.is_empty(None)
+True
+>>> w.is_empty(True)
+False
+
""" + \
r""" # [This concatenation is to keep the string below the jython's 32K limit].
# SelectMultiple Widget #######################################################
@@ -834,6 +849,16 @@
>>> w.render('name', ['john', 'lennon'])
u' '
+The MultiWidget will be empty only when all widgets are considered empty.
+>>> w.is_empty(['john', 'lennon'])
+False
+>>> w.is_empty(['john', ''])
+False
+>>> w.is_empty(['', ''])
+True
+>>> w.is_empty([None, None])
+True
+
# SplitDateTimeWidget #########################################################
>>> w = SplitDateTimeWidget()