From a4c9e4c911f1d9bbe9eb51d15e5c4953ea1a2d65 Mon Sep 17 00:00:00 2001 From: Noam Date: Fri, 1 Dec 2017 17:03:40 +0200 Subject: [PATCH 1/3] Split min_value/max_value field arguments to a base class and added them to DurationField. PR fixes. Python 2 fix. --- docs/api-guide/fields.md | 5 +++- rest_framework/fields.py | 62 ++++++++++------------------------------ tests/test_fields.py | 17 +++++++++++ 3 files changed, 36 insertions(+), 48 deletions(-) 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..6b32fd4964 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -903,20 +903,16 @@ def to_internal_value(self, data): # Number types... -class IntegerField(Field): +class MaxMinMixin(object): default_error_messages = { - 'invalid': _('A valid integer is required.'), '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}.'), - 'max_string_length': _('String value too large.') } - MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs. - re_decimal = re.compile(r'\.0*\s*$') # allow e.g. '1.0' as an int, but not '1.2' def __init__(self, **kwargs): self.max_value = kwargs.pop('max_value', None) self.min_value = kwargs.pop('min_value', None) - super(IntegerField, self).__init__(**kwargs) + super(MaxMinMixin, self).__init__(**kwargs) if self.max_value is not None: message = lazy( self.error_messages['max_value'].format, @@ -930,6 +926,15 @@ def __init__(self, **kwargs): self.validators.append( MinValueValidator(self.min_value, message=message)) + +class IntegerField(MaxMinMixin, Field): + default_error_messages = { + 'invalid': _('A valid integer is required.'), + 'max_string_length': _('String value too large.') + } + MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs. + re_decimal = re.compile(r'\.0*\s*$') # allow e.g. '1.0' as an int, but not '1.2' + def to_internal_value(self, data): if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH: self.fail('max_string_length') @@ -944,32 +949,13 @@ def to_representation(self, value): return int(value) -class FloatField(Field): +class FloatField(MaxMinMixin, Field): default_error_messages = { 'invalid': _('A valid number is required.'), - '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}.'), 'max_string_length': _('String value too large.') } MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs. - def __init__(self, **kwargs): - self.max_value = kwargs.pop('max_value', None) - self.min_value = kwargs.pop('min_value', None) - super(FloatField, 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, data): if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH: @@ -984,11 +970,9 @@ def to_representation(self, value): return float(value) -class DecimalField(Field): +class DecimalField(MaxMinMixin, Field): default_error_messages = { 'invalid': _('A valid number is required.'), - '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}.'), 'max_digits': _('Ensure that there are no more than {max_digits} digits in total.'), 'max_decimal_places': _('Ensure that there are no more than {max_decimal_places} decimal places.'), '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= if self.localize: self.coerce_to_string = True - self.max_value = max_value - self.min_value = min_value - if self.max_digits is not None and self.decimal_places is not None: self.max_whole_digits = self.max_digits - self.decimal_places else: self.max_whole_digits = None - super(DecimalField, 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)) + super(DecimalField, self).__init__(max_value=max_value, min_value=min_value, **kwargs) if rounding is not None: valid_roundings = [v for k, v in vars(decimal).items() if k.startswith('ROUND_')] @@ -1351,7 +1319,7 @@ def to_representation(self, value): return value.strftime(output_format) -class DurationField(Field): +class DurationField(MaxMinMixin, Field): default_error_messages = { 'invalid': _('Duration has wrong format. Use one of these formats instead: {format}.'), } diff --git a/tests/test_fields.py b/tests/test_fields.py index 0ee49e9c16..1f099e0e30 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 `IntegerField` 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`. From d1822ec6b6ebb38234d06b89eced9fe9be37f359 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Thu, 22 Feb 2018 15:23:40 +0100 Subject: [PATCH 2/3] Made field mapping use mix/max kwargs for DurationField validators. --- rest_framework/utils/field_mapping.py | 2 +- tests/test_model_serializer.py | 25 +++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) 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_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): From 45374449d3915a4c11a28bb52bc32ac64d752c37 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 24 Apr 2018 09:09:51 +0200 Subject: [PATCH 3/3] Remove mixin --- rest_framework/fields.py | 81 ++++++++++++++++++++++++++++++++-------- tests/test_fields.py | 2 +- 2 files changed, 67 insertions(+), 16 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 6b32fd4964..d6e3633392 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -903,16 +903,20 @@ def to_internal_value(self, data): # Number types... -class MaxMinMixin(object): +class IntegerField(Field): default_error_messages = { + 'invalid': _('A valid integer is required.'), '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}.'), + 'max_string_length': _('String value too large.') } + MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs. + re_decimal = re.compile(r'\.0*\s*$') # allow e.g. '1.0' as an int, but not '1.2' def __init__(self, **kwargs): self.max_value = kwargs.pop('max_value', None) self.min_value = kwargs.pop('min_value', None) - super(MaxMinMixin, self).__init__(**kwargs) + super(IntegerField, self).__init__(**kwargs) if self.max_value is not None: message = lazy( self.error_messages['max_value'].format, @@ -926,15 +930,6 @@ def __init__(self, **kwargs): self.validators.append( MinValueValidator(self.min_value, message=message)) - -class IntegerField(MaxMinMixin, Field): - default_error_messages = { - 'invalid': _('A valid integer is required.'), - 'max_string_length': _('String value too large.') - } - MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs. - re_decimal = re.compile(r'\.0*\s*$') # allow e.g. '1.0' as an int, but not '1.2' - def to_internal_value(self, data): if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH: self.fail('max_string_length') @@ -949,13 +944,32 @@ def to_representation(self, value): return int(value) -class FloatField(MaxMinMixin, Field): +class FloatField(Field): default_error_messages = { 'invalid': _('A valid number is required.'), + '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}.'), 'max_string_length': _('String value too large.') } MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs. + def __init__(self, **kwargs): + self.max_value = kwargs.pop('max_value', None) + self.min_value = kwargs.pop('min_value', None) + super(FloatField, 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, data): if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH: @@ -970,9 +984,11 @@ def to_representation(self, value): return float(value) -class DecimalField(MaxMinMixin, Field): +class DecimalField(Field): default_error_messages = { 'invalid': _('A valid number is required.'), + '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}.'), 'max_digits': _('Ensure that there are no more than {max_digits} digits in total.'), 'max_decimal_places': _('Ensure that there are no more than {max_decimal_places} decimal places.'), 'max_whole_digits': _('Ensure that there are no more than {max_whole_digits} digits before the decimal point.'), @@ -990,12 +1006,28 @@ def __init__(self, max_digits, decimal_places, coerce_to_string=None, max_value= if self.localize: self.coerce_to_string = True + self.max_value = max_value + self.min_value = min_value + if self.max_digits is not None and self.decimal_places is not None: self.max_whole_digits = self.max_digits - self.decimal_places else: self.max_whole_digits = None - super(DecimalField, self).__init__(max_value=max_value, min_value=min_value, **kwargs) + super(DecimalField, 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)) if rounding is not None: valid_roundings = [v for k, v in vars(decimal).items() if k.startswith('ROUND_')] @@ -1319,11 +1351,30 @@ def to_representation(self, value): return value.strftime(output_format) -class DurationField(MaxMinMixin, Field): +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/tests/test_fields.py b/tests/test_fields.py index 1f099e0e30..7227c2f5a8 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1461,7 +1461,7 @@ class TestNoOutputFormatTimeField(FieldValues): class TestMinMaxDurationField(FieldValues): """ - Valid and invalid values for `IntegerField` with min and max limits. + 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), pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

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:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy