mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-16 19:41:06 +03:00
Add DurationField
This commit is contained in:
parent
a0f66ffc69
commit
f701ecceb7
|
@ -302,6 +302,18 @@ Corresponds to `django.db.models.fields.TimeField`
|
||||||
|
|
||||||
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 string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style times should be used. (eg `'12:34:56.000000'`)
|
||||||
|
|
||||||
|
## DurationField
|
||||||
|
|
||||||
|
A Duration representation.
|
||||||
|
Corresponds to `django.db.models.fields.Duration`
|
||||||
|
|
||||||
|
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]'`.
|
||||||
|
|
||||||
|
**Note:** This field is only available with Django versions >= 1.8.
|
||||||
|
|
||||||
|
**Signature:** `DurationField()`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Choice selection fields
|
# Choice selection fields
|
||||||
|
|
|
@ -258,3 +258,11 @@ else:
|
||||||
SHORT_SEPARATORS = (b',', b':')
|
SHORT_SEPARATORS = (b',', b':')
|
||||||
LONG_SEPARATORS = (b', ', b': ')
|
LONG_SEPARATORS = (b', ', b': ')
|
||||||
INDENT_SEPARATORS = (b',', b': ')
|
INDENT_SEPARATORS = (b',', b': ')
|
||||||
|
|
||||||
|
|
||||||
|
if django.VERSION >= (1, 8):
|
||||||
|
from django.db.models import DurationField
|
||||||
|
from django.utils.dateparse import parse_duration
|
||||||
|
from django.utils.duration import duration_string
|
||||||
|
else:
|
||||||
|
DurationField = duration_string = parse_duration = None
|
||||||
|
|
|
@ -12,7 +12,7 @@ from rest_framework import ISO_8601
|
||||||
from rest_framework.compat import (
|
from rest_framework.compat import (
|
||||||
EmailValidator, MinValueValidator, MaxValueValidator,
|
EmailValidator, MinValueValidator, MaxValueValidator,
|
||||||
MinLengthValidator, MaxLengthValidator, URLValidator, OrderedDict,
|
MinLengthValidator, MaxLengthValidator, URLValidator, OrderedDict,
|
||||||
unicode_repr, unicode_to_repr
|
unicode_repr, unicode_to_repr, parse_duration, duration_string,
|
||||||
)
|
)
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
|
@ -1003,6 +1003,29 @@ class TimeField(Field):
|
||||||
return value.strftime(self.format)
|
return value.strftime(self.format)
|
||||||
|
|
||||||
|
|
||||||
|
class DurationField(Field):
|
||||||
|
default_error_messages = {
|
||||||
|
'invalid': _('Duration has wrong format. Use one of these formats instead: {format}.'),
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
if parse_duration is None:
|
||||||
|
raise NotImplementedError(
|
||||||
|
'DurationField not supported for django versions prior to 1.8')
|
||||||
|
return super(DurationField, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def to_internal_value(self, value):
|
||||||
|
if isinstance(value, datetime.timedelta):
|
||||||
|
return value
|
||||||
|
parsed = parse_duration(value)
|
||||||
|
if parsed is not None:
|
||||||
|
return parsed
|
||||||
|
self.fail('invalid', format='[DD] [HH:[MM:]]ss[.uuuuuu]')
|
||||||
|
|
||||||
|
def to_representation(self, value):
|
||||||
|
return duration_string(value)
|
||||||
|
|
||||||
|
|
||||||
# Choice types...
|
# Choice types...
|
||||||
|
|
||||||
class ChoiceField(Field):
|
class ChoiceField(Field):
|
||||||
|
|
|
@ -15,7 +15,11 @@ from django.db import models
|
||||||
from django.db.models.fields import FieldDoesNotExist, Field as DjangoModelField
|
from django.db.models.fields import FieldDoesNotExist, Field as DjangoModelField
|
||||||
from django.db.models import query
|
from django.db.models import query
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework.compat import postgres_fields, unicode_to_repr
|
from rest_framework.compat import (
|
||||||
|
postgres_fields,
|
||||||
|
unicode_to_repr,
|
||||||
|
DurationField as ModelDurationField,
|
||||||
|
)
|
||||||
from rest_framework.utils import model_meta
|
from rest_framework.utils import model_meta
|
||||||
from rest_framework.utils.field_mapping import (
|
from rest_framework.utils.field_mapping import (
|
||||||
get_url_kwargs, get_field_kwargs,
|
get_url_kwargs, get_field_kwargs,
|
||||||
|
@ -731,6 +735,8 @@ class ModelSerializer(Serializer):
|
||||||
models.TimeField: TimeField,
|
models.TimeField: TimeField,
|
||||||
models.URLField: URLField,
|
models.URLField: URLField,
|
||||||
}
|
}
|
||||||
|
if ModelDurationField is not None:
|
||||||
|
serializer_field_mapping[ModelDurationField] = DurationField
|
||||||
serializer_related_field = PrimaryKeyRelatedField
|
serializer_related_field = PrimaryKeyRelatedField
|
||||||
serializer_url_field = HyperlinkedIdentityField
|
serializer_url_field = HyperlinkedIdentityField
|
||||||
serializer_choice_field = ChoiceField
|
serializer_choice_field = ChoiceField
|
||||||
|
|
|
@ -905,6 +905,29 @@ class TestNoOutputFormatTimeField(FieldValues):
|
||||||
field = serializers.TimeField(format=None)
|
field = serializers.TimeField(format=None)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(django.VERSION < (1, 8),
|
||||||
|
reason='DurationField is only available for django1.8+')
|
||||||
|
class TestDurationField(FieldValues):
|
||||||
|
"""
|
||||||
|
Valid and invalid values for `DurationField`.
|
||||||
|
"""
|
||||||
|
valid_inputs = {
|
||||||
|
'13': datetime.timedelta(seconds=13),
|
||||||
|
'3 08:32:01.000123': datetime.timedelta(days=3, hours=8, minutes=32, seconds=1, microseconds=123),
|
||||||
|
'08:01': datetime.timedelta(minutes=8, seconds=1),
|
||||||
|
datetime.timedelta(days=3, hours=8, minutes=32, seconds=1, microseconds=123): datetime.timedelta(days=3, hours=8, minutes=32, seconds=1, microseconds=123),
|
||||||
|
}
|
||||||
|
invalid_inputs = {
|
||||||
|
'abc': ['Duration has wrong format. Use one of these formats instead: [DD] [HH:[MM:]]ss[.uuuuuu].'],
|
||||||
|
'3 08:32 01.123': ['Duration has wrong format. Use one of these formats instead: [DD] [HH:[MM:]]ss[.uuuuuu].'],
|
||||||
|
}
|
||||||
|
outputs = {
|
||||||
|
datetime.timedelta(days=3, hours=8, minutes=32, seconds=1, microseconds=123): '3 08:32:01.000123',
|
||||||
|
}
|
||||||
|
if django.VERSION >= (1, 8):
|
||||||
|
field = serializers.DurationField()
|
||||||
|
|
||||||
|
|
||||||
# Choice types...
|
# Choice types...
|
||||||
|
|
||||||
class TestChoiceField(FieldValues):
|
class TestChoiceField(FieldValues):
|
||||||
|
|
|
@ -6,13 +6,15 @@ These tests deal with ensuring that we correctly map the model fields onto
|
||||||
an appropriate set of serializer fields for each case.
|
an appropriate set of serializer fields for each case.
|
||||||
"""
|
"""
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
import django
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator, MinLengthValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator, MinLengthValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
import pytest
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.compat import unicode_repr
|
from rest_framework.compat import unicode_repr, DurationField as ModelDurationField
|
||||||
|
|
||||||
|
|
||||||
def dedent(blocktext):
|
def dedent(blocktext):
|
||||||
|
@ -284,6 +286,28 @@ class TestRegularFieldMappings(TestCase):
|
||||||
ChildSerializer().fields
|
ChildSerializer().fields
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(django.VERSION < (1, 8),
|
||||||
|
reason='DurationField is only available for django1.8+')
|
||||||
|
class TestDurationFieldMapping(TestCase):
|
||||||
|
def test_duration_field(self):
|
||||||
|
class DurationFieldModel(models.Model):
|
||||||
|
"""
|
||||||
|
A model that defines DurationField.
|
||||||
|
"""
|
||||||
|
duration_field = ModelDurationField()
|
||||||
|
|
||||||
|
class TestSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = DurationFieldModel
|
||||||
|
|
||||||
|
expected = dedent("""
|
||||||
|
TestSerializer():
|
||||||
|
id = IntegerField(label='ID', read_only=True)
|
||||||
|
duration_field = DurationField()
|
||||||
|
""")
|
||||||
|
self.assertEqual(unicode_repr(TestSerializer()), expected)
|
||||||
|
|
||||||
|
|
||||||
# Tests for relational field mappings.
|
# Tests for relational field mappings.
|
||||||
# ------------------------------------
|
# ------------------------------------
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user