From a03b356e28ce890c15883f5d19ca6dba3500c462 Mon Sep 17 00:00:00 2001 From: Anes Foufa Date: Tue, 28 Jul 2020 16:52:54 +0200 Subject: [PATCH] feat: FloatField optionally validates that the number is no greater than or equals a given value. --- docs/api-guide/fields.md | 3 ++- rest_framework/fields.py | 15 +++++++++++---- rest_framework/validators.py | 9 +++++++++ tests/test_fields.py | 20 ++++++++++++++++++++ 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 7722a1f27..c7a02bbb7 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -276,10 +276,11 @@ A floating point representation. Corresponds to `django.db.models.fields.FloatField`. -**Signature**: `FloatField(max_value=None, min_value=None)` +**Signature**: `FloatField(max_value=None, min_value=None, exclusive_min=False)` - `max_value` Validate that the number provided is no greater than this value. - `min_value` Validate that the number provided is no less than this value. +- `exclusive_min` If true, validate that the number provided is no greater than or equals `min_value`. ## DecimalField diff --git a/rest_framework/fields.py b/rest_framework/fields.py index da2dd54be..dd1cadc9b 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -38,7 +38,7 @@ from rest_framework.exceptions import ErrorDetail, ValidationError from rest_framework.settings import api_settings from rest_framework.utils import html, humanize_datetime, json, representation from rest_framework.utils.formatting import lazy_format -from rest_framework.validators import ProhibitSurrogateCharactersValidator +from rest_framework.validators import ExclusiveLMinValueValidator, ProhibitSurrogateCharactersValidator class empty: @@ -968,6 +968,7 @@ class FloatField(Field): '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}.'), + 'exclusive_min_value': _('Ensure this value is greater than {min_value}.'), 'max_string_length': _('String value too large.') } MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs. @@ -975,15 +976,21 @@ class FloatField(Field): def __init__(self, **kwargs): self.max_value = kwargs.pop('max_value', None) self.min_value = kwargs.pop('min_value', None) + self.exclusive_min = kwargs.pop("exclusive_min", False) super().__init__(**kwargs) if self.max_value is not None: message = lazy_format(self.error_messages['max_value'], max_value=self.max_value) self.validators.append( MaxValueValidator(self.max_value, message=message)) if self.min_value is not None: - message = lazy_format(self.error_messages['min_value'], min_value=self.min_value) - self.validators.append( - MinValueValidator(self.min_value, message=message)) + if self.exclusive_min: + message = lazy_format(self.error_messages["exclusive_min_value"], min_value=self.min_value) + self.validators.append( + ExclusiveLMinValueValidator(self.min_value, message=message)) + else: + message = lazy_format(self.error_messages['min_value'], min_value=self.min_value) + self.validators.append( + MinValueValidator(self.min_value, message=message)) def to_internal_value(self, data): diff --git a/rest_framework/validators.py b/rest_framework/validators.py index a5cb75a84..1cae3cb67 100644 --- a/rest_framework/validators.py +++ b/rest_framework/validators.py @@ -6,6 +6,7 @@ This gives us better separation of concerns, allows us to use single-step object creation, and makes it possible to switch between using the implicit `ModelSerializer` class and an equivalent explicit `Serializer` class. """ +from django.core.validators import BaseValidator from django.db import DataError from django.utils.translation import gettext_lazy as _ @@ -278,3 +279,11 @@ class UniqueForYearValidator(BaseUniqueForValidator): filter_kwargs[field_name] = value filter_kwargs['%s__year' % date_field_name] = date.year return qs_filter(queryset, **filter_kwargs) + + +class ExclusiveLMinValueValidator(BaseValidator): + message = _('Ensure this value is greater than %(limit_value)s.') + code = "exclusive_min_value" + + def compare(self, a, b): + return a <= b diff --git a/tests/test_fields.py b/tests/test_fields.py index b1ad1dc66..0e2f787b6 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1076,6 +1076,26 @@ class TestMinMaxFloatField(FieldValues): field = serializers.FloatField(min_value=1, max_value=3) +class TestExclusiveMinFloatField(FieldValues): + """ + Valid and invalid values for 'FloatField' with exclusive_min limits. + """ + valid_inputs = { + '1.01': 1.01, + '3': 3, + 1.01: 1.01, + 3: 3, + 3.0: 3.0, + } + invalid_inputs = { + 1: ['Ensure this value is greater than 1.'], + '1': ['Ensure this value is greater than 1.'], + '1.0': ['Ensure this value is greater than 1.'], + } + outputs = {} + field = serializers.FloatField(min_value=1, exclusive_min=True) + + class TestDecimalField(FieldValues): """ Valid and invalid values for `DecimalField`.