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

Validate related object field is list

The related object fields are not covered by the form, so don't pass
any validation before trying to iterate over them and accessing their
elements. Instead of allowing a hard technical error to be raised,
explicitly check that it is indeed a list, and raise a normal validation
error if not.

The error message is chosen to be similar in format and wording to the
other existing validation errors. The used word "list" is quite
universal, and conveys the wanted meaning in the context of python,
json and yaml.
Marko Hauptvogel 3 месяцев назад
Родитель
Сommit
78223cea03
2 измененных файлов с 46 добавлено и 1 удалено
  1. 36 0
      netbox/dcim/tests/test_views.py
  2. 10 1
      netbox/netbox/views/generic/bulk_views.py

+ 36 - 0
netbox/dcim/tests/test_views.py

@@ -1038,6 +1038,42 @@ module-bays:
         self.assertHttpStatus(response, 200)
         self.assertHttpStatus(response, 200)
         self.assertContains(response, "Record 2 module-bays[3].name: This field is required.")
         self.assertContains(response, "Record 2 module-bays[3].name: This field is required.")
 
 
+    @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
+    def test_import_nolist(self):
+        # Add all required permissions to the test user
+        self.add_permissions(
+            'dcim.view_devicetype',
+            'dcim.add_devicetype',
+            'dcim.add_consoleporttemplate',
+            'dcim.add_consoleserverporttemplate',
+            'dcim.add_powerporttemplate',
+            'dcim.add_poweroutlettemplate',
+            'dcim.add_interfacetemplate',
+            'dcim.add_frontporttemplate',
+            'dcim.add_rearporttemplate',
+            'dcim.add_modulebaytemplate',
+            'dcim.add_devicebaytemplate',
+            'dcim.add_inventoryitemtemplate',
+        )
+
+        for value in ('', 'null', '3', '"My console port"', '{name: "My other console port"}'):
+            with self.subTest(value=value):
+                import_data = f'''
+manufacturer: Manufacturer 1
+model: TEST-3000
+slug: test-3000
+u_height: 1
+console-ports: {value}
+'''
+                form_data = {
+                    'data': import_data,
+                    'format': 'yaml'
+                }
+
+                response = self.client.post(reverse('dcim:devicetype_bulk_import'), data=form_data, follow=True)
+                self.assertHttpStatus(response, 200)
+                self.assertContains(response, "Record 1 console-ports: Must be a list.")
+
     def test_export_objects(self):
     def test_export_objects(self):
         url = reverse('dcim:devicetype_list')
         url = reverse('dcim:devicetype_list')
         self.add_permissions('dcim.view_devicetype')
         self.add_permissions('dcim.view_devicetype')

+ 10 - 1
netbox/netbox/views/generic/bulk_views.py

@@ -381,8 +381,17 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
         # Iterate through the related object forms (if any), validating and saving each instance.
         # Iterate through the related object forms (if any), validating and saving each instance.
         for field_name, related_object_form in self.related_object_forms.items():
         for field_name, related_object_form in self.related_object_forms.items():
 
 
+            related_objects = model_form.data.get(field_name, list())
+            if not isinstance(related_objects, list):
+                raise ValidationError(
+                    self._compile_form_errors(
+                        {field_name: [_("Must be a list.")]},
+                        index=parent_idx
+                    )
+                )
+
             related_obj_pks = []
             related_obj_pks = []
-            for i, rel_obj_data in enumerate(model_form.data.get(field_name, list()), start=1):
+            for i, rel_obj_data in enumerate(related_objects, start=1):
                 rel_obj_data = self.prep_related_object_data(obj, rel_obj_data)
                 rel_obj_data = self.prep_related_object_data(obj, rel_obj_data)
                 f = related_object_form(rel_obj_data)
                 f = related_object_form(rel_obj_data)