diff --git a/rest_framework/fields.py b/rest_framework/fields.py index b6c9ee5c5..d91299484 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1063,6 +1063,9 @@ class DecimalField(Field): 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 @@ class DecimalField(Field): 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()) diff --git a/tests/test_fields.py b/tests/test_fields.py index fdd570d8a..5842553f0 100644 --- 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.