diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 01f34298b4..279898d082 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -791,6 +791,8 @@ def raise_errors_on_nested_writes(method_name, serializer, validated_data): * Silently ignore the nested part of the update. * Automatically create a profile instance. """ + ModelClass = serializer.Meta.model + model_field_info = model_meta.get_field_info(ModelClass) # Ensure we don't have a writable nested field. For example: # @@ -800,6 +802,7 @@ def raise_errors_on_nested_writes(method_name, serializer, validated_data): assert not any( isinstance(field, BaseSerializer) and (field.source in validated_data) and + (field.source in model_field_info.relations) and isinstance(validated_data[field.source], (list, dict)) for field in serializer._writable_fields ), ( @@ -818,9 +821,19 @@ def raise_errors_on_nested_writes(method_name, serializer, validated_data): # class UserSerializer(ModelSerializer): # ... # address = serializer.CharField('profile.address') + # + # Though, non-relational fields (e.g., JSONField) are acceptable. 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 ab30fad223..a614e05d13 100644 --- a/tests/test_serializer_nested.py +++ b/tests/test_serializer_nested.py @@ -4,6 +4,8 @@ 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 class TestNestedSerializer: @@ -302,3 +304,50 @@ class Meta: 'serializer `tests.test_serializer_nested.DottedAddressSerializer`, ' 'or set `read_only=True` on dotted-source serializer fields.' ) + + +if postgres_fields: + class NonRelationalPersonModel(models.Model): + """Model declaring a postgres JSONField""" + data = postgres_fields.JSONField() + + +@pytest.mark.skipif(not postgres_fields, reason='psycopg2 is not installed') +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 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'}} + raise_errors_on_nested_writes('create', serializer, serializer.validated_data) + raise_errors_on_nested_writes('update', serializer, serializer.validated_data) + + def test_dotted_source_field_create_and_update(self): + + 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'}} + raise_errors_on_nested_writes('create', serializer, serializer.validated_data) + raise_errors_on_nested_writes('update', serializer, serializer.validated_data)