Skip to content

Commit 30e56f6

Browse files
tselepakisrpkilby
authored andcommitted
Fix nested write of non-relational fields (#6916)
1 parent 0dac98d commit 30e56f6

File tree

2 files changed

+62
-0
lines changed

2 files changed

+62
-0
lines changed

rest_framework/serializers.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,8 @@ def raise_errors_on_nested_writes(method_name, serializer, validated_data):
790790
* Silently ignore the nested part of the update.
791791
* Automatically create a profile instance.
792792
"""
793+
ModelClass = serializer.Meta.model
794+
model_field_info = model_meta.get_field_info(ModelClass)
793795

794796
# Ensure we don't have a writable nested field. For example:
795797
#
@@ -799,6 +801,7 @@ def raise_errors_on_nested_writes(method_name, serializer, validated_data):
799801
assert not any(
800802
isinstance(field, BaseSerializer) and
801803
(field.source in validated_data) and
804+
(field.source in model_field_info.relations) and
802805
isinstance(validated_data[field.source], (list, dict))
803806
for field in serializer._writable_fields
804807
), (
@@ -817,9 +820,19 @@ def raise_errors_on_nested_writes(method_name, serializer, validated_data):
817820
# class UserSerializer(ModelSerializer):
818821
# ...
819822
# address = serializer.CharField('profile.address')
823+
#
824+
# Though, non-relational fields (e.g., JSONField) are acceptable. For example:
825+
#
826+
# class NonRelationalPersonModel(models.Model):
827+
# profile = JSONField()
828+
#
829+
# class UserSerializer(ModelSerializer):
830+
# ...
831+
# address = serializer.CharField('profile.address')
820832
assert not any(
821833
len(field.source_attrs) > 1 and
822834
(field.source_attrs[0] in validated_data) and
835+
(field.source_attrs[0] in model_field_info.relations) and
823836
isinstance(validated_data[field.source_attrs[0]], (list, dict))
824837
for field in serializer._writable_fields
825838
), (

tests/test_serializer_nested.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from django.test import TestCase
55

66
from rest_framework import serializers
7+
from rest_framework.compat import postgres_fields
8+
from rest_framework.serializers import raise_errors_on_nested_writes
79

810

911
class TestNestedSerializer:
@@ -302,3 +304,50 @@ class Meta:
302304
'serializer `tests.test_serializer_nested.DottedAddressSerializer`, '
303305
'or set `read_only=True` on dotted-source serializer fields.'
304306
)
307+
308+
309+
if postgres_fields:
310+
class NonRelationalPersonModel(models.Model):
311+
"""Model declaring a postgres JSONField"""
312+
data = postgres_fields.JSONField()
313+
314+
315+
@pytest.mark.skipif(not postgres_fields, reason='psycopg2 is not installed')
316+
class TestNestedNonRelationalFieldWrite:
317+
"""
318+
Test that raise_errors_on_nested_writes does not raise `AssertionError` when the
319+
model field is not a relation.
320+
"""
321+
322+
def test_nested_serializer_create_and_update(self):
323+
324+
class NonRelationalPersonDataSerializer(serializers.Serializer):
325+
occupation = serializers.CharField()
326+
327+
class NonRelationalPersonSerializer(serializers.ModelSerializer):
328+
data = NonRelationalPersonDataSerializer()
329+
330+
class Meta:
331+
model = NonRelationalPersonModel
332+
fields = ['data']
333+
334+
serializer = NonRelationalPersonSerializer(data={'data': {'occupation': 'developer'}})
335+
assert serializer.is_valid()
336+
assert serializer.validated_data == {'data': {'occupation': 'developer'}}
337+
raise_errors_on_nested_writes('create', serializer, serializer.validated_data)
338+
raise_errors_on_nested_writes('update', serializer, serializer.validated_data)
339+
340+
def test_dotted_source_field_create_and_update(self):
341+
342+
class DottedNonRelationalPersonSerializer(serializers.ModelSerializer):
343+
occupation = serializers.CharField(source='data.occupation')
344+
345+
class Meta:
346+
model = NonRelationalPersonModel
347+
fields = ['occupation']
348+
349+
serializer = DottedNonRelationalPersonSerializer(data={'occupation': 'developer'})
350+
assert serializer.is_valid()
351+
assert serializer.validated_data == {'data': {'occupation': 'developer'}}
352+
raise_errors_on_nested_writes('create', serializer, serializer.validated_data)
353+
raise_errors_on_nested_writes('update', serializer, serializer.validated_data)

0 commit comments

Comments
 (0)