Skip to content

Commit 96ce8ea

Browse files
noamkushcarltongibson
authored andcommitted
Split min_value/max_value field arguments to a base class and added them to DurationField.
PR fixes. Python 2 fix.
1 parent d2994e0 commit 96ce8ea

File tree

3 files changed

+36
-48
lines changed

3 files changed

+36
-48
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: 15 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -903,20 +903,16 @@ def to_internal_value(self, data):
903903

904904
# Number types...
905905

906-
class IntegerField(Field):
906+
class MaxMinMixin(object):
907907
default_error_messages = {
908-
'invalid': _('A valid integer is required.'),
909908
'max_value': _('Ensure this value is less than or equal to {max_value}.'),
910909
'min_value': _('Ensure this value is greater than or equal to {min_value}.'),
911-
'max_string_length': _('String value too large.')
912910
}
913-
MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs.
914-
re_decimal = re.compile(r'\.0*\s*$') # allow e.g. '1.0' as an int, but not '1.2'
915911

916912
def __init__(self, **kwargs):
917913
self.max_value = kwargs.pop('max_value', None)
918914
self.min_value = kwargs.pop('min_value', None)
919-
super(IntegerField, self).__init__(**kwargs)
915+
super(MaxMinMixin, self).__init__(**kwargs)
920916
if self.max_value is not None:
921917
message = lazy(
922918
self.error_messages['max_value'].format,
@@ -930,6 +926,15 @@ def __init__(self, **kwargs):
930926
self.validators.append(
931927
MinValueValidator(self.min_value, message=message))
932928

929+
930+
class IntegerField(MaxMinMixin, Field):
931+
default_error_messages = {
932+
'invalid': _('A valid integer is required.'),
933+
'max_string_length': _('String value too large.')
934+
}
935+
MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs.
936+
re_decimal = re.compile(r'\.0*\s*$') # allow e.g. '1.0' as an int, but not '1.2'
937+
933938
def to_internal_value(self, data):
934939
if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH:
935940
self.fail('max_string_length')
@@ -944,32 +949,13 @@ def to_representation(self, value):
944949
return int(value)
945950

946951

947-
class FloatField(Field):
952+
class FloatField(MaxMinMixin, Field):
948953
default_error_messages = {
949954
'invalid': _('A valid number is required.'),
950-
'max_value': _('Ensure this value is less than or equal to {max_value}.'),
951-
'min_value': _('Ensure this value is greater than or equal to {min_value}.'),
952955
'max_string_length': _('String value too large.')
953956
}
954957
MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs.
955958

956-
def __init__(self, **kwargs):
957-
self.max_value = kwargs.pop('max_value', None)
958-
self.min_value = kwargs.pop('min_value', None)
959-
super(FloatField, self).__init__(**kwargs)
960-
if self.max_value is not None:
961-
message = lazy(
962-
self.error_messages['max_value'].format,
963-
six.text_type)(max_value=self.max_value)
964-
self.validators.append(
965-
MaxValueValidator(self.max_value, message=message))
966-
if self.min_value is not None:
967-
message = lazy(
968-
self.error_messages['min_value'].format,
969-
six.text_type)(min_value=self.min_value)
970-
self.validators.append(
971-
MinValueValidator(self.min_value, message=message))
972-
973959
def to_internal_value(self, data):
974960

975961
if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH:
@@ -984,11 +970,9 @@ def to_representation(self, value):
984970
return float(value)
985971

986972

987-
class DecimalField(Field):
973+
class DecimalField(MaxMinMixin, Field):
988974
default_error_messages = {
989975
'invalid': _('A valid number is required.'),
990-
'max_value': _('Ensure this value is less than or equal to {max_value}.'),
991-
'min_value': _('Ensure this value is greater than or equal to {min_value}.'),
992976
'max_digits': _('Ensure that there are no more than {max_digits} digits in total.'),
993977
'max_decimal_places': _('Ensure that there are no more than {max_decimal_places} decimal places.'),
994978
'max_whole_digits': _('Ensure that there are no more than {max_whole_digits} digits before the decimal point.'),
@@ -1006,28 +990,12 @@ def __init__(self, max_digits, decimal_places, coerce_to_string=None, max_value=
1006990
if self.localize:
1007991
self.coerce_to_string = True
1008992

1009-
self.max_value = max_value
1010-
self.min_value = min_value
1011-
1012993
if self.max_digits is not None and self.decimal_places is not None:
1013994
self.max_whole_digits = self.max_digits - self.decimal_places
1014995
else:
1015996
self.max_whole_digits = None
1016997

1017-
super(DecimalField, self).__init__(**kwargs)
1018-
1019-
if self.max_value is not None:
1020-
message = lazy(
1021-
self.error_messages['max_value'].format,
1022-
six.text_type)(max_value=self.max_value)
1023-
self.validators.append(
1024-
MaxValueValidator(self.max_value, message=message))
1025-
if self.min_value is not None:
1026-
message = lazy(
1027-
self.error_messages['min_value'].format,
1028-
six.text_type)(min_value=self.min_value)
1029-
self.validators.append(
1030-
MinValueValidator(self.min_value, message=message))
998+
super(DecimalField, self).__init__(max_value=max_value, min_value=min_value, **kwargs)
1031999

10321000
if rounding is not None:
10331001
valid_roundings = [v for k, v in vars(decimal).items() if k.startswith('ROUND_')]
@@ -1351,7 +1319,7 @@ def to_representation(self, value):
13511319
return value.strftime(output_format)
13521320

13531321

1354-
class DurationField(Field):
1322+
class DurationField(MaxMinMixin, Field):
13551323
default_error_messages = {
13561324
'invalid': _('Duration has wrong format. Use one of these formats instead: {format}.'),
13571325
}

tests/test_fields.py

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

14031403

1404+
class TestMinMaxDurationField(FieldValues):
1405+
"""
1406+
Valid and invalid values for `IntegerField` with min and max limits.
1407+
"""
1408+
valid_inputs = {
1409+
'3 08:32:01.000123': datetime.timedelta(days=3, hours=8, minutes=32, seconds=1, microseconds=123),
1410+
86401: datetime.timedelta(days=1, seconds=1),
1411+
}
1412+
invalid_inputs = {
1413+
3600: ['Ensure this value is greater than or equal to 1 day, 0:00:00.'],
1414+
'4 08:32:01.000123': ['Ensure this value is less than or equal to 4 days, 0:00:00.'],
1415+
'3600': ['Ensure this value is greater than or equal to 1 day, 0:00:00.'],
1416+
}
1417+
outputs = {}
1418+
field = serializers.DurationField(min_value=datetime.timedelta(days=1), max_value=datetime.timedelta(days=4))
1419+
1420+
14041421
class TestDurationField(FieldValues):
14051422
"""
14061423
Valid and invalid values for `DurationField`.

0 commit comments

Comments
 (0)