Repository: encode/django-rest-framework

Issue:
Resetting a CharFiled to an empty string after fix for #3130
Commit d231f36 to fix #3130 added the following check to `fields.Field.get_value()`:

```
        elif ret == '' and self.default:
            return empty
```

Maybe I am wrong, but it looks like with this change it is no longer possible to reset a `CharField` with `allow_blank=True` but no `default` argument which currently has a non-empty value back to an empty string: The default value for the `default` argument is the `empty` marker. Since the `validate_empty_values()` method raises a `SkipField` execption for this marker the field is skipped on update if set to an empty string.

To work around this problem the field may be defined with the empty string as `default` argument, but to have both `allow_blank=True` and `default=''` in the field's argument list seems counter-intuitive.



Discussion:
I'm sorry it's a bit abstract to me. Do you have a failing test case or snippet we could look at ?

Hi Xavier,

thanks for your prompt reply. You can reproduce the problem with the tutorial.

The `Snippet` class has a `title` field: 

```
class Snippet(models.Model):
    ...
    title = models.CharField(max_length=100, blank=True, default='')
    ...
```

(btw I wonder why the `default=''` argument is added, since that's what `blank=True` is saying already).

To reproduce the problem:
1. Go to the browsable API and add a snippet with a title.
2. Update the snippet and reset the title to an empty string. This has no effect, the original title is still displayed.

Adding

```
title = serializers.CharField(required=False, allow_blank=True,
                              max_length=100, default='')
```

to the `SnippetSerializer` fixes this. Note the `default=''` argument (actually any value evaluating to `False` will do).

I forget to mention that the problem occurs with HTML requests only, not with JSON requests (as can also be reproduced in the browsable API).

The reason for the described behaviour is that with the change introduced in commit d231f36 the empty string is skipped on validation of the serializer, therefore not included in the validated serializer data, and therefore not updated. See https://gist.github.com/mkemmerling/c6f532b31c8c41e9b5c8 .

Does this help to clarify the problem?

Actually, I gave this a try and my title was set to an empty string.
What Django REST framework version are you using ?

Note that default is enforced at the DB level which means that if you're creating an instance with the Django shell without giving a title it'll be an empty string.

> I forget to mention that the problem occurs with HTML requests only, not with JSON requests (as can also be reproduced in the browsable API).

This is a limitation of the html forms that would send an empty string where JSON data makes a difference between empty and null.

Will have a look at your test case. Thanks for the details.

I'm using version 3.2.3 (with Django 1.8.,4). The problem arises with the changes made for 3.2.0. And yes it occurs only with html forms where the `empty` marker is used.

Thanks again for having a look into this.

ok, I had an issue in my DRF version and confirme I could reproduce your issue with the tutorial.


Resolving patch:
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -385,8 +385,10 @@ def get_value(self, dictionary):
                 # If the field is blank, and null is a valid value then
                 # determine if we should use null instead.
                 return '' if getattr(self, 'allow_blank', False) else None
-            elif ret == '' and self.default:
-                return empty
+            elif ret == '' and not self.required:
+                # If the field is blank, and emptyness is valid then
+                # determine if we should use emptyness instead.
+                return '' if getattr(self, 'allow_blank', False) else empty
             return ret
         return dictionary.get(self.field_name, empty)
 


Test patch:
diff --git a/tests/test_fields.py b/tests/test_fields.py
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -253,7 +253,7 @@ class TestSerializer(serializers.Serializer):
 
 
 class TestHTMLInput:
-    def test_empty_html_charfield(self):
+    def test_empty_html_charfield_with_default(self):
         class TestSerializer(serializers.Serializer):
             message = serializers.CharField(default='happy')
 
@@ -261,6 +261,22 @@ class TestSerializer(serializers.Serializer):
         assert serializer.is_valid()
         assert serializer.validated_data == {'message': 'happy'}
 
+    def test_empty_html_charfield_without_default(self):
+        class TestSerializer(serializers.Serializer):
+            message = serializers.CharField(allow_blank=True)
+
+        serializer = TestSerializer(data=QueryDict('message='))
+        assert serializer.is_valid()
+        assert serializer.validated_data == {'message': ''}
+
+    def test_empty_html_charfield_without_default_not_required(self):
+        class TestSerializer(serializers.Serializer):
+            message = serializers.CharField(allow_blank=True, required=False)
+
+        serializer = TestSerializer(data=QueryDict('message='))
+        assert serializer.is_valid()
+        assert serializer.validated_data == {'message': ''}
+
     def test_empty_html_integerfield(self):
         class TestSerializer(serializers.Serializer):
             message = serializers.IntegerField(default=123)