Prevent NestedBoundField child access crash after parent validation error (#4073)

This commit is contained in:
Shrikant Giri 2025-12-19 22:12:19 +05:30
parent 055c422b34
commit 3fc1c73168
2 changed files with 46 additions and 2 deletions

View File

@ -130,13 +130,21 @@ class NestedBoundField(BoundField):
def __getitem__(self, key):
field = self.fields[key]
value = self.value.get(key) if self.value else None
error = self.errors.get(key) if isinstance(self.errors, dict) else None
if isinstance(self.errors, dict):
error = self.errors.get(key)
elif isinstance(self.errors, list):
error = {} # normalize list to empty dict for nested children
else:
error = None
if hasattr(field, 'fields'):
return NestedBoundField(field, value, error, prefix=self.name + '.')
elif getattr(field, '_is_jsonfield', False):
return JSONBoundField(field, value, error, prefix=self.name + '.')
return BoundField(field, value, error, prefix=self.name + '.')
def as_form_field(self):
values = {}
for key, value in self.value.items():

View File

@ -1,7 +1,7 @@
from django.http import QueryDict
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
class TestSimpleBoundField:
def test_empty_bound_field(self):
@ -211,6 +211,42 @@ class TestNestedBoundField:
rendered_packed = ''.join(rendered.split())
assert rendered_packed == expected_packed
def test_child_bound_field_after_parent_validation_error(self):
"""
After a parent-level ValidationError on a nested serializer field,
child BoundFields should remain accessible and receive a mapping
for `errors` so the Browsable API can render safely.
Regression test for #4073.
"""
class ChildSerializer(serializers.Serializer):
value = serializers.CharField()
class ParentSerializer(serializers.Serializer):
nested = ChildSerializer()
def validate_nested(self, nested):
# Raise parent-level (non-field) validation error
raise ValidationError(["parent-level nested error"])
serializer = ParentSerializer(data={"nested": {"value": "ignored"}})
assert not serializer.is_valid()
# Parent-level error is a list (current problematic case)
assert serializer.errors["nested"] == ["parent-level nested error"]
# Access nested bound field
parent_bound = serializer["nested"]
# Access child bound field should not raise
child_bound = parent_bound["value"]
# Core contract: errors must be a mapping, not None or list
assert isinstance(child_bound.errors, dict)
# Sanity checks
assert child_bound.value == "ignored"
assert child_bound.name == "nested.value"
class TestJSONBoundField:
def test_as_form_fields(self):