Repository: encode/django-rest-framework

Issue:
Partial updates always change fields that are not in data, read only, and have a default
This is kind of an edge case, maybe? Maybe not. I ran into it because I have a field which is readonly on update but has a default for the create case.

See [Serializer._writable_fields](https://github.com/tomchristie/django-rest-framework/blob/322bda8/rest_framework/serializers.py#L344). I'm trying to do a partial update on one field, but I have another field that's readonly on update. BUT instead of being left alone, since it has a default, it always gets set TO THAT DEFAULT.

From my reading of writable_fields, it looks like this would happen with _any_ field that has a default and isn't included in a partial update, but for it to happen on a read_only field seems particularly egregious to me. :-p



Discussion:
(My expectation would be that a partial update would only affect the fields that have data being sent their way.)

Is this still occurring against the latest release?

I tried upgrading to the latest stable release (3.3.1) and still had the issue, and I didn't see any changes between that and master in the relevant parts of the code.

This is still present. The documentation ([3.0 announcement](http://www.django-rest-framework.org/topics/3.0-announcement/#the-required-allow_null-allow_blank-and-default-arguments), which is the only place I could find a reference to this) is also wrong: it doesn't mention that it'll only be used for read_only fields.

Milestoning to confirm against latest version.


Resolving patch:
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -435,7 +435,8 @@ def get_default(self):
         return `empty`, indicating that no value should be set in the
         validated data for this field.
         """
-        if self.default is empty:
+        if self.default is empty or getattr(self.root, 'partial', False):
+            # No default, or this is a partial update.
             raise SkipField()
         if callable(self.default):
             if hasattr(self.default, 'set_context'):


Test patch:
diff --git a/tests/test_serializer.py b/tests/test_serializer.py
--- a/tests/test_serializer.py
+++ b/tests/test_serializer.py
@@ -309,3 +309,31 @@ class ExampleSerializer(serializers.Serializer):
         pickled = pickle.dumps(serializer.data)
         data = pickle.loads(pickled)
         assert data == {'field1': 'a', 'field2': 'b'}
+
+
+class TestDefaultInclusions:
+    def setup(self):
+        class ExampleSerializer(serializers.Serializer):
+            char = serializers.CharField(read_only=True, default='abc')
+            integer = serializers.IntegerField()
+        self.Serializer = ExampleSerializer
+
+    def test_default_should_included_on_create(self):
+        serializer = self.Serializer(data={'integer': 456})
+        assert serializer.is_valid()
+        assert serializer.validated_data == {'char': 'abc', 'integer': 456}
+        assert serializer.errors == {}
+
+    def test_default_should_be_included_on_update(self):
+        instance = MockObject(char='def', integer=123)
+        serializer = self.Serializer(instance, data={'integer': 456})
+        assert serializer.is_valid()
+        assert serializer.validated_data == {'char': 'abc', 'integer': 456}
+        assert serializer.errors == {}
+
+    def test_default_should_not_be_included_on_partial_update(self):
+        instance = MockObject(char='def', integer=123)
+        serializer = self.Serializer(instance, data={'integer': 456}, partial=True)
+        assert serializer.is_valid()
+        assert serializer.validated_data == {'integer': 456}
+        assert serializer.errors == {}