From 58e80f442bca6930e5f413856fefa41c7b599d9e Mon Sep 17 00:00:00 2001 From: Pierre Chiquet Date: Tue, 15 Jun 2021 11:15:07 +0200 Subject: [PATCH 1/3] Separated run_child_validation method in ListSerializer --- rest_framework/serializers.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index eae6a0b2e1..4f2712fa13 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -627,6 +627,17 @@ def run_validation(self, data=empty): return value + def run_child_validation(self, data): + """ + Run validation on child serializer. + You may need to override this method to support multiple updates. For example: + + self.child.instance = self.instance.get(pk=data['id']) + self.child.initial_data = data + return super().run_child_validation(data) + """ + return self.child.run_validation(data) + def to_internal_value(self, data): """ List of dicts of native values <- List of dicts of primitive datatypes. @@ -665,7 +676,7 @@ def to_internal_value(self, data): for item in data: try: - validated = self.child.run_validation(item) + validated = self.run_child_validation(item) except ValidationError as exc: errors.append(exc.detail) else: From 312c09c915cc81b472d377f8e75d48063b067917 Mon Sep 17 00:00:00 2001 From: Pierre Chiquet Date: Tue, 13 Dec 2022 17:17:53 +0100 Subject: [PATCH 2/3] fix typo --- tests/test_serializer_lists.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_serializer_lists.py b/tests/test_serializer_lists.py index 10463d29ab..8aea218667 100644 --- a/tests/test_serializer_lists.py +++ b/tests/test_serializer_lists.py @@ -481,7 +481,7 @@ class Serializer(serializers.Serializer): assert serializer.validated_data == {} assert serializer.errors == {} - def test_udate_as_field_allow_empty_true(self): + def test_update_as_field_allow_empty_true(self): class ListSerializer(serializers.Serializer): update_field = serializers.IntegerField() store_field = serializers.IntegerField() From 08c3e595770df751f3069c4d7f474aaf76015ce1 Mon Sep 17 00:00:00 2001 From: Pierre Chiquet Date: Tue, 13 Dec 2022 17:17:25 +0100 Subject: [PATCH 3/3] Add test_update_allow_custom_child_validation --- tests/test_serializer_lists.py | 55 ++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/tests/test_serializer_lists.py b/tests/test_serializer_lists.py index 8aea218667..4070de7a51 100644 --- a/tests/test_serializer_lists.py +++ b/tests/test_serializer_lists.py @@ -153,6 +153,61 @@ def test_validate_html_input(self): assert serializer.is_valid() assert serializer.validated_data == expected_output + def test_update_allow_custom_child_validation(self): + """ + Update a list of objects thanks custom run_child_validation implementation. + """ + + class TestUpdateSerializer(serializers.Serializer): + integer = serializers.IntegerField() + boolean = serializers.BooleanField() + + def update(self, instance, validated_data): + instance._data.update(validated_data) + return instance + + def validate(self, data): + # self.instance is set to current BasicObject instance + assert isinstance(self.instance, BasicObject) + # self.initial_data is current dictionary + assert isinstance(self.initial_data, dict) + assert self.initial_data["pk"] == self.instance.pk + return super().validate(data) + + class ListUpdateSerializer(serializers.ListSerializer): + child = TestUpdateSerializer() + + def run_child_validation(self, data): + # find related instance in self.instance list + child_instance = next(o for o in self.instance if o.pk == data["pk"]) + # set instance and initial_data for child serializer + self.child.instance = child_instance + self.child.initial_data = data + return super().run_child_validation(data) + + def update(self, instance, validated_data): + return [ + self.child.update(instance, attrs) + for instance, attrs in zip(self.instance, validated_data) + ] + + instance = [ + BasicObject(pk=1, integer=11, private_field="a"), + BasicObject(pk=2, integer=22, private_field="b"), + ] + input_data = [ + {"pk": 1, "integer": "123", "boolean": "true"}, + {"pk": 2, "integer": "456", "boolean": "false"}, + ] + expected_output = [ + BasicObject(pk=1, integer=123, boolean=True, private_field="a"), + BasicObject(pk=2, integer=456, boolean=False, private_field="b"), + ] + serializer = ListUpdateSerializer(instance, data=input_data) + assert serializer.is_valid() + updated_instances = serializer.save() + assert updated_instances == expected_output + class TestNestedListSerializer: """