From 2159bfd877ea7497aaee5f3069d6667f75168ef6 Mon Sep 17 00:00:00 2001 From: Pravin Kamble Date: Tue, 25 Nov 2025 14:58:48 +0530 Subject: [PATCH 1/6] Added Test for expected behaviour --- tests/test_serializer_lists.py | 81 ++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/tests/test_serializer_lists.py b/tests/test_serializer_lists.py index f76451a5ad..95886210f5 100644 --- a/tests/test_serializer_lists.py +++ b/tests/test_serializer_lists.py @@ -775,3 +775,84 @@ def test(self): queryset = NullableOneToOneSource.objects.all() serializer = self.serializer(queryset, many=True) assert serializer.data + + +class TestListSerializerDictErrorBehavior: + """ + Tests for the proposed dict-based error structure for ListSerializer, + and consistency with ListField. + + https://github.com/encode/django-rest-framework/issues/7279 + """ + + def setup_method(self): + class SampleSerializer(serializers.Serializer): + num = serializers.BooleanField() + + class ChildSerializer(serializers.Serializer): + num = serializers.BooleanField() + + class WrapperSerializer(serializers.Serializer): + list_serializer = ChildSerializer(many=True) + list_field = serializers.ListField( + child=serializers.DictField(allow_empty=False) + ) + + self.SampleSerializer = SampleSerializer + self.WrapperSerializer = WrapperSerializer + + def test_listserializer_dict_error_format(self): + + data = [ + {"num": "1"}, + {"num": "x"}, + {"num": "0"}, + {"num": "hello"}, + ] + + serializer = self.SampleSerializer(data=data, many=True) + serializer.is_valid() + + errors = serializer.errors + assert isinstance(errors, dict) + for _, value in errors.items(): + assert value == { + "num": [ErrorDetail(string="Must be a valid boolean.", code="invalid")] + } + + + + def test_listserializer_and_listfield_consistency(self): + + data = { + "list_serializer": [ + {"num": "1"}, + {"num": "wrong"}, + {"num": "0"}, + {"num": ""}, + ], + "list_field": [ + {"ok": "x"}, + {}, + {"valid": "y"}, + {}, + ], + } + + serializer = self.WrapperSerializer(data=data) + serializer.is_valid() + + errors = serializer.errors + + assert isinstance(errors["list_serializer"], dict) + assert isinstance(errors["list_field"], dict) + + assert set(errors["list_serializer"].keys()) == {0, 1,} + assert set(errors["list_field"].keys()) == {1, 3} + + assert errors["list_serializer"][1] == { + "num": [ErrorDetail(string="Must be a valid boolean.", code="invalid")] + } + + for index, value in errors["list_field"].items(): + assert value == [ErrorDetail(string='This dictionary may not be empty.', code='empty')] From c95a7aa40af1d6d91dc1988937474787b06a9b0e Mon Sep 17 00:00:00 2001 From: Pravin Kamble Date: Tue, 25 Nov 2025 15:13:27 +0530 Subject: [PATCH 2/6] formatted list serializer errors in dict format --- rest_framework/serializers.py | 9 +++++---- tests/test_serializer_lists.py | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index ea2daffd5a..2b13962a0e 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -688,18 +688,19 @@ def to_internal_value(self, data): }, code='min_length') ret = [] - errors = [] + errors = {} + index = 0 for item in data: try: validated = self.run_child_validation(item) except ValidationError as exc: - errors.append(exc.detail) + errors[index] = exc.detail else: ret.append(validated) - errors.append({}) + index += 1 - if any(errors): + if errors: raise ValidationError(errors) return ret diff --git a/tests/test_serializer_lists.py b/tests/test_serializer_lists.py index 95886210f5..40461aca45 100644 --- a/tests/test_serializer_lists.py +++ b/tests/test_serializer_lists.py @@ -781,7 +781,7 @@ class TestListSerializerDictErrorBehavior: """ Tests for the proposed dict-based error structure for ListSerializer, and consistency with ListField. - + https://github.com/encode/django-rest-framework/issues/7279 """ @@ -847,7 +847,7 @@ def test_listserializer_and_listfield_consistency(self): assert isinstance(errors["list_serializer"], dict) assert isinstance(errors["list_field"], dict) - assert set(errors["list_serializer"].keys()) == {0, 1,} + assert set(errors["list_serializer"].keys()) == {1,3} assert set(errors["list_field"].keys()) == {1, 3} assert errors["list_serializer"][1] == { From c04d6299093b040de86ede8be986631488d58bb9 Mon Sep 17 00:00:00 2001 From: Pravin Kamble Date: Wed, 26 Nov 2025 11:26:18 +0530 Subject: [PATCH 3/6] fixed flake8 errors --- tests/test_serializer_lists.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/tests/test_serializer_lists.py b/tests/test_serializer_lists.py index 40461aca45..cc8eeba3ca 100644 --- a/tests/test_serializer_lists.py +++ b/tests/test_serializer_lists.py @@ -779,8 +779,7 @@ def test(self): class TestListSerializerDictErrorBehavior: """ - Tests for the proposed dict-based error structure for ListSerializer, - and consistency with ListField. + Tests dict-based error structure for ListSerializer, and consistency with ListField. https://github.com/encode/django-rest-framework/issues/7279 """ @@ -805,8 +804,8 @@ def test_listserializer_dict_error_format(self): data = [ {"num": "1"}, - {"num": "x"}, - {"num": "0"}, + {"num": "x"}, + {"num": "0"}, {"num": "hello"}, ] @@ -820,22 +819,20 @@ def test_listserializer_dict_error_format(self): "num": [ErrorDetail(string="Must be a valid boolean.", code="invalid")] } - - def test_listserializer_and_listfield_consistency(self): data = { "list_serializer": [ {"num": "1"}, - {"num": "wrong"}, + {"num": "wrong"}, {"num": "0"}, - {"num": ""}, + {"num": ""}, ], "list_field": [ {"ok": "x"}, {}, {"valid": "y"}, - {}, + {}, ], } @@ -847,7 +844,7 @@ def test_listserializer_and_listfield_consistency(self): assert isinstance(errors["list_serializer"], dict) assert isinstance(errors["list_field"], dict) - assert set(errors["list_serializer"].keys()) == {1,3} + assert set(errors["list_serializer"].keys()) == {1, 3} assert set(errors["list_field"].keys()) == {1, 3} assert errors["list_serializer"][1] == { @@ -855,4 +852,4 @@ def test_listserializer_and_listfield_consistency(self): } for index, value in errors["list_field"].items(): - assert value == [ErrorDetail(string='This dictionary may not be empty.', code='empty')] + assert value == [ErrorDetail(string='This dictionary may not be empty.', code='empty')] From 54d649b8ed0edd60484cf6404a40ee4a84f257e4 Mon Sep 17 00:00:00 2001 From: Pravin Kamble Date: Wed, 26 Nov 2025 11:51:25 +0530 Subject: [PATCH 4/6] update test_serializer_bulk_update for handling new error formatting --- tests/test_serializer_bulk_update.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/tests/test_serializer_bulk_update.py b/tests/test_serializer_bulk_update.py index 0465578bb6..c888e6aa82 100644 --- a/tests/test_serializer_bulk_update.py +++ b/tests/test_serializer_bulk_update.py @@ -65,11 +65,9 @@ def test_bulk_create_errors(self): 'author': 'Haruki Murakami' } ] - expected_errors = [ - {}, - {}, - {'id': ['A valid integer is required.']} - ] + expected_errors = { + 2: {'id': ['A valid integer is required.']} + } serializer = self.BookSerializer(data=data, many=True) assert serializer.is_valid() is False @@ -85,11 +83,7 @@ def test_invalid_list_datatype(self): assert serializer.is_valid() is False message = 'Invalid data. Expected a dictionary, but got str.' - expected_errors = [ - {'non_field_errors': [message]}, - {'non_field_errors': [message]}, - {'non_field_errors': [message]} - ] + expected_errors = {idx: {'non_field_errors': [message]} for idx in range(len(data))} assert serializer.errors == expected_errors From 681582bfcf13b340fdd4b0232b9ac49e91157d7f Mon Sep 17 00:00:00 2001 From: Pravin Kamble Date: Wed, 26 Nov 2025 14:23:32 +0530 Subject: [PATCH 5/6] Use enumerate for index and item in loop --- rest_framework/serializers.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 2b13962a0e..448b59b774 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -689,16 +689,14 @@ def to_internal_value(self, data): ret = [] errors = {} - index = 0 - for item in data: + for index, item in enumerate(data): try: validated = self.run_child_validation(item) except ValidationError as exc: errors[index] = exc.detail else: ret.append(validated) - index += 1 if errors: raise ValidationError(errors) From 5b44bf9391e6433acef22e4bf7dfa9b266113c9d Mon Sep 17 00:00:00 2001 From: Pravin Kamble Date: Thu, 27 Nov 2025 10:15:43 +0530 Subject: [PATCH 6/6] refactor tests --- tests/test_serializer_lists.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/test_serializer_lists.py b/tests/test_serializer_lists.py index cc8eeba3ca..eaff6ac1fb 100644 --- a/tests/test_serializer_lists.py +++ b/tests/test_serializer_lists.py @@ -814,10 +814,10 @@ def test_listserializer_dict_error_format(self): errors = serializer.errors assert isinstance(errors, dict) - for _, value in errors.items(): - assert value == { - "num": [ErrorDetail(string="Must be a valid boolean.", code="invalid")] - } + assert set(errors.keys()) == {1, 3} + + assert errors[1] == {"num": [ErrorDetail(string="Must be a valid boolean.", code="invalid")]} + assert errors[3] == {"num": [ErrorDetail(string="Must be a valid boolean.", code="invalid")]} def test_listserializer_and_listfield_consistency(self): @@ -847,9 +847,8 @@ def test_listserializer_and_listfield_consistency(self): assert set(errors["list_serializer"].keys()) == {1, 3} assert set(errors["list_field"].keys()) == {1, 3} - assert errors["list_serializer"][1] == { - "num": [ErrorDetail(string="Must be a valid boolean.", code="invalid")] - } + assert errors["list_serializer"][1] == {"num": [ErrorDetail(string="Must be a valid boolean.", code="invalid")]} + assert errors["list_serializer"][3] == {"num": [ErrorDetail(string="Must be a valid boolean.", code="invalid")]} - for index, value in errors["list_field"].items(): - assert value == [ErrorDetail(string='This dictionary may not be empty.', code='empty')] + assert errors["list_field"][1] == [ErrorDetail(string='This dictionary may not be empty.', code='empty')] + assert errors["list_field"][3] == [ErrorDetail(string='This dictionary may not be empty.', code='empty')]