From 7c1414c45da624831abb2d95181ef119fb322ade Mon Sep 17 00:00:00 2001 From: Henrik Palmlund Wahlgren Date: Tue, 19 Mar 2019 17:04:10 +0100 Subject: [PATCH 1/4] Added normalize parameter to DecimalField to be able to strip trailing zeros. Fixes #6151. --- rest_framework/fields.py | 8 +++++++- tests/test_fields.py | 20 ++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index c8f65db0e5..612568e573 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1014,10 +1014,11 @@ class DecimalField(Field): MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs. def __init__(self, max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None, - localize=False, rounding=None, **kwargs): + localize=False, rounding=None, normalize=False, **kwargs): self.max_digits = max_digits self.decimal_places = decimal_places self.localize = localize + self.normalize = normalize if coerce_to_string is not None: self.coerce_to_string = coerce_to_string if self.localize: @@ -1125,6 +1126,11 @@ def to_representation(self, value): quantized = self.quantize(value) + # TODO: Should maybe name the value to something not bound to + # quantized. Ex: out_value + if self.normalize: + quantized = quantized.normalize() + if not coerce_to_string: return quantized if self.localize: diff --git a/tests/test_fields.py b/tests/test_fields.py index 12c936b229..103940ea70 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1169,6 +1169,26 @@ def test_part_precision_string_quantized_value_for_decimal(self): expected_digit_tuple = (0, (1, 2, 0, 0), -2) assert value == expected_digit_tuple +class TestNormalizedValueDecimalField(TestCase): + """ + Test that we get the expected behavior of on DecimalField when normalize=True + """ + + def test_normalize_output(self): + field = serializers.DecimalField(max_digits=4, decimal_places=3, normalize=True) + output = field.to_representation(Decimal('1.000')) + assert output == '1' + + def test_non_normalize_output(self): + field = serializers.DecimalField(max_digits=4, decimal_places=3, normalize=False) + output = field.to_representation(Decimal('1.000')) + assert output == '1.000' + + def test_normalize_coeherce_to_string(self): + field = serializers.DecimalField(max_digits=4, decimal_places=3, normalize=True, coerce_to_string=False) + output = field.to_representation(Decimal('1.000')) + assert output == Decimal('1') + class TestNoDecimalPlaces(FieldValues): valid_inputs = { From 963bdc78b077b052252230b62156582af4a4ed6f Mon Sep 17 00:00:00 2001 From: Henrik Palmlund Wahlgren Date: Tue, 19 Mar 2019 17:10:23 +0100 Subject: [PATCH 2/4] Updated docs to include normalize option on DecimalField --- docs/api-guide/fields.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index ede4f15ad5..6ee3c7308d 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -281,6 +281,7 @@ Corresponds to `django.db.models.fields.DecimalField`. - `min_value` Validate that the number provided is no less than this value. - `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 quantising to the configured precision. Valid values are [`decimal` module rounding modes][python-decimal-rounding-modes]. Defaults to `None`. +- `normalize` Will normalize the decimal value. 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 loosing data. Defaults to `False`. #### Example usage From 9c1fffa8c39eeb646b73dd5cdf7635d5726fd34b Mon Sep 17 00:00:00 2001 From: Henrik Palmlund Wahlgren Date: Tue, 19 Mar 2019 22:07:12 +0100 Subject: [PATCH 3/4] Fixed linting error in test_fields --- tests/test_fields.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_fields.py b/tests/test_fields.py index 103940ea70..1d44c72605 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1169,6 +1169,7 @@ def test_part_precision_string_quantized_value_for_decimal(self): expected_digit_tuple = (0, (1, 2, 0, 0), -2) assert value == expected_digit_tuple + class TestNormalizedValueDecimalField(TestCase): """ Test that we get the expected behavior of on DecimalField when normalize=True From f22acca58c5d393419d63e410bdc77a25f5b2bad Mon Sep 17 00:00:00 2001 From: Henrik Palmlund Wahlgren Date: Fri, 11 Sep 2020 11:13:41 +0200 Subject: [PATCH 4/4] Removed comment and renamed normalize to normalize_output as suggested in code review --- docs/api-guide/fields.md | 2 +- rest_framework/fields.py | 8 +++----- tests/test_fields.py | 8 ++++---- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 6ee3c7308d..b7ed87fdc9 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -281,7 +281,7 @@ Corresponds to `django.db.models.fields.DecimalField`. - `min_value` Validate that the number provided is no less than this value. - `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 quantising to the configured precision. Valid values are [`decimal` module rounding modes][python-decimal-rounding-modes]. Defaults to `None`. -- `normalize` Will normalize the decimal value. 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 loosing data. Defaults to `False`. +- `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 loosing data. Defaults to `False`. #### Example usage diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 612568e573..6efb381504 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1014,11 +1014,11 @@ class DecimalField(Field): MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs. def __init__(self, max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None, - localize=False, rounding=None, normalize=False, **kwargs): + localize=False, rounding=None, normalize_output=False, **kwargs): self.max_digits = max_digits self.decimal_places = decimal_places self.localize = localize - self.normalize = normalize + self.normalize_output = normalize_output if coerce_to_string is not None: self.coerce_to_string = coerce_to_string if self.localize: @@ -1126,9 +1126,7 @@ def to_representation(self, value): quantized = self.quantize(value) - # TODO: Should maybe name the value to something not bound to - # quantized. Ex: out_value - if self.normalize: + if self.normalize_output: quantized = quantized.normalize() if not coerce_to_string: diff --git a/tests/test_fields.py b/tests/test_fields.py index 1d44c72605..1e5a25cba6 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1170,23 +1170,23 @@ def test_part_precision_string_quantized_value_for_decimal(self): assert value == expected_digit_tuple -class TestNormalizedValueDecimalField(TestCase): +class TestNormalizedOutputValueDecimalField(TestCase): """ Test that we get the expected behavior of on DecimalField when normalize=True """ def test_normalize_output(self): - field = serializers.DecimalField(max_digits=4, decimal_places=3, normalize=True) + field = serializers.DecimalField(max_digits=4, decimal_places=3, normalize_output=True) output = field.to_representation(Decimal('1.000')) assert output == '1' def test_non_normalize_output(self): - field = serializers.DecimalField(max_digits=4, decimal_places=3, normalize=False) + field = serializers.DecimalField(max_digits=4, decimal_places=3, normalize_output=False) output = field.to_representation(Decimal('1.000')) assert output == '1.000' def test_normalize_coeherce_to_string(self): - field = serializers.DecimalField(max_digits=4, decimal_places=3, normalize=True, coerce_to_string=False) + field = serializers.DecimalField(max_digits=4, decimal_places=3, normalize_output=True, coerce_to_string=False) output = field.to_representation(Decimal('1.000')) assert output == Decimal('1')