diff --git a/django/newforms/forms.py b/django/newforms/forms.py index fdc41f7dee..88d5f8b3df 100644 --- a/django/newforms/forms.py +++ b/django/newforms/forms.py @@ -182,12 +182,11 @@ class BaseForm(StrAndUnicode): 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 diff --git a/django/newforms/widgets.py b/django/newforms/widgets.py index e134e60bd7..9c4a3cd698 100644 --- a/django/newforms/widgets.py +++ b/django/newforms/widgets.py @@ -165,6 +165,15 @@ class Widget(object): 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_): """ @@ -301,6 +310,11 @@ class CheckboxInput(Widget): # 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=()): @@ -343,6 +357,12 @@ class NullBooleanSelect(Select): 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=()): @@ -539,6 +559,12 @@ class MultiWidget(Widget): 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): """ diff --git a/tests/regressiontests/forms/widgets.py b/tests/regressiontests/forms/widgets.py index 0e69602103..ccfddc9fcd 100644 --- a/tests/regressiontests/forms/widgets.py +++ b/tests/regressiontests/forms/widgets.py @@ -292,6 +292,12 @@ checkboxes). >>> 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() @@ -453,6 +459,15 @@ over multiple times without getting consumed: +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 ####################################################### @@ -895,6 +910,16 @@ u'
< >>> 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()