Repository: encode/django-rest-framework

Issue:
DecimalField fails when `max_digits=None`
## Steps to reproduce

1) Create a DecimalField inside a serializer with max_value, min_value, and decimal_places set, but set max_digits = None.  EG: serializers.DecimalField(max_value=100, min_value=0, decimal_places=10, max_digits=None)
2) call serializer.is_valid()
3) observe traceback similar to the following : 

Traceback (most recent call last):
  File "~/api/views.py", line 263, in monitor
    serializer.is_valid()
  File "~/env/local/lib/python2.7/site-packages/rest_framework/serializers.py", line 213, in is_valid
    self._validated_data = self.run_validation(self.initial_data)
  File "~/env/local/lib/python2.7/site-packages/rest_framework/serializers.py", line 407, in run_validation
    value = self.to_internal_value(data)
  File "~/env/local/lib/python2.7/site-packages/rest_framework/serializers.py", line 437, in to_internal_value
    validated_value = field.run_validation(primitive_value)
  File "~/env/local/lib/python2.7/site-packages/rest_framework/fields.py", line 488, in run_validation
    value = self.to_internal_value(data)
  File "~/env/local/lib/python2.7/site-packages/rest_framework/fields.py", line 959, in to_internal_value
    return self.quantize(self.validate_precision(value))
  File "~/env/local/lib/python2.7/site-packages/rest_framework/fields.py", line 1022, in quantize
    context=context
  File "/usr/lib/python2.7/decimal.py", line 2455, in quantize
    if not (context.Etiny() <= exp._exp <= context.Emax):
  File "/usr/lib/python2.7/decimal.py", line 3898, in Etiny
    return int(self.Emin - self.prec + 1)
TypeError: unsupported operand type(s) for -: 'int' and 'NoneType'
## Expected behavior

is_valid returns true if the value is between min and max, without concerning itself with the number of digits / precision of the number, OR do not accept None as a valid value for max_digits.
## Actual behavior

an exception is raised, which is difficult to track down and counter intuitive.



Discussion:
> is_valid returns true if the value is between min and max, without concerning itself with the number of digits / precision of the number, OR do not accept None as a valid value for max_digits.

Nop. `max_digits` is a required argument. It shouldn't be `None` or ignored. I'd assume you'll get a faster traceback if you simply ignore it.

What I'd be willing to do here would be:
- to add an explicit mention in the doc about the required fields.
- to add an assert about `max_digits` not being None.

Closing this issue meanwhile as we're not making `max_digit` optional.

That makes sense, yeah assert when max_digits is None would be great.
Definitely easier for people to debug.

On Aug 9, 2016 11:53 PM, "Xavier Ordoquy" notifications@github.com wrote:

> Closed #4372
> https://github.com/tomchristie/django-rest-framework/issues/4372.
> 
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub
> https://github.com/tomchristie/django-rest-framework/issues/4372#event-750879863,
> or mute the thread
> https://github.com/notifications/unsubscribe-auth/AE4PcV0S1msjrxb6c-2dJxbv8Gx6aFplks5qeXVtgaJpZM4Jgk4N
> .

Taking a look through the code it's not clear if `max_digits` is intended to be optional or not. We _do_ switch on `if self.max_digits is None`, and a `DecimalField` _without_ digit restrictions is a reasonable enough use case. Worth putting on to the queue to have a second think over.

@tomchristie `max_digits` is not a keyword argument in `__init__` therefore it's supposed to be required. Same applies to `decimal_field`. This is likely inherited from Django's own `DecimalField`.
If it becomes an optional argument, we'll need to cover the case where it's over the Django's Model boundaries.


Resolving patch:
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -1016,7 +1016,8 @@ def quantize(self, value):
             return value
 
         context = decimal.getcontext().copy()
-        context.prec = self.max_digits
+        if self.max_digits is not None:
+            context.prec = self.max_digits
         return value.quantize(
             decimal.Decimal('.1') ** self.decimal_places,
             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
@@ -876,6 +876,18 @@ class TestMinMaxDecimalField(FieldValues):
     )
 
 
+class TestNoMaxDigitsDecimalField(FieldValues):
+    field = serializers.DecimalField(
+        max_value=100, min_value=0,
+        decimal_places=2, max_digits=None
+    )
+    valid_inputs = {
+        '10': Decimal('10.00')
+    }
+    invalid_inputs = {}
+    outputs = {}
+
+
 class TestNoStringCoercionDecimalField(FieldValues):
     """
     Output values for `DecimalField` with `coerce_to_string=False`.