Repository: encode/django-rest-framework

Issue:
DecimalField: str value without trailing zeros
I guess, `coerce_to_string=True` in DecimalField should safely remove trailing zeros and `coerce_to_string=False` should leave value unchanged. If it's true - why my values are with trailing zeros?
In old version of DRF values were without trailing zeros as I remember
```
| vaue | 24.471635150166826 |

value = serializers.DecimalField(decimal_places=30, max_digits=35, default=0, coerce_to_string=True)
>> 24.471635150166826000000000000000
```





Discussion:
Just ran into this. I am using it to store SI multipliers for values series and having a series in kilo{something} it is not needed to output "1000.000000000000" in my JSON and its annoying to have to remember the amount of decimal places for simple tests.

This on the DecimalField seems to do it:

``` python

def quantize(self, value):
        """
        Quantize the decimal value to the configured precision.
        """
        if self.decimal_places is None:
            return value

        context = decimal.getcontext().copy()
        if self.max_digits is not None:
            context.prec = self.max_digits
        return value.quantize(
            decimal.Decimal('.1') ** self.decimal_places,
            rounding=self.rounding,
            context=context
        )

```

Maybe another option is to use .normalize() on the value?

``` python
decimal.Decimal('0.001000000).normalize()`
>> decimal.Decimal('0.001')

# but the following might be a problem for serializing but according to json standard for number it should be ok. (https://json.org/)

decimal.Decimal('1000.00000000).normalize()
>> decimal.Decimal('1E+3')
```

``` python

multiplier = fields.DecimalField(max_digits=25, decimal_places=12, normalize=True)

```

Another option could be to create a subclass of the field that makes the quantize function do nothing. but that would get problems if you got a result that has more decimal places than wanted and there would be no rounding.

Not sure what the best way to solve it would be.

@carltongibson added the rounding parameter on DecimalField. Maybe he could give some insight to what could be a good direction to solve the problem.

Seems to me like adding a normalize option would be good.
> Another option could be to create a subclass...

This is always the first option. Capture the exact behaviour you need for your application. Then propose a change if you think it appropriate, with a set of test cases that show exactly the what behaviour is altered. 

I suspect all the options here are a matter of choice. If we change the default behaviour someone else will complain. I'm not sure there's much we can do. 
I assume this is related to changes in #4075 or #4339. Also, this sounds related to #6311, which has additional, extensive discussion. Not 100% sure, since I don't have the bandwidth to read all of these threads extensively.


in #6311 @tomchristie say that he would accept `strip_trailing_zeros` flag. Would a `normalize` argument achieving pretty much the same thing also be acceptable?

I think the normalize flag could be added without breaking any existing stuff. I'm working on a deadline right now but afterwards I could try and put together a PR for it.

Hey @Krolken. A PR is always worth looking at. 🙂

Resolving patch:
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -963,10 +963,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_output=False, **kwargs):
         self.max_digits = max_digits
         self.decimal_places = decimal_places
         self.localize = localize
+        self.normalize_output = normalize_output
         if coerce_to_string is not None:
             self.coerce_to_string = coerce_to_string
         if self.localize:
@@ -1079,6 +1080,9 @@ def to_representation(self, value):
 
         quantized = self.quantize(value)
 
+        if self.normalize_output:
+            quantized = quantized.normalize()
+
         if not coerce_to_string:
             return quantized
         if self.localize:


Test patch:
diff --git a/tests/test_fields.py b/tests/test_fields.py
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -1251,6 +1251,27 @@ def test_part_precision_string_quantized_value_for_decimal(self):
         assert value == expected_digit_tuple
 
 
+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_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_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_output=True, coerce_to_string=False)
+        output = field.to_representation(Decimal('1.000'))
+        assert output == Decimal('1')
+
+
 class TestNoDecimalPlaces(FieldValues):
     valid_inputs = {
         '0.12345': Decimal('0.12345'),