Allow format duration as ISO-8601

This commit is contained in:
sevdog 2022-06-24 10:59:20 +02:00
parent ccfe0a9637
commit bcc4ebfa81
No known key found for this signature in database
GPG Key ID: D939AF7A93A9C178
6 changed files with 54 additions and 7 deletions

View File

@ -377,13 +377,16 @@ A Duration representation.
Corresponds to `django.db.models.fields.DurationField` Corresponds to `django.db.models.fields.DurationField`
The `validated_data` for these fields will contain a `datetime.timedelta` instance. The `validated_data` for these fields will contain a `datetime.timedelta` instance.
The representation is a string following this format `'[DD] [HH:[MM:]]ss[.uuuuuu]'`.
**Signature:** `DurationField(max_value=None, min_value=None)` **Signature:** `DurationField(format=api_settings.DURATION_FORMAT, max_value=None, min_value=None)`
* `format` - A string representing the output format. If not specified, this defaults to the same value as the `DURATION_FORMAT` settings key, which will be `'standard'` unless set. Setting to a format string indicates that `to_representation` return values should be coerced to string output. Format strings are described below. Setting this value to `None` indicates that Python `timedelta` objects should be returned by `to_representation`. In this case the date encoding will be determined by the renderer.
* `max_value` Validate that the duration provided is no greater than this value. * `max_value` Validate that the duration provided is no greater than this value.
* `min_value` Validate that the duration provided is no less than this value. * `min_value` Validate that the duration provided is no less than this value.
#### `DurationField` format strings
Format strings may either be the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style intervals should be used (eg `'P4DT1H15M20S'`), or the special string `'standard'`, which indicates that Django interval format `'[DD] [HH:[MM:]]ss[.uuuuuu]'` should be used (eg: `'4 1:15:20'`).
--- ---
# Choice selection fields # Choice selection fields

View File

@ -314,6 +314,15 @@ May be a list including the string `'iso-8601'` or Python [strftime format][strf
Default: `['iso-8601']` Default: `['iso-8601']`
#### DURATION_FORMAT
A format string that should be used by default for rendering the output of `DurationField` serializer fields. If `None`, then `DurationField` serializer fields will return Python `timedelta` objects, and the duration encoding will be determined by the renderer.
May be any of `None`, `'iso-8601'` or `'standard'` (the format accepted by `django.utils.dateparse.parse_duration`).
Default: `'standard'`
--- ---
## Encodings ## Encodings

View File

@ -21,6 +21,7 @@ HTTP_HEADER_ENCODING = 'iso-8859-1'
# Default datetime input and output formats # Default datetime input and output formats
ISO_8601 = 'iso-8601' ISO_8601 = 'iso-8601'
STD_DURATION = 'standard'
class RemovedInDRF316Warning(DeprecationWarning): class RemovedInDRF316Warning(DeprecationWarning):

View File

@ -24,7 +24,7 @@ from django.utils import timezone
from django.utils.dateparse import ( from django.utils.dateparse import (
parse_date, parse_datetime, parse_duration, parse_time parse_date, parse_datetime, parse_duration, parse_time
) )
from django.utils.duration import duration_string from django.utils.duration import duration_iso_string, duration_string
from django.utils.encoding import is_protected_type, smart_str from django.utils.encoding import is_protected_type, smart_str
from django.utils.formats import localize_input, sanitize_separators from django.utils.formats import localize_input, sanitize_separators
from django.utils.ipv6 import clean_ipv6_address from django.utils.ipv6 import clean_ipv6_address
@ -1351,9 +1351,11 @@ class DurationField(Field):
'overflow': _('The number of days must be between {min_days} and {max_days}.'), 'overflow': _('The number of days must be between {min_days} and {max_days}.'),
} }
def __init__(self, **kwargs): def __init__(self, format=empty, **kwargs):
self.max_value = kwargs.pop('max_value', None) self.max_value = kwargs.pop('max_value', None)
self.min_value = kwargs.pop('min_value', None) self.min_value = kwargs.pop('min_value', None)
if format is not empty:
self.format = format
super().__init__(**kwargs) super().__init__(**kwargs)
if self.max_value is not None: if self.max_value is not None:
message = lazy_format(self.error_messages['max_value'], max_value=self.max_value) message = lazy_format(self.error_messages['max_value'], max_value=self.max_value)
@ -1376,6 +1378,13 @@ class DurationField(Field):
self.fail('invalid', format='[DD] [HH:[MM:]]ss[.uuuuuu]') self.fail('invalid', format='[DD] [HH:[MM:]]ss[.uuuuuu]')
def to_representation(self, value): def to_representation(self, value):
output_format = getattr(self, 'format', api_settings.DURATION_FORMAT)
if output_format is None or isinstance(value, str):
return value
if output_format.lower() == ISO_8601:
return duration_iso_string(value)
return duration_string(value) return duration_string(value)

View File

@ -24,7 +24,7 @@ from django.conf import settings
from django.core.signals import setting_changed from django.core.signals import setting_changed
from django.utils.module_loading import import_string from django.utils.module_loading import import_string
from rest_framework import ISO_8601 from rest_framework import ISO_8601, STD_DURATION
DEFAULTS = { DEFAULTS = {
# Base API policies # Base API policies
@ -109,6 +109,8 @@ DEFAULTS = {
'TIME_FORMAT': ISO_8601, 'TIME_FORMAT': ISO_8601,
'TIME_INPUT_FORMATS': [ISO_8601], 'TIME_INPUT_FORMATS': [ISO_8601],
'DURATION_FORMAT': STD_DURATION,
# Encoding # Encoding
'UNICODE_JSON': True, 'UNICODE_JSON': True,
'COMPACT_JSON': True, 'COMPACT_JSON': True,

View File

@ -1782,8 +1782,31 @@ class TestDurationField(FieldValues):
field = serializers.DurationField() field = serializers.DurationField()
# Choice types... class TestNoOutputFormatDurationField(FieldValues):
"""
Values for `TimeField` with a no output format.
"""
valid_inputs = {}
invalid_inputs = {}
outputs = {
datetime.timedelta(1): datetime.timedelta(1)
}
field = serializers.DurationField(format=None)
class TestISOOutputFormatDurationField(FieldValues):
"""
Values for `TimeField` with a custom output format.
"""
valid_inputs = {}
invalid_inputs = {}
outputs = {
datetime.timedelta(days=3, hours=8, minutes=32, seconds=1, microseconds=123): 'P3DT08H32M01.000123S'
}
field = serializers.DurationField(format='iso-8601')
# Choice types...
class TestChoiceField(FieldValues): class TestChoiceField(FieldValues):
""" """
Valid and invalid values for `ChoiceField`. Valid and invalid values for `ChoiceField`.