diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 5cb096f1c0..8d25d6c78e 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -360,7 +360,10 @@ Corresponds to `django.db.models.fields.DurationField` 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()` +**Signature:** `DurationField(max_value=None, min_value=None)` + +- `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. --- diff --git a/rest_framework/fields.py b/rest_framework/fields.py index c13279675b..d6e3633392 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1354,8 +1354,27 @@ def to_representation(self, value): class DurationField(Field): default_error_messages = { 'invalid': _('Duration has wrong format. Use one of these formats instead: {format}.'), + 'max_value': _('Ensure this value is less than or equal to {max_value}.'), + 'min_value': _('Ensure this value is greater than or equal to {min_value}.'), } + def __init__(self, **kwargs): + self.max_value = kwargs.pop('max_value', None) + self.min_value = kwargs.pop('min_value', None) + super(DurationField, self).__init__(**kwargs) + if self.max_value is not None: + message = lazy( + self.error_messages['max_value'].format, + six.text_type)(max_value=self.max_value) + self.validators.append( + MaxValueValidator(self.max_value, message=message)) + if self.min_value is not None: + message = lazy( + self.error_messages['min_value'].format, + six.text_type)(min_value=self.min_value) + self.validators.append( + MinValueValidator(self.min_value, message=message)) + def to_internal_value(self, value): if isinstance(value, datetime.timedelta): return value diff --git a/rest_framework/utils/field_mapping.py b/rest_framework/utils/field_mapping.py index 722981b203..50de3f1254 100644 --- a/rest_framework/utils/field_mapping.py +++ b/rest_framework/utils/field_mapping.py @@ -12,7 +12,7 @@ from rest_framework.validators import UniqueValidator NUMERIC_FIELD_TYPES = ( - models.IntegerField, models.FloatField, models.DecimalField + models.IntegerField, models.FloatField, models.DecimalField, models.DurationField, ) diff --git a/tests/test_fields.py b/tests/test_fields.py index 0ee49e9c16..7227c2f5a8 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1459,6 +1459,23 @@ class TestNoOutputFormatTimeField(FieldValues): field = serializers.TimeField(format=None) +class TestMinMaxDurationField(FieldValues): + """ + Valid and invalid values for `DurationField` with min and max limits. + """ + valid_inputs = { + '3 08:32:01.000123': datetime.timedelta(days=3, hours=8, minutes=32, seconds=1, microseconds=123), + 86401: datetime.timedelta(days=1, seconds=1), + } + invalid_inputs = { + 3600: ['Ensure this value is greater than or equal to 1 day, 0:00:00.'], + '4 08:32:01.000123': ['Ensure this value is less than or equal to 4 days, 0:00:00.'], + '3600': ['Ensure this value is greater than or equal to 1 day, 0:00:00.'], + } + outputs = {} + field = serializers.DurationField(min_value=datetime.timedelta(days=1), max_value=datetime.timedelta(days=4)) + + class TestDurationField(FieldValues): """ Valid and invalid values for `DurationField`. diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index e4fc8b37f6..d865350fba 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -7,6 +7,7 @@ """ from __future__ import unicode_literals +import datetime import decimal from collections import OrderedDict @@ -16,7 +17,6 @@ MaxValueValidator, MinLengthValidator, MinValueValidator ) from django.db import models -from django.db.models import DurationField as ModelDurationField from django.test import TestCase from django.utils import six @@ -349,7 +349,7 @@ class DurationFieldModel(models.Model): """ A model that defines DurationField. """ - duration_field = ModelDurationField() + duration_field = models.DurationField() class TestSerializer(serializers.ModelSerializer): class Meta: @@ -363,6 +363,27 @@ class Meta: """) self.assertEqual(unicode_repr(TestSerializer()), expected) + def test_duration_field_with_validators(self): + class ValidatedDurationFieldModel(models.Model): + """ + A model that defines DurationField with validators. + """ + duration_field = models.DurationField( + validators=[MinValueValidator(datetime.timedelta(days=1)), MaxValueValidator(datetime.timedelta(days=3))] + ) + + class TestSerializer(serializers.ModelSerializer): + class Meta: + model = ValidatedDurationFieldModel + fields = '__all__' + + expected = dedent(""" + TestSerializer(): + id = IntegerField(label='ID', read_only=True) + duration_field = DurationField(max_value=datetime.timedelta(3), min_value=datetime.timedelta(1)) + """) + self.assertEqual(unicode_repr(TestSerializer()), expected) + class TestGenericIPAddressFieldValidation(TestCase): def test_ip_address_validation(self):
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: