From 4e83673a4a96330127c2625fb541e989b2011ac7 Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Thu, 29 Aug 2024 14:21:25 +0100 Subject: [PATCH 1/5] Use Decimal for min/max values of DecimalField in tests --- tests/importable/__init__.py | 5 ++++- tests/test_fields.py | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/importable/__init__.py b/tests/importable/__init__.py index ded08258c8..415d9a6b62 100644 --- a/tests/importable/__init__.py +++ b/tests/importable/__init__.py @@ -2,6 +2,7 @@ This test "app" exists to ensure that parts of Django REST Framework can be imported/invoked before Django itself has been fully initialized. """ +from decimal import Decimal from rest_framework import compat, serializers # noqa @@ -11,6 +12,8 @@ class ExampleSerializer(serializers.Serializer): charfield = serializers.CharField(min_length=1, max_length=2) integerfield = serializers.IntegerField(min_value=1, max_value=2) floatfield = serializers.FloatField(min_value=1, max_value=2) - decimalfield = serializers.DecimalField(max_digits=10, decimal_places=1, min_value=1, max_value=2) + decimalfield = serializers.DecimalField( + max_digits=10, decimal_places=1, min_value=Decimal(1), max_value=Decimal(2) + ) durationfield = serializers.DurationField(min_value=1, max_value=2) listfield = serializers.ListField(min_length=1, max_length=2) diff --git a/tests/test_fields.py b/tests/test_fields.py index 4306817634..533f631fd1 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1291,12 +1291,14 @@ class TestAllowEmptyStrDecimalFieldWithValidators(FieldValues): outputs = { None: '', } - field = serializers.DecimalField(max_digits=3, decimal_places=1, allow_null=True, min_value=0, max_value=10) + field = serializers.DecimalField( + max_digits=3, decimal_places=1, allow_null=True, min_value=Decimal(0), max_value=Decimal(10) + ) class TestNoMaxDigitsDecimalField(FieldValues): field = serializers.DecimalField( - max_value=100, min_value=0, + max_value=Decimal(100), min_value=Decimal(0), decimal_places=2, max_digits=None ) valid_inputs = { From b8c646ac86973aeff61b126ff6d8bc7f8da46606 Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Thu, 29 Aug 2024 14:21:55 +0100 Subject: [PATCH 2/5] Update docs to mention that min/max values should be Decimal objects --- docs/api-guide/fields.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 94b6e7c21a..dfae686098 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -291,8 +291,8 @@ Corresponds to `django.db.models.fields.DecimalField`. * `max_digits` The maximum number of digits allowed in the number. It must be either `None` or an integer greater than or equal to `decimal_places`. * `decimal_places` The number of decimal places to store with the number. * `coerce_to_string` Set to `True` if string values should be returned for the representation, or `False` if `Decimal` objects should be returned. Defaults to the same value as the `COERCE_DECIMAL_TO_STRING` settings key, which will be `True` unless overridden. If `Decimal` objects are returned by the serializer, then the final output format will be determined by the renderer. Note that setting `localize` will force the value to `True`. -* `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. +* `max_value` Validate that the number provided is no greater than this value. Should be a `Decimal` object. +* `min_value` Validate that the number provided is no less than this value. Should be a `Decimal` object. * `localize` Set to `True` to enable localization of input and output based on the current locale. This will also force `coerce_to_string` to `True`. Defaults to `False`. Note that data formatting is enabled if you have set `USE_L10N=True` in your settings file. * `rounding` Sets the rounding mode used when quantizing to the configured precision. Valid values are [`decimal` module rounding modes][python-decimal-rounding-modes]. Defaults to `None`. * `normalize_output` Will normalize the decimal value when serialized. This will strip all trailing zeroes and change the value's precision to the minimum required precision to be able to represent the value without losing data. Defaults to `False`. From 9fef0f6926dca53b7935a04d27a213aea2c9ebf5 Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Fri, 6 Sep 2024 16:40:10 +0100 Subject: [PATCH 3/5] Accept integer values for DecimalField min and max values --- docs/api-guide/fields.md | 4 ++-- rest_framework/fields.py | 8 ++++---- tests/importable/__init__.py | 5 +---- tests/test_fields.py | 10 ++++------ 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index dfae686098..5cbedd964a 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -291,8 +291,8 @@ Corresponds to `django.db.models.fields.DecimalField`. * `max_digits` The maximum number of digits allowed in the number. It must be either `None` or an integer greater than or equal to `decimal_places`. * `decimal_places` The number of decimal places to store with the number. * `coerce_to_string` Set to `True` if string values should be returned for the representation, or `False` if `Decimal` objects should be returned. Defaults to the same value as the `COERCE_DECIMAL_TO_STRING` settings key, which will be `True` unless overridden. If `Decimal` objects are returned by the serializer, then the final output format will be determined by the renderer. Note that setting `localize` will force the value to `True`. -* `max_value` Validate that the number provided is no greater than this value. Should be a `Decimal` object. -* `min_value` Validate that the number provided is no less than this value. Should be a `Decimal` object. +* `max_value` Validate that the number provided is no greater than this value. Should be an integer or `Decimal` object. +* `min_value` Validate that the number provided is no less than this value. Should be an integer or `Decimal` object. * `localize` Set to `True` to enable localization of input and output based on the current locale. This will also force `coerce_to_string` to `True`. Defaults to `False`. Note that data formatting is enabled if you have set `USE_L10N=True` in your settings file. * `rounding` Sets the rounding mode used when quantizing to the configured precision. Valid values are [`decimal` module rounding modes][python-decimal-rounding-modes]. Defaults to `None`. * `normalize_output` Will normalize the decimal value when serialized. This will strip all trailing zeroes and change the value's precision to the minimum required precision to be able to represent the value without losing data. Defaults to `False`. diff --git a/rest_framework/fields.py b/rest_framework/fields.py index cbc02e2c2b..6989edc0a8 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -986,10 +986,10 @@ def __init__(self, max_digits, decimal_places, coerce_to_string=None, max_value= self.max_value = max_value self.min_value = min_value - if self.max_value is not None and not isinstance(self.max_value, decimal.Decimal): - warnings.warn("max_value should be a Decimal instance.") - if self.min_value is not None and not isinstance(self.min_value, decimal.Decimal): - warnings.warn("min_value should be a Decimal instance.") + if self.max_value is not None and not isinstance(self.max_value, (int, decimal.Decimal)): + warnings.warn("max_value should be an integer or Decimal instance.") + if self.min_value is not None and not isinstance(self.min_value, (int, decimal.Decimal)): + warnings.warn("min_value should be an integer or Decimal instance.") if self.max_digits is not None and self.decimal_places is not None: self.max_whole_digits = self.max_digits - self.decimal_places diff --git a/tests/importable/__init__.py b/tests/importable/__init__.py index 415d9a6b62..ded08258c8 100644 --- a/tests/importable/__init__.py +++ b/tests/importable/__init__.py @@ -2,7 +2,6 @@ This test "app" exists to ensure that parts of Django REST Framework can be imported/invoked before Django itself has been fully initialized. """ -from decimal import Decimal from rest_framework import compat, serializers # noqa @@ -12,8 +11,6 @@ class ExampleSerializer(serializers.Serializer): charfield = serializers.CharField(min_length=1, max_length=2) integerfield = serializers.IntegerField(min_value=1, max_value=2) floatfield = serializers.FloatField(min_value=1, max_value=2) - decimalfield = serializers.DecimalField( - max_digits=10, decimal_places=1, min_value=Decimal(1), max_value=Decimal(2) - ) + decimalfield = serializers.DecimalField(max_digits=10, decimal_places=1, min_value=1, max_value=2) durationfield = serializers.DurationField(min_value=1, max_value=2) listfield = serializers.ListField(min_length=1, max_length=2) diff --git a/tests/test_fields.py b/tests/test_fields.py index 533f631fd1..6125d9b321 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1251,7 +1251,7 @@ class TestMinMaxDecimalField(FieldValues): outputs = {} field = serializers.DecimalField( max_digits=3, decimal_places=1, - min_value=10, max_value=20 + min_value=10.0, max_value=20.0 ) def test_warning_when_not_decimal_types(self, caplog): @@ -1260,7 +1260,7 @@ def test_warning_when_not_decimal_types(self, caplog): serializers.DecimalField( max_digits=3, decimal_places=1, - min_value=10, max_value=20 + min_value=10.0, max_value=20.0 ) assert len(w) == 2 @@ -1291,14 +1291,12 @@ class TestAllowEmptyStrDecimalFieldWithValidators(FieldValues): outputs = { None: '', } - field = serializers.DecimalField( - max_digits=3, decimal_places=1, allow_null=True, min_value=Decimal(0), max_value=Decimal(10) - ) + field = serializers.DecimalField(max_digits=3, decimal_places=1, allow_null=True, min_value=0, max_value=10) class TestNoMaxDigitsDecimalField(FieldValues): field = serializers.DecimalField( - max_value=Decimal(100), min_value=Decimal(0), + max_value=100, min_value=0, decimal_places=2, max_digits=None ) valid_inputs = { From 1289689f3b2696916fb293a17e94151516db5201 Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Fri, 6 Sep 2024 18:55:03 +0100 Subject: [PATCH 4/5] Update expected error messages in tests --- tests/test_fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_fields.py b/tests/test_fields.py index 6125d9b321..bae8ed062d 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1245,8 +1245,8 @@ class TestMinMaxDecimalField(FieldValues): '20.0': Decimal('20.0'), } invalid_inputs = { - '9.9': ['Ensure this value is greater than or equal to 10.'], - '20.1': ['Ensure this value is less than or equal to 20.'], + '9.9': ['Ensure this value is greater than or equal to 10.0.'], + '20.1': ['Ensure this value is less than or equal to 20.0.'], } outputs = {} field = serializers.DecimalField( From c6b46f6db3dec0ca2caaee4a9d7384655e7d2f52 Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Fri, 6 Sep 2024 18:58:59 +0100 Subject: [PATCH 5/5] Update expected warning message in tests --- tests/test_fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_fields.py b/tests/test_fields.py index bae8ed062d..1403a6a35d 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1266,8 +1266,8 @@ def test_warning_when_not_decimal_types(self, caplog): assert len(w) == 2 assert all(issubclass(i.category, UserWarning) for i in w) - assert 'max_value should be a Decimal instance' in str(w[0].message) - assert 'min_value should be a Decimal instance' in str(w[1].message) + assert 'max_value should be an integer or Decimal instance' in str(w[0].message) + assert 'min_value should be an integer or Decimal instance' in str(w[1].message) class TestAllowEmptyStrDecimalFieldWithValidators(FieldValues):