Use a consistent structure for field errors

This commit is contained in:
Jeremy Stretch 2026-08-12 11:20:45 -04:00
parent f6e1bacfa1
commit b9f13c6c75
2 changed files with 47 additions and 1 deletions

View File

@ -547,6 +547,41 @@ class SiteTestCase(APIViewTestCases.APIViewTestCase):
site.refresh_from_db()
self.assertEqual(site.description, '')
def test_bulk_write_objects_null_entry(self):
"""
Address a list endpoint with a list containing a null entry. A null fails before any field
is considered, so its error arrives as a bare list of messages; it must still be reported
as a mapping keyed by field name, as the schema declares.
"""
obj_perm = ObjectPermission(name='Test permission', actions=['change', 'delete'])
obj_perm.save()
obj_perm.users.add(self.user)
obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
site = Site.objects.get(slug='site-1')
initial_count = Site.objects.count()
for method in ('patch', 'put', 'delete'):
with self.subTest(method=method):
data = [{'id': site.pk, 'description': 'x'}, None]
response = getattr(self.client, method)(
self._get_list_url(), data, format='json', **self.header
)
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
self.assertEqual(len(response.data['errors']), 1)
self.assertEqual(response.data['errors'][0]['index'], 1)
# Reported under the key which every other non-field error uses
entry_errors = response.data['errors'][0]['errors']
self.assertIsInstance(entry_errors, dict)
self.assertIn('__all__', entry_errors)
# The valid entry must not have been applied
site.refresh_from_db()
self.assertEqual(site.description, '')
self.assertEqual(Site.objects.count(), initial_count, 'No objects should have been deleted')
def test_bulk_update_objects_duplicate_id_invalid_entry(self):
"""
PATCH a set of objects in which one object is named twice, once with invalid data and once

View File

@ -107,6 +107,17 @@ def get_non_list_response(data):
return Response({'detail': detail}, status=status.HTTP_400_BAD_REQUEST)
def _as_field_errors(item_errors):
"""
Return the errors reported for one entry of a bulk request as a mapping of field name to
messages.
"""
if isinstance(item_errors, dict):
return item_errors
return {api_settings.NON_FIELD_ERRORS_KEY: item_errors}
def get_invalid_entries_response(entry_errors):
"""
Return a structured error Response for the entries of a bulk request which could not be
@ -124,7 +135,7 @@ def get_invalid_entries_response(entry_errors):
entry per object in the request (an empty dict where that object was interpretable).
"""
errors = [
{'index': i, 'errors': item_errors}
{'index': i, 'errors': _as_field_errors(item_errors)}
for i, item_errors in enumerate(entry_errors)
if item_errors
]