Add DurationField

This commit is contained in:
Nicolas Delaby 2015-06-01 18:20:53 +02:00
parent a0f66ffc69
commit f701ecceb7
6 changed files with 99 additions and 3 deletions

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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):

View File

@ -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.
# ------------------------------------ # ------------------------------------