Просмотр исходного кода

Use a consistent structure for field errors

Jeremy Stretch 1 неделя назад
Родитель
Сommit
b9f13c6c75
2 измененных файлов с 47 добавлено и 1 удалено
  1. 35 0
      netbox/dcim/tests/test_api.py
  2. 12 1
      netbox/netbox/api/viewsets/mixins.py

+ 35 - 0
netbox/dcim/tests/test_api.py

@@ -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

+ 12 - 1
netbox/netbox/api/viewsets/mixins.py

@@ -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
     ]