Repository: encode/django-rest-framework

Issue:
Add rounding parameter to DecimalField
## Checklist

- [x] I have verified that that issue exists against the `master` branch of Django REST framework.
- [x] I have searched for similar issues in both open and closed tickets and cannot find a duplicate.
- [x] This is not a usage question. (Those should be directed to the [discussion group](https://groups.google.com/forum/#!forum/django-rest-framework) instead.)
- [ ] This cannot be dealt with as a third party library. (We prefer new functionality to be [in the form of third party libraries](http://www.django-rest-framework.org/topics/third-party-resources/#about-third-party-packages) where possible.)
- [x] I have reduced the issue to the simplest possible case.
- [ ] I have included a failing test as a pull request. (If you are unable to do so we can still accept the issue.)

## Steps to reproduce

- Have a decimal field named score with decimal_places=1
- Serialize an objet with a score attribute that has a value of Decimal('7.25')

## Expected behavior

Output shows 7.3

## Actual behavior

Output shows 7.2.

The behaviour does not match human expectations and DecimalField does not accept a rounding parameter (for the Decimal.quantize method).

It seems overkill to install something like django-rest-framework-braces to work around one missing param in the API.


Discussion:
I think this is a sensible addition, given that `quantize()` only has two arguments (in addition to the context). 

For now, you *should* be able to control rounding by altering the global decimal context. It's not ideal, but it should work. 

Resolving patch:
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -997,7 +997,7 @@ 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, **kwargs):
+                 localize=False, rounding=None, **kwargs):
         self.max_digits = max_digits
         self.decimal_places = decimal_places
         self.localize = localize
@@ -1029,6 +1029,12 @@ def __init__(self, max_digits, decimal_places, coerce_to_string=None, max_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_')]
+            assert rounding in valid_roundings, (
+                'Invalid rounding option %s. Valid values for rounding are: %s' % (rounding, valid_roundings))
+        self.rounding = rounding
+
     def to_internal_value(self, data):
         """
         Validate that the input is a decimal number and return a Decimal
@@ -1121,6 +1127,7 @@ def quantize(self, value):
             context.prec = self.max_digits
         return value.quantize(
             decimal.Decimal('.1') ** self.decimal_places,
+            rounding=self.rounding,
             context=context
         )
 


Test patch:
diff --git a/tests/test_fields.py b/tests/test_fields.py
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -3,7 +3,7 @@
 import re
 import unittest
 import uuid
-from decimal import Decimal
+from decimal import ROUND_DOWN, ROUND_UP, Decimal
 
 import django
 import pytest
@@ -1092,8 +1092,21 @@ class TestNoDecimalPlaces(FieldValues):
     field = serializers.DecimalField(max_digits=6, decimal_places=None)
 
 
-# Date & time serializers...
+class TestRoundingDecimalField(TestCase):
+    def test_valid_rounding(self):
+        field = serializers.DecimalField(max_digits=4, decimal_places=2, rounding=ROUND_UP)
+        assert field.to_representation(Decimal('1.234')) == '1.24'
+
+        field = serializers.DecimalField(max_digits=4, decimal_places=2, rounding=ROUND_DOWN)
+        assert field.to_representation(Decimal('1.234')) == '1.23'
+
+    def test_invalid_rounding(self):
+        with pytest.raises(AssertionError) as excinfo:
+            serializers.DecimalField(max_digits=1, decimal_places=1, rounding='ROUND_UNKNOWN')
+        assert 'Invalid rounding option' in str(excinfo.value)
 
+
+# Date & time serializers...
 class TestDateField(FieldValues):
     """
     Valid and invalid values for `DateField`.