diff options
| author | Tom Christie | 2014-09-25 13:10:33 +0100 |
|---|---|---|
| committer | Tom Christie | 2014-09-25 13:10:33 +0100 |
| commit | 8ee92f8a18c3a31a2a95233f36754203dc60bb18 (patch) | |
| tree | 56a06f627659dee8094581f216be69bf3fe8a8a1 /rest_framework/fields.py | |
| parent | b47ca158b9ba9733baad080e648d24b0465ec697 (diff) | |
| download | django-rest-framework-8ee92f8a18c3a31a2a95233f36754203dc60bb18.tar.bz2 | |
Refuse to downcast from datetime to date or time
Diffstat (limited to 'rest_framework/fields.py')
| -rw-r--r-- | rest_framework/fields.py | 118 |
1 files changed, 65 insertions, 53 deletions
diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 328e93ef..d855e6fd 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -608,120 +608,126 @@ class DecimalField(Field): # Date & time fields... -class DateField(Field): +class DateTimeField(Field): default_error_messages = { - 'invalid': _('Date has wrong format. Use one of these formats instead: {format}'), - 'datetime': _('Expected a date but got a datetime.'), + 'invalid': _('Datetime has wrong format. Use one of these formats instead: {format}'), + 'date': _('Expected a datetime but got a date.'), } - format = api_settings.DATE_FORMAT - input_formats = api_settings.DATE_INPUT_FORMATS + format = api_settings.DATETIME_FORMAT + input_formats = api_settings.DATETIME_INPUT_FORMATS + default_timezone = timezone.get_default_timezone() if settings.USE_TZ else None - def __init__(self, format=empty, input_formats=None, *args, **kwargs): + def __init__(self, format=empty, input_formats=None, default_timezone=None, *args, **kwargs): self.format = format if format is not empty else self.format self.input_formats = input_formats if input_formats is not None else self.input_formats - super(DateField, self).__init__(*args, **kwargs) + self.default_timezone = default_timezone if default_timezone is not None else self.default_timezone + super(DateTimeField, self).__init__(*args, **kwargs) + + def enforce_timezone(self, value): + """ + When `self.default_timezone` is `None`, always return naive datetimes. + When `self.default_timezone` is not `None`, always return aware datetimes. + """ + if (self.default_timezone is not None) and not timezone.is_aware(value): + return timezone.make_aware(value, self.default_timezone) + elif (self.default_timezone is None) and timezone.is_aware(value): + return timezone.make_naive(value, timezone.UTC()) + return value def to_internal_value(self, value): - if isinstance(value, datetime.datetime): - self.fail('datetime') + if (isinstance(value, datetime.date) and not isinstance(value, datetime.datetime): + self.fail('date') - if isinstance(value, datetime.date): - return value + if isinstance(value, datetime.datetime): + return self.enforce_timezone(value) for format in self.input_formats: if format.lower() == ISO_8601: try: - parsed = parse_date(value) + parsed = parse_datetime(value) except (ValueError, TypeError): pass else: if parsed is not None: - return parsed + return self.enforce_timezone(parsed) else: try: parsed = datetime.datetime.strptime(value, format) except (ValueError, TypeError): pass else: - return parsed.date() + return self.enforce_timezone(parsed) - humanized_format = humanize_datetime.date_formats(self.input_formats) + humanized_format = humanize_datetime.datetime_formats(self.input_formats) self.fail('invalid', format=humanized_format) def to_representation(self, value): if value is None or self.format is None: return value - if isinstance(value, datetime.datetime): - value = value.date() - if self.format.lower() == ISO_8601: - return value.isoformat() + ret = value.isoformat() + if ret.endswith('+00:00'): + ret = ret[:-6] + 'Z' + return ret return value.strftime(self.format) -class DateTimeField(Field): +class DateField(Field): default_error_messages = { - 'invalid': _('Datetime has wrong format. Use one of these formats instead: {format}'), - 'date': _('Expected a datetime but got a date.'), + 'invalid': _('Date has wrong format. Use one of these formats instead: {format}'), + 'datetime': _('Expected a date but got a datetime.'), } - format = api_settings.DATETIME_FORMAT - input_formats = api_settings.DATETIME_INPUT_FORMATS - default_timezone = timezone.get_default_timezone() if settings.USE_TZ else None + format = api_settings.DATE_FORMAT + input_formats = api_settings.DATE_INPUT_FORMATS - def __init__(self, format=empty, input_formats=None, default_timezone=None, *args, **kwargs): + def __init__(self, format=empty, input_formats=None, *args, **kwargs): self.format = format if format is not empty else self.format self.input_formats = input_formats if input_formats is not None else self.input_formats - self.default_timezone = default_timezone if default_timezone is not None else self.default_timezone - super(DateTimeField, self).__init__(*args, **kwargs) - - def enforce_timezone(self, value): - """ - When `self.default_timezone` is `None`, always return naive datetimes. - When `self.default_timezone` is not `None`, always return aware datetimes. - """ - if (self.default_timezone is not None) and not timezone.is_aware(value): - return timezone.make_aware(value, self.default_timezone) - elif (self.default_timezone is None) and timezone.is_aware(value): - return timezone.make_naive(value, timezone.UTC()) - return value + super(DateField, self).__init__(*args, **kwargs) def to_internal_value(self, value): - if isinstance(value, datetime.date) and not isinstance(value, datetime.datetime): - self.fail('date') - if isinstance(value, datetime.datetime): - return self.enforce_timezone(value) + self.fail('datetime') + + if isinstance(value, datetime.date): + return value for format in self.input_formats: if format.lower() == ISO_8601: try: - parsed = parse_datetime(value) + parsed = parse_date(value) except (ValueError, TypeError): pass else: if parsed is not None: - return self.enforce_timezone(parsed) + return parsed else: try: parsed = datetime.datetime.strptime(value, format) except (ValueError, TypeError): pass else: - return self.enforce_timezone(parsed) + return parsed.date() - humanized_format = humanize_datetime.datetime_formats(self.input_formats) + humanized_format = humanize_datetime.date_formats(self.input_formats) self.fail('invalid', format=humanized_format) def to_representation(self, value): if value is None or self.format is None: return value + # Applying a `DateField` to a datetime value is almost always + # not a sensible thing to do, as it means naively dropping + # any explicit or implicit timezone info. + assert not isinstance(value, datetime.datetime), ( + 'Expected a `date`, but got a `datetime`. Refusing to coerce, ' + 'as this may mean losing timezone information. Use a custom ' + 'read-only field and deal with timezone issues explicitly.' + ) + if self.format.lower() == ISO_8601: - ret = value.isoformat() - if ret.endswith('+00:00'): - ret = ret[:-6] + 'Z' - return ret + return value.isoformat() return value.strftime(self.format) @@ -765,8 +771,14 @@ class TimeField(Field): if value is None or self.format is None: return value - if isinstance(value, datetime.datetime): - value = value.time() + # Applying a `TimeField` to a datetime value is almost always + # not a sensible thing to do, as it means naively dropping + # any explicit or implicit timezone info. + assert not isinstance(value, datetime.datetime), ( + 'Expected a `time`, but got a `datetime`. Refusing to coerce, ' + 'as this may mean losing timezone information. Use a custom ' + 'read-only field and deal with timezone issues explicitly.' + ) if self.format.lower() == ISO_8601: return value.isoformat() |
