diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 56a7c7b26..67b29d53a 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -819,9 +819,21 @@ def raise_errors_on_nested_writes(method_name, serializer, validated_data, model # class UserSerializer(ModelSerializer): # ... # address = serializer.CharField('profile.address') + # + # Though, we can have a dotted field if it is not expressing a model relation. + # + # For example: + # + # class NonRelationalPersonModel(models.Model): + # profile = JSONField() + # + # class UserSerializer(ModelSerializer): + # ... + # address = serializer.CharField('profile.address') assert not any( len(field.source_attrs) > 1 and (field.source_attrs[0] in validated_data) and + (field.source_attrs[0] in model_field_info.relations) and isinstance(validated_data[field.source_attrs[0]], (list, dict)) for field in serializer._writable_fields ), ( diff --git a/tests/test_serializer_nested.py b/tests/test_serializer_nested.py index ab30fad22..92f2084df 100644 --- a/tests/test_serializer_nested.py +++ b/tests/test_serializer_nested.py @@ -4,6 +4,9 @@ from django.http import QueryDict from django.test import TestCase from rest_framework import serializers +from rest_framework.compat import postgres_fields +from rest_framework.serializers import raise_errors_on_nested_writes +from rest_framework.utils import model_meta class TestNestedSerializer: @@ -302,3 +305,55 @@ class TestNestedWriteErrors(TestCase): 'serializer `tests.test_serializer_nested.DottedAddressSerializer`, ' 'or set `read_only=True` on dotted-source serializer fields.' ) + + +@pytest.mark.skipif('not postgres_fields') +class TestNestedNonRelationalFieldWrite: + """ + Test that raise_errors_on_nested_writes does not raise `AssertionError` when the + model field is not a relation. + """ + + def test_nested_serializer_create_and_update(self): + class NonRelationalPersonModel(models.Model): + """Model declaring a postgres JSONField""" + data = postgres_fields.JSONField() + + class NonRelationalPersonDataSerializer(serializers.Serializer): + occupation = serializers.CharField() + + class NonRelationalPersonSerializer(serializers.ModelSerializer): + data = NonRelationalPersonDataSerializer() + + class Meta: + model = NonRelationalPersonModel + fields = ['data'] + + serializer = NonRelationalPersonSerializer(data={'data': {'occupation': 'developer'}}) + assert serializer.is_valid() + assert serializer.validated_data == {'data': {'occupation': 'developer'}} + ModelClass = serializer.Meta.model + info = model_meta.get_field_info(ModelClass) + raise_errors_on_nested_writes('create', serializer, serializer.validated_data, info) + raise_errors_on_nested_writes('update', serializer, serializer.validated_data, info) + + def test_dotted_source_field_create_and_update(self): + class NonRelationalPersonModel(models.Model): + """Model declaring a postgres JSONField""" + data = postgres_fields.JSONField() + + class DottedNonRelationalPersonSerializer(serializers.ModelSerializer): + occupation = serializers.CharField(source='data.occupation') + + class Meta: + model = NonRelationalPersonModel + fields = ['occupation'] + + serializer = DottedNonRelationalPersonSerializer(data={'occupation': 'developer'}) + assert serializer.is_valid() + assert serializer.validated_data == {'data': {'occupation': 'developer'}} + ModelClass = serializer.Meta.model + info = model_meta.get_field_info(ModelClass) + raise_errors_on_nested_writes('create', serializer, serializer.validated_data, info) + raise_errors_on_nested_writes('update', serializer, serializer.validated_data, info) + assert serializer.data == {'occupation': 'developer'}