Repository: encode/django-rest-framework

Issue:
DecimalField errors on serialization of `None`, even when `allow_null=True`
## 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.)
- [x] This cannot be dealt with as a third party library. (We prefer new functionality to be [in the form of third party libraries](https://www.django-rest-framework.org/community/third-party-packages/#about-third-party-packages) where possible.)
- [x] I have reduced the issue to the simplest possible case.
- [x] 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
Try to serialize `None` with a `DecimalField` where `allow_null = True`.

## Expected behavior
What to expect here is a bit subjective... I'm expecting `''` if `coerce_to_string=True`, otherwise `None`, but that's just my subjective opinion.

However, I think we can agree that the actual behavior isn't the expected behavior. :)

## Actual behavior
Throws `InvalidOperation: [<class 'decimal.ConversionSyntax'>]`


Discussion:

Resolving patch:
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -1063,6 +1063,9 @@ def to_internal_value(self, data):
         try:
             value = decimal.Decimal(data)
         except decimal.DecimalException:
+            if data == '' and self.allow_null:
+                return None
+
             self.fail('invalid')
 
         if value.is_nan():
@@ -1112,6 +1115,12 @@ def validate_precision(self, value):
     def to_representation(self, value):
         coerce_to_string = getattr(self, 'coerce_to_string', api_settings.COERCE_DECIMAL_TO_STRING)
 
+        if value is None:
+            if coerce_to_string:
+                return ''
+            else:
+                return None
+
         if not isinstance(value, decimal.Decimal):
             value = decimal.Decimal(str(value).strip())
 


Test patch:
diff --git a/tests/test_fields.py b/tests/test_fields.py
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -1090,6 +1090,9 @@ class TestDecimalField(FieldValues):
         '2E+1': Decimal('20'),
     }
     invalid_inputs = (
+        (None, ["This field may not be null."]),
+        ('', ["A valid number is required."]),
+        (' ', ["A valid number is required."]),
         ('abc', ["A valid number is required."]),
         (Decimal('Nan'), ["A valid number is required."]),
         (Decimal('Snan'), ["A valid number is required."]),
@@ -1115,6 +1118,32 @@ class TestDecimalField(FieldValues):
     field = serializers.DecimalField(max_digits=3, decimal_places=1)
 
 
+class TestAllowNullDecimalField(FieldValues):
+    valid_inputs = {
+        None: None,
+        '': None,
+        ' ': None,
+    }
+    invalid_inputs = {}
+    outputs = {
+        None: '',
+    }
+    field = serializers.DecimalField(max_digits=3, decimal_places=1, allow_null=True)
+
+
+class TestAllowNullNoStringCoercionDecimalField(FieldValues):
+    valid_inputs = {
+        None: None,
+        '': None,
+        ' ': None,
+    }
+    invalid_inputs = {}
+    outputs = {
+        None: None,
+    }
+    field = serializers.DecimalField(max_digits=3, decimal_places=1, allow_null=True, coerce_to_string=False)
+
+
 class TestMinMaxDecimalField(FieldValues):
     """
     Valid and invalid values for `DecimalField` with min and max limits.