From 16e284f03a74895da7dc1f4054045272804a38a5 Mon Sep 17 00:00:00 2001 From: Marko Hauptvogel Date: Fri, 22 Aug 2025 09:53:51 +0200 Subject: [PATCH] Fix hard related object not dict error Elements of the "related objects list" are passed to the `prep_related_object_data` function before any validation takes place, with the potential of failing with a hard error. Similar to the "related objects not list" case explicitly validate the elements general type, and raise a normal validation error if it isn't a dictionary. --- netbox/dcim/tests/test_views.py | 37 +++++++++++++++++++++++ netbox/netbox/views/generic/bulk_views.py | 4 +++ 2 files changed, 41 insertions(+) diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index 822a92818..26aad4b68 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -1018,6 +1018,43 @@ console-ports: {value} self.assertHttpStatus(response, 200) self.assertContains(response, "console-ports: Must be a list.") + @override_settings(EXEMPT_VIEW_PERMISSIONS=['*']) + def test_import_nodict(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"', '["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, "console-ports[0]: Must be a dictionary.") + def test_export_objects(self): url = reverse('dcim:devicetype_list') self.add_permissions('dcim.view_devicetype') diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index ca26d5a36..b89b1d3a0 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -371,6 +371,10 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView): related_obj_pks = [] for i, rel_obj_data in related_objects: + if not isinstance(rel_obj_data, dict): # TODO isinstance(MutableMapping)? + import_form.add_error(None, f"{field_name}[{i}]: {_('Must be a dictionary.')}") + raise AbortTransaction() + rel_obj_data = self.prep_related_object_data(obj, rel_obj_data) f = related_object_form(rel_obj_data)