Repository: encode/django-rest-framework

Issue:
Check source attrs when checking for nested writes
## Description

When checking for 'dotted' source nested values prior to serializer create or update, the problematic fields are not being identified correctly.

In the example given in the code comment, the validated data would look like `{'profile': {'address': '10 downing street'}}` but the code is currently checking for key 'address' in the dict.

## Test case

The issue can be seen currently with the following test (which will fail):
```
import pytest
from django.contrib.postgres.fields import JSONField
from django.db import models
from rest_framework import serializers

class NestedSourceModel(models.Model):
    profile = JSONField()

class NestedSourceSerializer(serializers.ModelSerializer):
    address = serializers.CharField(source='profile.address')

    class Meta:
        model = NestedSourceModel
        fields = ('address',)

data = QueryDict('address=10 downing street')
serializer = NestedSourceSerializer(data=data)
serializer.is_valid()
assert serializer.validated_data == {'profile': {'address': '10 downing street'}}
with pytest.raises(AssertionError):
    serializer.save()
```


Discussion:
Thanks for the PR.
Test added.
I'm a bit annoyed as I can't run the tests without the other part of the PR on my box.
@xordoquy What do you mean by 'other part of the PR'?
sorry, I meant the test alone with the current code base.
Copy and paste? :P

How do you normally show issues like this and provide a resolution? I can't introduce the test before the fix....
I usually try to make sure new test cases are failing without code addition so I'm confident the code fixes something ;)
How do you do that? Make a temporary PR that is just the test so you can see the failure?
Looks good. Little bit cautious about pulling into a minor release - we *could* choose to defer to the next major release.

Resolving patch:
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -819,10 +819,10 @@ def raise_errors_on_nested_writes(method_name, serializer, validated_data):
     #     ...
     #     address = serializer.CharField('profile.address')
     assert not any(
-        '.' in field.source and
-        (key in validated_data) and
-        isinstance(validated_data[key], (list, dict))
-        for key, field in serializer.fields.items()
+        len(field.source_attrs) > 1 and
+        (field.source_attrs[0] in validated_data) and
+        isinstance(validated_data[field.source_attrs[0]], (list, dict))
+        for field in serializer._writable_fields
     ), (
         'The `.{method_name}()` method does not support writable dotted-source '
         'fields by default.\nWrite an explicit `.{method_name}()` method for '


Test patch:
diff --git a/tests/test_serializer_nested.py b/tests/test_serializer_nested.py
--- a/tests/test_serializer_nested.py
+++ b/tests/test_serializer_nested.py
@@ -1,4 +1,7 @@
+import pytest
+from django.db import models
 from django.http import QueryDict
+from django.test import TestCase
 
 from rest_framework import serializers
 
@@ -241,3 +244,61 @@ def test_multipart_validate(self):
         serializer = self.Serializer(data=input_data)
         assert serializer.is_valid()
         assert 'nested' in serializer.validated_data
+
+
+class NestedWriteProfile(models.Model):
+    address = models.CharField(max_length=100)
+
+
+class NestedWritePerson(models.Model):
+    profile = models.ForeignKey(NestedWriteProfile, on_delete=models.CASCADE)
+
+
+class TestNestedWriteErrors(TestCase):
+    # tests for rests_framework.serializers.raise_errors_on_nested_writes
+    def test_nested_serializer_error(self):
+        class ProfileSerializer(serializers.ModelSerializer):
+            class Meta:
+                model = NestedWriteProfile
+                fields = ['address']
+
+        class NestedProfileSerializer(serializers.ModelSerializer):
+            profile = ProfileSerializer()
+
+            class Meta:
+                model = NestedWritePerson
+                fields = ['profile']
+
+        serializer = NestedProfileSerializer(data={'profile': {'address': '52 festive road'}})
+        assert serializer.is_valid()
+        assert serializer.validated_data == {'profile': {'address': '52 festive road'}}
+        with pytest.raises(AssertionError) as exc_info:
+            serializer.save()
+
+        assert str(exc_info.value) == (
+            'The `.create()` method does not support writable nested fields by '
+            'default.\nWrite an explicit `.create()` method for serializer '
+            '`tests.test_serializer_nested.NestedProfileSerializer`, or set '
+            '`read_only=True` on nested serializer fields.'
+        )
+
+    def test_dotted_source_field_error(self):
+        class DottedAddressSerializer(serializers.ModelSerializer):
+            address = serializers.CharField(source='profile.address')
+
+            class Meta:
+                model = NestedWritePerson
+                fields = ['address']
+
+        serializer = DottedAddressSerializer(data={'address': '52 festive road'})
+        assert serializer.is_valid()
+        assert serializer.validated_data == {'profile': {'address': '52 festive road'}}
+        with pytest.raises(AssertionError) as exc_info:
+            serializer.save()
+
+        assert str(exc_info.value) == (
+            'The `.create()` method does not support writable dotted-source '
+            'fields by default.\nWrite an explicit `.create()` method for '
+            'serializer `tests.test_serializer_nested.DottedAddressSerializer`, '
+            'or set `read_only=True` on dotted-source serializer fields.'
+        )