diff --git a/django/forms/fields.py b/django/forms/fields.py index 9538e27fbe..e3299c07aa 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -19,7 +19,7 @@ from django.core.exceptions import ValidationError from django.core import validators from django.utils import formats from django.utils.translation import ugettext_lazy as _ -from django.utils.encoding import smart_unicode, smart_str +from django.utils.encoding import smart_unicode, smart_str, force_unicode from django.utils.functional import lazy # Provide this import for backwards compatibility. @@ -320,16 +320,37 @@ class DecimalField(Field): raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places)) return value -class DateField(Field): +class BaseTemporalField(Field): + + def __init__(self, input_formats=None, *args, **kwargs): + super(BaseTemporalField, self).__init__(*args, **kwargs) + if input_formats is not None: + self.input_formats = input_formats + + def to_python(self, value): + # Try to coerce the value to unicode. + unicode_value = force_unicode(value, strings_only=True) + if isinstance(unicode_value, unicode): + value = unicode_value.strip() + # If unicode, try to strptime against each input format. + if isinstance(value, unicode): + for format in self.input_formats: + try: + return self.strptime(value, format) + except ValueError: + continue + raise ValidationError(self.error_messages['invalid']) + + def strptime(self, value, format): + raise NotImplementedError('Subclasses must define this method.') + +class DateField(BaseTemporalField): widget = DateInput + input_formats = formats.get_format_lazy('DATE_INPUT_FORMATS') default_error_messages = { 'invalid': _(u'Enter a valid date.'), } - def __init__(self, input_formats=None, *args, **kwargs): - super(DateField, self).__init__(*args, **kwargs) - self.input_formats = input_formats - def to_python(self, value): """ Validates that the input can be converted to a date. Returns a Python @@ -341,23 +362,18 @@ class DateField(Field): return value.date() if isinstance(value, datetime.date): return value - for format in self.input_formats or formats.get_format('DATE_INPUT_FORMATS'): - try: - return datetime.date(*time.strptime(value, format)[:3]) - except ValueError: - continue - raise ValidationError(self.error_messages['invalid']) + return super(DateField, self).to_python(value) -class TimeField(Field): + def strptime(self, value, format): + return datetime.date(*time.strptime(value, format)[:3]) + +class TimeField(BaseTemporalField): widget = TimeInput + input_formats = formats.get_format_lazy('TIME_INPUT_FORMATS') default_error_messages = { 'invalid': _(u'Enter a valid time.') } - def __init__(self, input_formats=None, *args, **kwargs): - super(TimeField, self).__init__(*args, **kwargs) - self.input_formats = input_formats - def to_python(self, value): """ Validates that the input can be converted to a time. Returns a Python @@ -367,23 +383,18 @@ class TimeField(Field): return None if isinstance(value, datetime.time): return value - for format in self.input_formats or formats.get_format('TIME_INPUT_FORMATS'): - try: - return datetime.time(*time.strptime(value, format)[3:6]) - except ValueError: - continue - raise ValidationError(self.error_messages['invalid']) + return super(TimeField, self).to_python(value) -class DateTimeField(Field): + def strptime(self, value, format): + return datetime.time(*time.strptime(value, format)[3:6]) + +class DateTimeField(BaseTemporalField): widget = DateTimeInput + input_formats = formats.get_format_lazy('DATETIME_INPUT_FORMATS') default_error_messages = { 'invalid': _(u'Enter a valid date/time.'), } - def __init__(self, input_formats=None, *args, **kwargs): - super(DateTimeField, self).__init__(*args, **kwargs) - self.input_formats = input_formats - def to_python(self, value): """ Validates that the input can be converted to a datetime. Returns a @@ -403,12 +414,10 @@ class DateTimeField(Field): if value[0] in validators.EMPTY_VALUES and value[1] in validators.EMPTY_VALUES: return None value = '%s %s' % tuple(value) - for format in self.input_formats or formats.get_format('DATETIME_INPUT_FORMATS'): - try: - return datetime.datetime(*time.strptime(value, format)[:6]) - except ValueError: - continue - raise ValidationError(self.error_messages['invalid']) + return super(DateTimeField, self).to_python(value) + + def strptime(self, value, format): + return datetime.datetime(*time.strptime(value, format)[:6]) class RegexField(CharField): def __init__(self, regex, max_length=None, min_length=None, error_message=None, *args, **kwargs): diff --git a/django/utils/formats.py b/django/utils/formats.py index 0dc23402f2..e176985791 100644 --- a/django/utils/formats.py +++ b/django/utils/formats.py @@ -2,11 +2,12 @@ import decimal import datetime from django.conf import settings -from django.utils.translation import get_language, to_locale, check_for_language +from django.utils import dateformat, numberformat, datetime_safe from django.utils.importlib import import_module from django.utils.encoding import smart_str -from django.utils import dateformat, numberformat, datetime_safe +from django.utils.functional import lazy from django.utils.safestring import mark_safe +from django.utils.translation import get_language, to_locale, check_for_language # format_cache is a mapping from (format_type, lang) to the format string. # By using the cache, it is possible to avoid running get_format_modules @@ -81,6 +82,8 @@ def get_format(format_type, lang=None, use_l10n=None): _format_cache[cache_key] = None return getattr(settings, format_type) +get_format_lazy = lazy(get_format, unicode, list, tuple) + def date_format(value, format=None, use_l10n=None): """ Formats a datetime.date or datetime.datetime object using a diff --git a/tests/regressiontests/forms/tests/fields.py b/tests/regressiontests/forms/tests/fields.py index f5bb4637fd..59c761c76d 100644 --- a/tests/regressiontests/forms/tests/fields.py +++ b/tests/regressiontests/forms/tests/fields.py @@ -334,6 +334,17 @@ class FieldsTests(TestCase): self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.']", f.clean, '10/25/2006') self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.']", f.clean, '10/25/06') + def test_datefield_4(self): + # Test whitespace stripping behavior (#5714) + f = DateField() + self.assertEqual(datetime.date(2006, 10, 25), f.clean(' 10/25/2006 ')) + self.assertEqual(datetime.date(2006, 10, 25), f.clean(' 10/25/06 ')) + self.assertEqual(datetime.date(2006, 10, 25), f.clean(' Oct 25 2006 ')) + self.assertEqual(datetime.date(2006, 10, 25), f.clean(' October 25 2006 ')) + self.assertEqual(datetime.date(2006, 10, 25), f.clean(' October 25, 2006 ')) + self.assertEqual(datetime.date(2006, 10, 25), f.clean(' 25 October 2006 ')) + self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.']", f.clean, ' ') + # TimeField ################################################################### def test_timefield_1(self): @@ -353,6 +364,13 @@ class FieldsTests(TestCase): self.assertEqual(datetime.time(16, 25), f.clean('4:25 PM')) self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid time.']", f.clean, '14:30:45') + def test_timefield_3(self): + f = TimeField() + # Test whitespace stripping behavior (#5714) + self.assertEqual(datetime.time(14, 25), f.clean(' 14:25 ')) + self.assertEqual(datetime.time(14, 25, 59), f.clean(' 14:25:59 ')) + self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid time.']", f.clean, ' ') + # DateTimeField ############################################################### def test_datetimefield_1(self): @@ -392,6 +410,18 @@ class FieldsTests(TestCase): self.assertEqual(None, f.clean('')) self.assertEqual('None', repr(f.clean(''))) + def test_datetimefield_4(self): + f = DateTimeField() + # Test whitespace stripping behavior (#5714) + self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45), f.clean(' 2006-10-25 14:30:45 ')) + self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean(' 2006-10-25 ')) + self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45), f.clean(' 10/25/2006 14:30:45 ')) + self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean(' 10/25/2006 14:30 ')) + self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean(' 10/25/2006 ')) + self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45), f.clean(' 10/25/06 14:30:45 ')) + self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean(' 10/25/06 ')) + self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date/time.']", f.clean, ' ') + # RegexField ################################################################## def test_regexfield_1(self):