aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework/fields.py
diff options
context:
space:
mode:
authorTom Christie2014-09-25 13:10:33 +0100
committerTom Christie2014-09-25 13:10:33 +0100
commit8ee92f8a18c3a31a2a95233f36754203dc60bb18 (patch)
tree56a06f627659dee8094581f216be69bf3fe8a8a1 /rest_framework/fields.py
parentb47ca158b9ba9733baad080e648d24b0465ec697 (diff)
downloaddjango-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.py118
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()