فهرست منبع

Use a consistent structure for field errors

Jeremy Stretch 1 هفته پیش
والد
کامیت
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()
         site.refresh_from_db()
         self.assertEqual(site.description, '')
         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):
     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
         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)
     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):
 def get_invalid_entries_response(entry_errors):
     """
     """
     Return a structured error Response for the entries of a bulk request which could not be
     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).
         entry per object in the request (an empty dict where that object was interpretable).
     """
     """
     errors = [
     errors = [
-        {'index': i, 'errors': item_errors}
+        {'index': i, 'errors': _as_field_errors(item_errors)}
         for i, item_errors in enumerate(entry_errors)
         for i, item_errors in enumerate(entry_errors)
         if item_errors
         if item_errors
     ]
     ]