Fixed #29036 -- Fixed HTML5 required validation on SelectDateWidget if the attribute is added by JavaScript.

Thanks Tim Graham for the initial patch.
This commit is contained in:
Vlastimil Zíma 2018-01-30 10:33:13 +01:00 committed by Tim Graham
parent 5538729e4e
commit fbc3c29e7c
4 changed files with 56 additions and 49 deletions

View File

@ -911,7 +911,7 @@ class SelectDateWidget(Widget):
This also serves as an example of a Widget that has more than one HTML This also serves as an example of a Widget that has more than one HTML
element and hence implements value_from_datadict. element and hence implements value_from_datadict.
""" """
none_value = (0, '---') none_value = ('', '---')
month_field = '%s_month' month_field = '%s_month'
day_field = '%s_day' day_field = '%s_day'
year_field = '%s_year' year_field = '%s_year'
@ -941,12 +941,12 @@ class SelectDateWidget(Widget):
if not len(empty_label) == 3: if not len(empty_label) == 3:
raise ValueError('empty_label list/tuple must have 3 elements.') raise ValueError('empty_label list/tuple must have 3 elements.')
self.year_none_value = (0, empty_label[0]) self.year_none_value = ('', empty_label[0])
self.month_none_value = (0, empty_label[1]) self.month_none_value = ('', empty_label[1])
self.day_none_value = (0, empty_label[2]) self.day_none_value = ('', empty_label[2])
else: else:
if empty_label is not None: if empty_label is not None:
self.none_value = (0, empty_label) self.none_value = ('', empty_label)
self.year_none_value = self.none_value self.year_none_value = self.none_value
self.month_none_value = self.none_value self.month_none_value = self.none_value
@ -1006,7 +1006,9 @@ class SelectDateWidget(Widget):
elif isinstance(value, str): elif isinstance(value, str):
match = self.date_re.match(value) match = self.date_re.match(value)
if match: if match:
year, month, day = [int(val) for val in match.groups()] # Convert any zeros in the date to empty strings to match the
# empty option value.
year, month, day = [int(val) or '' for val in match.groups()]
elif settings.USE_L10N: elif settings.USE_L10N:
input_format = get_format('DATE_INPUT_FORMATS')[0] input_format = get_format('DATE_INPUT_FORMATS')[0]
try: try:
@ -1042,20 +1044,21 @@ class SelectDateWidget(Widget):
y = data.get(self.year_field % name) y = data.get(self.year_field % name)
m = data.get(self.month_field % name) m = data.get(self.month_field % name)
d = data.get(self.day_field % name) d = data.get(self.day_field % name)
if y == m == d == "0": if y == m == d == '':
return None return None
if y and m and d: if y is not None and m is not None and d is not None:
if settings.USE_L10N: if settings.USE_L10N:
input_format = get_format('DATE_INPUT_FORMATS')[0] input_format = get_format('DATE_INPUT_FORMATS')[0]
try: try:
date_value = datetime.date(int(y), int(m), int(d)) date_value = datetime.date(int(y), int(m), int(d))
except ValueError: except ValueError:
return '%s-%s-%s' % (y, m, d) pass
else: else:
date_value = datetime_safe.new_date(date_value) date_value = datetime_safe.new_date(date_value)
return date_value.strftime(input_format) return date_value.strftime(input_format)
else: # Return pseudo-ISO dates with zeros for any unselected values,
return '%s-%s-%s' % (y, m, d) # e.g. '2017-0-23'.
return '%s-%s-%s' % (y or 0, m or 0, d or 0)
return data.get(name) return data.get(name)
def value_omitted_from_data(self, data, files, name): def value_omitted_from_data(self, data, files, name):

View File

@ -265,6 +265,10 @@ Miscellaneous
elements, e.g. ``<br>``. This is incompatible within XHTML, although some elements, e.g. ``<br>``. This is incompatible within XHTML, although some
widgets already used aspects of HTML5 such as boolean attributes. widgets already used aspects of HTML5 such as boolean attributes.
* The value of :class:`~django.forms.SelectDateWidget`'s empty options is
changed from 0 to an empty string, which mainly may require some adjustments
in tests that compare HTML.
.. _deprecated-features-2.1: .. _deprecated-features-2.1:
Features deprecated in 2.1 Features deprecated in 2.1

View File

@ -18,7 +18,7 @@ class SelectDateWidgetTest(WidgetTest):
self.check_html(self.widget, 'mydate', '', html=( self.check_html(self.widget, 'mydate', '', html=(
""" """
<select name="mydate_month" id="id_mydate_month"> <select name="mydate_month" id="id_mydate_month">
<option value="0">---</option> <option selected value="">---</option>
<option value="1">January</option> <option value="1">January</option>
<option value="2">February</option> <option value="2">February</option>
<option value="3">March</option> <option value="3">March</option>
@ -34,7 +34,7 @@ class SelectDateWidgetTest(WidgetTest):
</select> </select>
<select name="mydate_day" id="id_mydate_day"> <select name="mydate_day" id="id_mydate_day">
<option value="0">---</option> <option selected value="">---</option>
<option value="1">1</option> <option value="1">1</option>
<option value="2">2</option> <option value="2">2</option>
<option value="3">3</option> <option value="3">3</option>
@ -69,7 +69,7 @@ class SelectDateWidgetTest(WidgetTest):
</select> </select>
<select name="mydate_year" id="id_mydate_year"> <select name="mydate_year" id="id_mydate_year">
<option value="0">---</option> <option selected value="">---</option>
<option value="2007">2007</option> <option value="2007">2007</option>
<option value="2008">2008</option> <option value="2008">2008</option>
<option value="2009">2009</option> <option value="2009">2009</option>
@ -97,7 +97,7 @@ class SelectDateWidgetTest(WidgetTest):
self.check_html(self.widget, 'mydate', '2010-04-15', html=( self.check_html(self.widget, 'mydate', '2010-04-15', html=(
""" """
<select name="mydate_month" id="id_mydate_month"> <select name="mydate_month" id="id_mydate_month">
<option value="0">---</option> <option value="">---</option>
<option value="1">January</option> <option value="1">January</option>
<option value="2">February</option> <option value="2">February</option>
<option value="3">March</option> <option value="3">March</option>
@ -113,7 +113,7 @@ class SelectDateWidgetTest(WidgetTest):
</select> </select>
<select name="mydate_day" id="id_mydate_day"> <select name="mydate_day" id="id_mydate_day">
<option value="0">---</option> <option value="">---</option>
<option value="1">1</option> <option value="1">1</option>
<option value="2">2</option> <option value="2">2</option>
<option value="3">3</option> <option value="3">3</option>
@ -148,7 +148,7 @@ class SelectDateWidgetTest(WidgetTest):
</select> </select>
<select name="mydate_year" id="id_mydate_year"> <select name="mydate_year" id="id_mydate_year">
<option value="0">---</option> <option value="">---</option>
<option value="2007">2007</option> <option value="2007">2007</option>
<option value="2008">2008</option> <option value="2008">2008</option>
<option value="2009">2009</option> <option value="2009">2009</option>
@ -176,7 +176,7 @@ class SelectDateWidgetTest(WidgetTest):
self.check_html(self.widget, 'mydate', '2010-02-31', html=( self.check_html(self.widget, 'mydate', '2010-02-31', html=(
""" """
<select name="mydate_month" id="id_mydate_month"> <select name="mydate_month" id="id_mydate_month">
<option value="0">---</option> <option value="">---</option>
<option value="1">January</option> <option value="1">January</option>
<option value="2" selected>February</option> <option value="2" selected>February</option>
<option value="3">March</option> <option value="3">March</option>
@ -192,7 +192,7 @@ class SelectDateWidgetTest(WidgetTest):
</select> </select>
<select name="mydate_day" id="id_mydate_day"> <select name="mydate_day" id="id_mydate_day">
<option value="0">---</option> <option value="">---</option>
<option value="1">1</option> <option value="1">1</option>
<option value="2">2</option> <option value="2">2</option>
<option value="3">3</option> <option value="3">3</option>
@ -227,7 +227,7 @@ class SelectDateWidgetTest(WidgetTest):
</select> </select>
<select name="mydate_year" id="id_mydate_year"> <select name="mydate_year" id="id_mydate_year">
<option value="0">---</option> <option value="">---</option>
<option value="2007">2007</option> <option value="2007">2007</option>
<option value="2008">2008</option> <option value="2008">2008</option>
<option value="2009">2009</option> <option value="2009">2009</option>
@ -247,7 +247,7 @@ class SelectDateWidgetTest(WidgetTest):
self.check_html(widget, 'mydate', '', html=( self.check_html(widget, 'mydate', '', html=(
""" """
<select name="mydate_month" id="id_mydate_month"> <select name="mydate_month" id="id_mydate_month">
<option value="0">---</option> <option selected value="">---</option>
<option value="1">Jan.</option> <option value="1">Jan.</option>
<option value="2">Feb.</option> <option value="2">Feb.</option>
<option value="3">March</option> <option value="3">March</option>
@ -263,7 +263,7 @@ class SelectDateWidgetTest(WidgetTest):
</select> </select>
<select name="mydate_day" id="id_mydate_day"> <select name="mydate_day" id="id_mydate_day">
<option value="0">---</option> <option selected value="">---</option>
<option value="1">1</option> <option value="1">1</option>
<option value="2">2</option> <option value="2">2</option>
<option value="3">3</option> <option value="3">3</option>
@ -298,7 +298,7 @@ class SelectDateWidgetTest(WidgetTest):
</select> </select>
<select name="mydate_year" id="id_mydate_year"> <select name="mydate_year" id="id_mydate_year">
<option value="0">---</option> <option selected value="">---</option>
<option value="2013">2013</option> <option value="2013">2013</option>
</select> </select>
""" """
@ -318,7 +318,7 @@ class SelectDateWidgetTest(WidgetTest):
w = SelectDateWidget(years=('2014',), empty_label='empty_label') w = SelectDateWidget(years=('2014',), empty_label='empty_label')
# Rendering the default state with empty_label setted as string. # Rendering the default state with empty_label setted as string.
self.assertInHTML('<option value="0">empty_label</option>', w.render('mydate', ''), count=3) self.assertInHTML('<option selected value="">empty_label</option>', w.render('mydate', ''), count=3)
w = SelectDateWidget(years=('2014',), empty_label=('empty_year', 'empty_month', 'empty_day')) w = SelectDateWidget(years=('2014',), empty_label=('empty_year', 'empty_month', 'empty_day'))
@ -327,7 +327,7 @@ class SelectDateWidgetTest(WidgetTest):
w.render('mydate', ''), w.render('mydate', ''),
""" """
<select name="mydate_month" id="id_mydate_month"> <select name="mydate_month" id="id_mydate_month">
<option value="0">empty_month</option> <option selected value="">empty_month</option>
<option value="1">January</option> <option value="1">January</option>
<option value="2">February</option> <option value="2">February</option>
<option value="3">March</option> <option value="3">March</option>
@ -343,7 +343,7 @@ class SelectDateWidgetTest(WidgetTest):
</select> </select>
<select name="mydate_day" id="id_mydate_day"> <select name="mydate_day" id="id_mydate_day">
<option value="0">empty_day</option> <option selected value="">empty_day</option>
<option value="1">1</option> <option value="1">1</option>
<option value="2">2</option> <option value="2">2</option>
<option value="3">3</option> <option value="3">3</option>
@ -378,7 +378,7 @@ class SelectDateWidgetTest(WidgetTest):
</select> </select>
<select name="mydate_year" id="id_mydate_year"> <select name="mydate_year" id="id_mydate_year">
<option value="0">empty_year</option> <option selected value="">empty_year</option>
<option value="2014">2014</option> <option value="2014">2014</option>
</select> </select>
""", """,
@ -402,7 +402,7 @@ class SelectDateWidgetTest(WidgetTest):
w.render('date', '13-08-2010'), w.render('date', '13-08-2010'),
""" """
<select name="date_day" id="id_date_day"> <select name="date_day" id="id_date_day">
<option value="0">---</option> <option value="">---</option>
<option value="1">1</option> <option value="1">1</option>
<option value="2">2</option> <option value="2">2</option>
<option value="3">3</option> <option value="3">3</option>
@ -437,7 +437,7 @@ class SelectDateWidgetTest(WidgetTest):
</select> </select>
<select name="date_month" id="id_date_month"> <select name="date_month" id="id_date_month">
<option value="0">---</option> <option value="">---</option>
<option value="1">januari</option> <option value="1">januari</option>
<option value="2">februari</option> <option value="2">februari</option>
<option value="3">maart</option> <option value="3">maart</option>
@ -453,7 +453,7 @@ class SelectDateWidgetTest(WidgetTest):
</select> </select>
<select name="date_year" id="id_date_year"> <select name="date_year" id="id_date_year">
<option value="0">---</option> <option value="">---</option>
<option value="2007">2007</option> <option value="2007">2007</option>
<option value="2008">2008</option> <option value="2008">2008</option>
<option value="2009">2009</option> <option value="2009">2009</option>
@ -485,7 +485,7 @@ class SelectDateWidgetTest(WidgetTest):
'0-01-01', '0-01-0', '0-0-01', '0-0-0', '0-01-01', '0-01-0', '0-0-01', '0-0-0',
] ]
for value in valid_formats: for value in valid_formats:
year, month, day = (int(x) for x in value.split('-')) year, month, day = (int(x) or '' for x in value.split('-'))
with self.subTest(value=value): with self.subTest(value=value):
self.assertEqual(self.widget.format_value(value), {'day': day, 'month': month, 'year': year}) self.assertEqual(self.widget.format_value(value), {'day': day, 'month': month, 'year': year})
@ -500,9 +500,9 @@ class SelectDateWidgetTest(WidgetTest):
def test_value_from_datadict(self): def test_value_from_datadict(self):
tests = [ tests = [
(('2000', '12', '1'), '2000-12-1'), (('2000', '12', '1'), '2000-12-1'),
(('0', '12', '1'), '0-12-1'), (('', '12', '1'), '0-12-1'),
(('2000', '0', '1'), '2000-0-1'), (('2000', '', '1'), '2000-0-1'),
(('2000', '12', '0'), '2000-12-0'), (('2000', '12', ''), '2000-12-0'),
(('', '', '', ''), None), (('', '', '', ''), None),
((None, '12', '1'), None), ((None, '12', '1'), None),
(('2000', None, '1'), None), (('2000', None, '1'), None),
@ -530,7 +530,7 @@ class SelectDateWidgetTest(WidgetTest):
self.check_html(widget, 'mydate', '', html=( self.check_html(widget, 'mydate', '', html=(
""" """
<select name="mydate_month" id="id_mydate_month"> <select name="mydate_month" id="id_mydate_month">
<option value="0">---</option> <option selected value="">---</option>
<option value="1">January</option> <option value="1">January</option>
<option value="2">February</option> <option value="2">February</option>
<option value="3">March</option> <option value="3">March</option>
@ -545,7 +545,7 @@ class SelectDateWidgetTest(WidgetTest):
<option value="12">December</option> <option value="12">December</option>
</select> </select>
<select name="mydate_day" id="id_mydate_day"> <select name="mydate_day" id="id_mydate_day">
<option value="0">---</option> <option selected value="">---</option>
<option value="1">1</option> <option value="1">1</option>
<option value="2">2</option> <option value="2">2</option>
<option value="3">3</option> <option value="3">3</option>
@ -579,7 +579,7 @@ class SelectDateWidgetTest(WidgetTest):
<option value="31">31</option> <option value="31">31</option>
</select> </select>
<select name="mydate_year" id="id_mydate_year"> <select name="mydate_year" id="id_mydate_year">
<option value="0">---</option> <option selected value="">---</option>
<option value="2007">2007</option> <option value="2007">2007</option>
</select> </select>
""" """

View File

@ -443,7 +443,7 @@ class FormattingTests(SimpleTestCase):
self.assertEqual(datetime.date(2009, 12, 31), form2.cleaned_data['date_field']) self.assertEqual(datetime.date(2009, 12, 31), form2.cleaned_data['date_field'])
self.assertHTMLEqual( self.assertHTMLEqual(
'<select name="mydate_month" id="id_mydate_month">' '<select name="mydate_month" id="id_mydate_month">'
'<option value="0">---</option>' '<option value="">---</option>'
'<option value="1">gener</option>' '<option value="1">gener</option>'
'<option value="2">febrer</option>' '<option value="2">febrer</option>'
'<option value="3">mar\xe7</option>' '<option value="3">mar\xe7</option>'
@ -458,7 +458,7 @@ class FormattingTests(SimpleTestCase):
'<option value="12" selected>desembre</option>' '<option value="12" selected>desembre</option>'
'</select>' '</select>'
'<select name="mydate_day" id="id_mydate_day">' '<select name="mydate_day" id="id_mydate_day">'
'<option value="0">---</option>' '<option value="">---</option>'
'<option value="1">1</option>' '<option value="1">1</option>'
'<option value="2">2</option>' '<option value="2">2</option>'
'<option value="3">3</option>' '<option value="3">3</option>'
@ -492,7 +492,7 @@ class FormattingTests(SimpleTestCase):
'<option value="31" selected>31</option>' '<option value="31" selected>31</option>'
'</select>' '</select>'
'<select name="mydate_year" id="id_mydate_year">' '<select name="mydate_year" id="id_mydate_year">'
'<option value="0">---</option>' '<option value="">---</option>'
'<option value="2009" selected>2009</option>' '<option value="2009" selected>2009</option>'
'<option value="2010">2010</option>' '<option value="2010">2010</option>'
'<option value="2011">2011</option>' '<option value="2011">2011</option>'
@ -622,7 +622,7 @@ class FormattingTests(SimpleTestCase):
self.assertEqual(datetime.date(2009, 12, 31), form5.cleaned_data['date_field']) self.assertEqual(datetime.date(2009, 12, 31), form5.cleaned_data['date_field'])
self.assertHTMLEqual( self.assertHTMLEqual(
'<select name="mydate_day" id="id_mydate_day">' '<select name="mydate_day" id="id_mydate_day">'
'<option value="0">---</option>' '<option value="">---</option>'
'<option value="1">1</option>' '<option value="1">1</option>'
'<option value="2">2</option>' '<option value="2">2</option>'
'<option value="3">3</option>' '<option value="3">3</option>'
@ -656,7 +656,7 @@ class FormattingTests(SimpleTestCase):
'<option value="31" selected>31</option>' '<option value="31" selected>31</option>'
'</select>' '</select>'
'<select name="mydate_month" id="id_mydate_month">' '<select name="mydate_month" id="id_mydate_month">'
'<option value="0">---</option>' '<option value="">---</option>'
'<option value="1">gener</option>' '<option value="1">gener</option>'
'<option value="2">febrer</option>' '<option value="2">febrer</option>'
'<option value="3">mar\xe7</option>' '<option value="3">mar\xe7</option>'
@ -671,7 +671,7 @@ class FormattingTests(SimpleTestCase):
'<option value="12" selected>desembre</option>' '<option value="12" selected>desembre</option>'
'</select>' '</select>'
'<select name="mydate_year" id="id_mydate_year">' '<select name="mydate_year" id="id_mydate_year">'
'<option value="0">---</option>' '<option value="">---</option>'
'<option value="2009" selected>2009</option>' '<option value="2009" selected>2009</option>'
'<option value="2010">2010</option>' '<option value="2010">2010</option>'
'<option value="2011">2011</option>' '<option value="2011">2011</option>'
@ -690,7 +690,7 @@ class FormattingTests(SimpleTestCase):
with translation.override('ru', deactivate=True): with translation.override('ru', deactivate=True):
self.assertHTMLEqual( self.assertHTMLEqual(
'<select name="mydate_day" id="id_mydate_day">' '<select name="mydate_day" id="id_mydate_day">'
'<option value="0">---</option>' '<option value="">---</option>'
'<option value="1">1</option>' '<option value="1">1</option>'
'<option value="2">2</option>' '<option value="2">2</option>'
'<option value="3">3</option>' '<option value="3">3</option>'
@ -724,7 +724,7 @@ class FormattingTests(SimpleTestCase):
'<option value="31" selected>31</option>' '<option value="31" selected>31</option>'
'</select>' '</select>'
'<select name="mydate_month" id="id_mydate_month">' '<select name="mydate_month" id="id_mydate_month">'
'<option value="0">---</option>' '<option value="">---</option>'
'<option value="1">\u042f\u043d\u0432\u0430\u0440\u044c</option>' '<option value="1">\u042f\u043d\u0432\u0430\u0440\u044c</option>'
'<option value="2">\u0424\u0435\u0432\u0440\u0430\u043b\u044c</option>' '<option value="2">\u0424\u0435\u0432\u0440\u0430\u043b\u044c</option>'
'<option value="3">\u041c\u0430\u0440\u0442</option>' '<option value="3">\u041c\u0430\u0440\u0442</option>'
@ -739,7 +739,7 @@ class FormattingTests(SimpleTestCase):
'<option value="12" selected>\u0414\u0435\u043a\u0430\u0431\u0440\u044c</option>' '<option value="12" selected>\u0414\u0435\u043a\u0430\u0431\u0440\u044c</option>'
'</select>' '</select>'
'<select name="mydate_year" id="id_mydate_year">' '<select name="mydate_year" id="id_mydate_year">'
'<option value="0">---</option>' '<option value="">---</option>'
'<option value="2009" selected>2009</option>' '<option value="2009" selected>2009</option>'
'<option value="2010">2010</option>' '<option value="2010">2010</option>'
'<option value="2011">2011</option>' '<option value="2011">2011</option>'
@ -819,7 +819,7 @@ class FormattingTests(SimpleTestCase):
self.assertEqual(datetime.date(2009, 12, 31), form6.cleaned_data['date_field']) self.assertEqual(datetime.date(2009, 12, 31), form6.cleaned_data['date_field'])
self.assertHTMLEqual( self.assertHTMLEqual(
'<select name="mydate_month" id="id_mydate_month">' '<select name="mydate_month" id="id_mydate_month">'
'<option value="0">---</option>' '<option value="">---</option>'
'<option value="1">January</option>' '<option value="1">January</option>'
'<option value="2">February</option>' '<option value="2">February</option>'
'<option value="3">March</option>' '<option value="3">March</option>'
@ -834,7 +834,7 @@ class FormattingTests(SimpleTestCase):
'<option value="12" selected>December</option>' '<option value="12" selected>December</option>'
'</select>' '</select>'
'<select name="mydate_day" id="id_mydate_day">' '<select name="mydate_day" id="id_mydate_day">'
'<option value="0">---</option>' '<option value="">---</option>'
'<option value="1">1</option>' '<option value="1">1</option>'
'<option value="2">2</option>' '<option value="2">2</option>'
'<option value="3">3</option>' '<option value="3">3</option>'
@ -868,7 +868,7 @@ class FormattingTests(SimpleTestCase):
'<option value="31" selected>31</option>' '<option value="31" selected>31</option>'
'</select>' '</select>'
'<select name="mydate_year" id="id_mydate_year">' '<select name="mydate_year" id="id_mydate_year">'
'<option value="0">---</option>' '<option value="">---</option>'
'<option value="2009" selected>2009</option>' '<option value="2009" selected>2009</option>'
'<option value="2010">2010</option>' '<option value="2010">2010</option>'
'<option value="2011">2011</option>' '<option value="2011">2011</option>'