From 0f7c796275a0c8c3a2020de8f1a26d138628b02c Mon Sep 17 00:00:00 2001 From: Sergei Shishov Date: Wed, 2 Sep 2026 16:27:01 +0400 Subject: [PATCH] Fix lost 0. index prefix for ListSerializer errors on DRF 3.18 Since DRF 3.18, ListSerializer.to_internal_value collects child validation errors in a dict keyed by the integer index of the item (e.g. {0: {"email": [...]}}) instead of a positional list. In flatten_errors, the dict branch only prefixed a child key with the parent attr when `if attr:` was truthy. Since the integer 0 is falsy in Python, errors on the first item of a many=True payload lost their "0." prefix, while items at index 1 and above were prefixed correctly (any other int is truthy). Fix by checking `attr is not None` instead of relying on the truthiness of attr, so an attr of 0 is still treated as a valid prefix segment. Fixes ghazi-git/drf-standardized-errors#121 --- docs/changelog.md | 4 ++++ drf_standardized_errors/formatter.py | 2 +- tests/test_flatten_errors.py | 19 +++++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index c37838e..24b8c01 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [UNRELEASED] +### Fixed +- Fix the `attr` of errors raised on the first item (index `0`) of a `many=True` list serializer losing its `0.` + index prefix with DRF 3.18, since `ListSerializer.to_internal_value` now collects child errors in a dict keyed by + the integer index of the item, and integer `0` was being treated as falsy. ## [0.16.0] - 2026-04-29 ### Added diff --git a/drf_standardized_errors/formatter.py b/drf_standardized_errors/formatter.py index f51c374..32e9ecd 100644 --- a/drf_standardized_errors/formatter.py +++ b/drf_standardized_errors/formatter.py @@ -135,7 +135,7 @@ def flatten_errors( elif isinstance(detail, dict): for key, value in detail.items(): - if attr: + if attr is not None: key = f"{attr}{package_settings.NESTED_FIELD_SEPARATOR}{key}" fifo.append((value, key, None)) diff --git a/tests/test_flatten_errors.py b/tests/test_flatten_errors.py index 92d58c9..19fd7f1 100644 --- a/tests/test_flatten_errors.py +++ b/tests/test_flatten_errors.py @@ -126,3 +126,22 @@ def test_exception_with_detail_empty(): assert len(errors) == 1 assert errors[0].attr == "some_field" assert errors[0].detail == "" + + +@pytest.fixture +def list_serializer_errors_indexed_dict(): + # since DRF 3.18, ListSerializer.to_internal_value collects child errors in a + # dict keyed by the integer index of the item instead of a positional list. + return { + 0: {"email": [ErrorDetail("Enter a valid email address.", code="invalid")]}, + 1: {"email": [ErrorDetail("Enter a valid email address.", code="invalid")]}, + } + + +def test_list_serializer_errors_indexed_dict(list_serializer_errors_indexed_dict): + errors = flatten_errors(list_serializer_errors_indexed_dict) + assert len(errors) == 2 + assert errors[0].code == "invalid" + assert errors[0].attr == "0.email" + assert errors[1].code == "invalid" + assert errors[1].attr == "1.email"