Skip to content

Commit 7268643

Browse files
noamkushcarltongibson
authored andcommitted
min_value/max_value support in DurationField (#5643)
* Added min_value/max_value field arguments to DurationField. * Made field mapping use mix/max kwargs for DurationField validators.
1 parent 7d64b70 commit 7268643

File tree

5 files changed

+64
-4
lines changed

5 files changed

+64
-4
lines changed

docs/api-guide/fields.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,10 @@ Corresponds to `django.db.models.fields.DurationField`
360360
The `validated_data` for these fields will contain a `datetime.timedelta` instance.
361361
The representation is a string following this format `'[DD] [HH:[MM:]]ss[.uuuuuu]'`.
362362

363-
**Signature:** `DurationField()`
363+
**Signature:** `DurationField(max_value=None, min_value=None)`
364+
365+
- `max_value` Validate that the duration provided is no greater than this value.
366+
- `min_value` Validate that the duration provided is no less than this value.
364367

365368
---
366369

rest_framework/fields.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1354,8 +1354,27 @@ def to_representation(self, value):
13541354
class DurationField(Field):
13551355
default_error_messages = {
13561356
'invalid': _('Duration has wrong format. Use one of these formats instead: {format}.'),
1357+
'max_value': _('Ensure this value is less than or equal to {max_value}.'),
1358+
'min_value': _('Ensure this value is greater than or equal to {min_value}.'),
13571359
}
13581360

1361+
def __init__(self, **kwargs):
1362+
self.max_value = kwargs.pop('max_value', None)
1363+
self.min_value = kwargs.pop('min_value', None)
1364+
super(DurationField, self).__init__(**kwargs)
1365+
if self.max_value is not None:
1366+
message = lazy(
1367+
self.error_messages['max_value'].format,
1368+
six.text_type)(max_value=self.max_value)
1369+
self.validators.append(
1370+
MaxValueValidator(self.max_value, message=message))
1371+
if self.min_value is not None:
1372+
message = lazy(
1373+
self.error_messages['min_value'].format,
1374+
six.text_type)(min_value=self.min_value)
1375+
self.validators.append(
1376+
MinValueValidator(self.min_value, message=message))
1377+
13591378
def to_internal_value(self, value):
13601379
if isinstance(value, datetime.timedelta):
13611380
return value

rest_framework/utils/field_mapping.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from rest_framework.validators import UniqueValidator
1313

1414
NUMERIC_FIELD_TYPES = (
15-
models.IntegerField, models.FloatField, models.DecimalField
15+
models.IntegerField, models.FloatField, models.DecimalField, models.DurationField,
1616
)
1717

1818

tests/test_fields.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1459,6 +1459,23 @@ class TestNoOutputFormatTimeField(FieldValues):
14591459
field = serializers.TimeField(format=None)
14601460

14611461

1462+
class TestMinMaxDurationField(FieldValues):
1463+
"""
1464+
Valid and invalid values for `DurationField` with min and max limits.
1465+
"""
1466+
valid_inputs = {
1467+
'3 08:32:01.000123': datetime.timedelta(days=3, hours=8, minutes=32, seconds=1, microseconds=123),
1468+
86401: datetime.timedelta(days=1, seconds=1),
1469+
}
1470+
invalid_inputs = {
1471+
3600: ['Ensure this value is greater than or equal to 1 day, 0:00:00.'],
1472+
'4 08:32:01.000123': ['Ensure this value is less than or equal to 4 days, 0:00:00.'],
1473+
'3600': ['Ensure this value is greater than or equal to 1 day, 0:00:00.'],
1474+
}
1475+
outputs = {}
1476+
field = serializers.DurationField(min_value=datetime.timedelta(days=1), max_value=datetime.timedelta(days=4))
1477+
1478+
14621479
class TestDurationField(FieldValues):
14631480
"""
14641481
Valid and invalid values for `DurationField`.

tests/test_model_serializer.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"""
88
from __future__ import unicode_literals
99

10+
import datetime
1011
import decimal
1112
from collections import OrderedDict
1213

@@ -16,7 +17,6 @@
1617
MaxValueValidator, MinLengthValidator, MinValueValidator
1718
)
1819
from django.db import models
19-
from django.db.models import DurationField as ModelDurationField
2020
from django.test import TestCase
2121
from django.utils import six
2222

@@ -349,7 +349,7 @@ class DurationFieldModel(models.Model):
349349
"""
350350
A model that defines DurationField.
351351
"""
352-
duration_field = ModelDurationField()
352+
duration_field = models.DurationField()
353353

354354
class TestSerializer(serializers.ModelSerializer):
355355
class Meta:
@@ -363,6 +363,27 @@ class Meta:
363363
""")
364364
self.assertEqual(unicode_repr(TestSerializer()), expected)
365365

366+
def test_duration_field_with_validators(self):
367+
class ValidatedDurationFieldModel(models.Model):
368+
"""
369+
A model that defines DurationField with validators.
370+
"""
371+
duration_field = models.DurationField(
372+
validators=[MinValueValidator(datetime.timedelta(days=1)), MaxValueValidator(datetime.timedelta(days=3))]
373+
)
374+
375+
class TestSerializer(serializers.ModelSerializer):
376+
class Meta:
377+
model = ValidatedDurationFieldModel
378+
fields = '__all__'
379+
380+
expected = dedent("""
381+
TestSerializer():
382+
id = IntegerField(label='ID', read_only=True)
383+
duration_field = DurationField(max_value=datetime.timedelta(3), min_value=datetime.timedelta(1))
384+
""")
385+
self.assertEqual(unicode_repr(TestSerializer()), expected)
386+
366387

367388
class TestGenericIPAddressFieldValidation(TestCase):
368389
def test_ip_address_validation(self):

0 commit comments

Comments
 (0)