mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-05 04:50:12 +03:00
Merge f2cb74edde
into d6c6df40c6
This commit is contained in:
commit
cfb21f4ae7
|
@ -299,7 +299,7 @@ Corresponds to `django.db.models.fields.DateTimeField`.
|
|||
|
||||
#### `DateTimeField` format strings.
|
||||
|
||||
Format strings may either be [Python strftime formats][strftime] which explicitly specify the format, or the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style datetimes should be used. (eg `'2013-01-29T12:34:56.000000Z'`)
|
||||
Format strings may either be [Python strftime formats][strftime] which explicitly specify the format, or the special strings `'iso-8601'`, which indicates that [ISO 8601][iso8601] style datetimes should be used (eg `'2013-01-29T12:34:56.000000Z'`), and `'iso-8601-strict'`, which indicates that [ISO 8601][iso8601] style datetimes without fraction seconds should be used (eg `'2013-01-29T12:34:56Z'`)
|
||||
|
||||
When a value of `None` is used for the format `datetime` objects will be returned by `to_representation` and the final output representation will determined by the renderer class.
|
||||
|
||||
|
@ -345,7 +345,7 @@ Corresponds to `django.db.models.fields.TimeField`
|
|||
|
||||
#### `TimeField` format strings
|
||||
|
||||
Format strings may either be [Python strftime formats][strftime] which explicitly specify the format, or the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style times should be used. (eg `'12:34:56.000000'`)
|
||||
Format strings may either be [Python strftime formats][strftime] which explicitly specify the format, or the special strings `'iso-8601'`, which indicates that [ISO 8601][iso8601] style times should be used (eg `'12:34:56.000000'`), and `'iso-8601-strict'`, which indicates that [ISO 8601][iso8601] style times without fractional seconds should be used (eg `'12:34:56'`).
|
||||
|
||||
## DurationField
|
||||
|
||||
|
|
|
@ -286,7 +286,7 @@ Default: `'format'`
|
|||
|
||||
A format string that should be used by default for rendering the output of `DateTimeField` serializer fields. If `None`, then `DateTimeField` serializer fields will return Python `datetime` objects, and the datetime encoding will be determined by the renderer.
|
||||
|
||||
May be any of `None`, `'iso-8601'` or a Python [strftime format][strftime] string.
|
||||
May be any of `None`, `'iso-8601'`, `'iso-8601-strict'`, or a Python [strftime format][strftime] string.
|
||||
|
||||
Default: `'iso-8601'`
|
||||
|
||||
|
@ -294,7 +294,7 @@ Default: `'iso-8601'`
|
|||
|
||||
A list of format strings that should be used by default for parsing inputs to `DateTimeField` serializer fields.
|
||||
|
||||
May be a list including the string `'iso-8601'` or Python [strftime format][strftime] strings.
|
||||
May be a list including the string `'iso-8601'`, `'iso-8601-strict'` or Python [strftime format][strftime] strings.
|
||||
|
||||
Default: `['iso-8601']`
|
||||
|
||||
|
@ -318,7 +318,7 @@ Default: `['iso-8601']`
|
|||
|
||||
A format string that should be used by default for rendering the output of `TimeField` serializer fields. If `None`, then `TimeField` serializer fields will return Python `time` objects, and the time encoding will be determined by the renderer.
|
||||
|
||||
May be any of `None`, `'iso-8601'` or a Python [strftime format][strftime] string.
|
||||
May be any of `None`, `'iso-8601'`, `'iso-8601-strict'` or a Python [strftime format][strftime] string.
|
||||
|
||||
Default: `'iso-8601'`
|
||||
|
||||
|
@ -326,7 +326,7 @@ Default: `'iso-8601'`
|
|||
|
||||
A list of format strings that should be used by default for parsing inputs to `TimeField` serializer fields.
|
||||
|
||||
May be a list including the string `'iso-8601'` or Python [strftime format][strftime] strings.
|
||||
May be a list including the string `'iso-8601'`, `'iso-8601-strict'` or Python [strftime format][strftime] strings.
|
||||
|
||||
Default: `['iso-8601']`
|
||||
|
||||
|
|
|
@ -21,3 +21,4 @@ HTTP_HEADER_ENCODING = 'iso-8859-1'
|
|||
|
||||
# Default datetime input and output formats
|
||||
ISO_8601 = 'iso-8601'
|
||||
ISO_8601_STRICT = 'iso-8601-strict'
|
||||
|
|
|
@ -30,7 +30,7 @@ from django.utils.functional import cached_property
|
|||
from django.utils.ipv6 import clean_ipv6_address
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from rest_framework import ISO_8601
|
||||
from rest_framework import ISO_8601, ISO_8601_STRICT
|
||||
from rest_framework.compat import (
|
||||
get_remote_field, unicode_repr, unicode_to_repr, value_from_object
|
||||
)
|
||||
|
@ -1120,13 +1120,15 @@ class DateTimeField(Field):
|
|||
return self.enforce_timezone(value)
|
||||
|
||||
for input_format in input_formats:
|
||||
if input_format.lower() == ISO_8601:
|
||||
if input_format.lower() in (ISO_8601, ISO_8601_STRICT):
|
||||
try:
|
||||
parsed = parse_datetime(value)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
else:
|
||||
if parsed is not None:
|
||||
if input_format == ISO_8601_STRICT:
|
||||
parsed = parsed - datetime.timedelta(microseconds=parsed.microsecond)
|
||||
return self.enforce_timezone(parsed)
|
||||
else:
|
||||
try:
|
||||
|
@ -1153,6 +1155,10 @@ class DateTimeField(Field):
|
|||
if value.endswith('+00:00'):
|
||||
value = value[:-6] + 'Z'
|
||||
return value
|
||||
if output_format.lower() == ISO_8601_STRICT:
|
||||
value = value - datetime.timedelta(microseconds=value.microsecond)
|
||||
return value.isoformat()
|
||||
|
||||
return value.strftime(output_format)
|
||||
|
||||
|
||||
|
@ -1243,13 +1249,15 @@ class TimeField(Field):
|
|||
return value
|
||||
|
||||
for input_format in input_formats:
|
||||
if input_format.lower() == ISO_8601:
|
||||
if input_format.lower() in (ISO_8601, ISO_8601_STRICT):
|
||||
try:
|
||||
parsed = parse_time(value)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
else:
|
||||
if parsed is not None:
|
||||
if input_format.lower() == ISO_8601_STRICT:
|
||||
return parsed.replace(microsecond=0)
|
||||
return parsed
|
||||
else:
|
||||
try:
|
||||
|
@ -1282,6 +1290,10 @@ class TimeField(Field):
|
|||
|
||||
if output_format.lower() == ISO_8601:
|
||||
return value.isoformat()
|
||||
if output_format.lower() == ISO_8601_STRICT:
|
||||
if value.microsecond:
|
||||
value = value.replace(microsecond=0)
|
||||
return value.isoformat()
|
||||
return value.strftime(output_format)
|
||||
|
||||
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
"""
|
||||
Helper functions that convert strftime formats into more readable representations.
|
||||
"""
|
||||
from rest_framework import ISO_8601
|
||||
from rest_framework import ISO_8601, ISO_8601_STRICT
|
||||
|
||||
|
||||
def datetime_formats(formats):
|
||||
format = ', '.join(formats).replace(
|
||||
ISO_8601_STRICT,
|
||||
'YYYY-MM-DDThh:mm[:ss][+HH:MM|-HH:MM|Z]'
|
||||
).replace(
|
||||
ISO_8601,
|
||||
'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z]'
|
||||
)
|
||||
|
@ -18,7 +21,8 @@ def date_formats(formats):
|
|||
|
||||
|
||||
def time_formats(formats):
|
||||
format = ', '.join(formats).replace(ISO_8601, 'hh:mm[:ss[.uuuuuu]]')
|
||||
format = ', '.join(formats).replace(ISO_8601_STRICT, 'hh:mm[:ss]') \
|
||||
.replace(ISO_8601, 'hh:mm[:ss[.uuuuuu]]')
|
||||
return humanize_strptime(format)
|
||||
|
||||
|
||||
|
|
|
@ -1130,10 +1130,15 @@ class TestDateTimeField(FieldValues):
|
|||
"""
|
||||
valid_inputs = {
|
||||
'2001-01-01 13:00': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
|
||||
'2001-01-01 13:00:00.123456': datetime.datetime(2001, 1, 1, 13, 00, 00, 123456, tzinfo=timezone.UTC()),
|
||||
'2001-01-01T13:00': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
|
||||
'2001-01-01T13:00:00.123456': datetime.datetime(2001, 1, 1, 13, 00, 00, 123456, tzinfo=timezone.UTC()),
|
||||
'2001-01-01T13:00Z': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
|
||||
'2001-01-01T13:00:00.123456Z': datetime.datetime(2001, 1, 1, 13, 00, 00, 123456, tzinfo=timezone.UTC()),
|
||||
datetime.datetime(2001, 1, 1, 13, 00): datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
|
||||
datetime.datetime(2001, 1, 1, 13, 00, 00, 123456): datetime.datetime(2001, 1, 1, 13, 00, 00, 123456, tzinfo=timezone.UTC()),
|
||||
datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()): datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
|
||||
datetime.datetime(2001, 1, 1, 13, 00, 00, 123456, tzinfo=timezone.UTC()): datetime.datetime(2001, 1, 1, 13, 00, 00, 123456, tzinfo=timezone.UTC()),
|
||||
# Django 1.4 does not support timezone string parsing.
|
||||
'2001-01-01T13:00Z': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC())
|
||||
}
|
||||
|
@ -1144,7 +1149,9 @@ class TestDateTimeField(FieldValues):
|
|||
}
|
||||
outputs = {
|
||||
datetime.datetime(2001, 1, 1, 13, 00): '2001-01-01T13:00:00',
|
||||
datetime.datetime(2001, 1, 1, 13, 00, 00, 123456): '2001-01-01T13:00:00.123456',
|
||||
datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()): '2001-01-01T13:00:00Z',
|
||||
datetime.datetime(2001, 1, 1, 13, 00, 00, 123456, tzinfo=timezone.UTC()): '2001-01-01T13:00:00.123456Z',
|
||||
'2001-01-01T00:00:00': '2001-01-01T00:00:00',
|
||||
six.text_type('2016-01-10T00:00:00'): '2016-01-10T00:00:00',
|
||||
None: None,
|
||||
|
@ -1153,6 +1160,41 @@ class TestDateTimeField(FieldValues):
|
|||
field = serializers.DateTimeField(default_timezone=timezone.UTC())
|
||||
|
||||
|
||||
class TestIso8601StrictDateTimeField(FieldValues):
|
||||
valid_inputs = {
|
||||
'2001-01-01 13:00': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
|
||||
'2001-01-01 13:00:00.123456': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
|
||||
'2001-01-01T13:00': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
|
||||
'2001-01-01T13:00:00.123456': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
|
||||
'2001-01-01T13:00Z': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
|
||||
'2001-01-01T13:00:00.123456Z': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
|
||||
datetime.datetime(2001, 1, 1, 13, 00): datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
|
||||
datetime.datetime(2001, 1, 1, 13, 00, 00, 123456): datetime.datetime(2001, 1, 1, 13, 00, 00, 123456, tzinfo=timezone.UTC()),
|
||||
datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()): datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
|
||||
datetime.datetime(2001, 1, 1, 13, 00, 00, 123456, tzinfo=timezone.UTC()): datetime.datetime(2001, 1, 1, 13, 00, 00, 123456, tzinfo=timezone.UTC()),
|
||||
# Django 1.4 does not support timezone string parsing.
|
||||
'2001-01-01T13:00Z': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC())
|
||||
}
|
||||
invalid_inputs = {
|
||||
'abc': ['Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss][+HH:MM|-HH:MM|Z].'],
|
||||
'2001-99-99T99:00': ['Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss][+HH:MM|-HH:MM|Z].'],
|
||||
datetime.date(2001, 1, 1): ['Expected a datetime but got a date.'],
|
||||
}
|
||||
outputs = {
|
||||
datetime.datetime(2001, 1, 1, 13, 00): '2001-01-01T13:00:00',
|
||||
datetime.datetime(2001, 1, 1, 13, 00, 00, 123456): '2001-01-01T13:00:00',
|
||||
datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()): '2001-01-01T13:00:00+00:00',
|
||||
datetime.datetime(2001, 1, 1, 13, 00, 00, 123456, tzinfo=timezone.UTC()): '2001-01-01T13:00:00+00:00',
|
||||
'2001-01-01T00:00:00': '2001-01-01T00:00:00',
|
||||
six.text_type('2016-01-10T00:00:00'): '2016-01-10T00:00:00',
|
||||
None: None,
|
||||
'': None,
|
||||
}
|
||||
|
||||
field = serializers.DateTimeField(format=rest_framework.ISO_8601_STRICT, input_formats=[rest_framework.ISO_8601_STRICT],
|
||||
default_timezone=timezone.UTC())
|
||||
|
||||
|
||||
class TestCustomInputFormatDateTimeField(FieldValues):
|
||||
"""
|
||||
Valid and invalid values for `DateTimeField` with a custom input format.
|
||||
|
@ -1210,7 +1252,9 @@ class TestTimeField(FieldValues):
|
|||
"""
|
||||
valid_inputs = {
|
||||
'13:00': datetime.time(13, 00),
|
||||
'13:00:00.123456': datetime.time(13, 00, 00, 123456),
|
||||
datetime.time(13, 00): datetime.time(13, 00),
|
||||
datetime.time(13, 00, 00, 123456): datetime.time(13, 00, 00, 123456),
|
||||
}
|
||||
invalid_inputs = {
|
||||
'abc': ['Time has wrong format. Use one of these formats instead: hh:mm[:ss[.uuuuuu]].'],
|
||||
|
@ -1218,14 +1262,44 @@ class TestTimeField(FieldValues):
|
|||
}
|
||||
outputs = {
|
||||
datetime.time(13, 0): '13:00:00',
|
||||
datetime.time(13, 0, 0, 123456): '13:00:00.123456',
|
||||
datetime.time(0, 0): '00:00:00',
|
||||
datetime.time(0, 0, 0, 0): '00:00:00',
|
||||
'00:00:00': '00:00:00',
|
||||
'00:00:00.000000': '00:00:00.000000',
|
||||
None: None,
|
||||
'': None,
|
||||
}
|
||||
field = serializers.TimeField()
|
||||
|
||||
|
||||
class TestIso8601StrictTimeField(FieldValues):
|
||||
"""
|
||||
Valid and invalid values for `TimeField`.
|
||||
"""
|
||||
valid_inputs = {
|
||||
'13:00': datetime.time(13, 00),
|
||||
'13:00:00.123456': datetime.time(13, 00),
|
||||
datetime.time(13, 00): datetime.time(13, 00),
|
||||
datetime.time(13, 00, 00, 123456): datetime.time(13, 00, 00, 123456),
|
||||
}
|
||||
invalid_inputs = {
|
||||
'abc': ['Time has wrong format. Use one of these formats instead: hh:mm[:ss].'],
|
||||
'99:99': ['Time has wrong format. Use one of these formats instead: hh:mm[:ss].'],
|
||||
}
|
||||
outputs = {
|
||||
datetime.time(13, 0): '13:00:00',
|
||||
datetime.time(13, 0, 0, 123456): '13:00:00',
|
||||
datetime.time(0, 0): '00:00:00',
|
||||
datetime.time(0, 0, 0, 0): '00:00:00',
|
||||
'00:00:00': '00:00:00',
|
||||
'00:00:00.000000': '00:00:00.000000',
|
||||
None: None,
|
||||
'': None,
|
||||
}
|
||||
field = serializers.TimeField(format=rest_framework.ISO_8601_STRICT, input_formats=[rest_framework.ISO_8601_STRICT])
|
||||
|
||||
|
||||
class TestCustomInputFormatTimeField(FieldValues):
|
||||
"""
|
||||
Valid and invalid values for `TimeField` with a custom input format.
|
||||
|
|
Loading…
Reference in New Issue
Block a user