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() | 
