From 18480668e0f5da46ac073314c8a1b4a2e5ac94b2 Mon Sep 17 00:00:00 2001 From: Marko Hauptvogel Date: Fri, 22 Aug 2025 09:53:51 +0200 Subject: [PATCH] Allow bridge when importing interface templates Adding the field to the InterfaceTemplateImportForm is enough. The two clean_*_type methods are for properly dynamically restricting what values can be selected. --- netbox/dcim/forms/object_import.py | 24 +++++++++++++-- netbox/dcim/tests/test_views.py | 49 ++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/netbox/dcim/forms/object_import.py b/netbox/dcim/forms/object_import.py index 3f2cc3ef6..25880348e 100644 --- a/netbox/dcim/forms/object_import.py +++ b/netbox/dcim/forms/object_import.py @@ -84,6 +84,12 @@ class InterfaceTemplateImportForm(forms.ModelForm): label=_('Type'), choices=InterfaceTypeChoices.CHOICES ) + bridge = forms.ModelChoiceField( + label=_('Bridge'), + queryset=InterfaceTemplate.objects.all(), + to_field_name='name', + required=False + ) poe_mode = forms.ChoiceField( choices=InterfacePoEModeChoices, required=False, @@ -103,10 +109,24 @@ class InterfaceTemplateImportForm(forms.ModelForm): class Meta: model = InterfaceTemplate fields = [ - 'device_type', 'module_type', 'name', 'label', 'type', 'enabled', 'mgmt_only', 'description', 'poe_mode', - 'poe_type', 'rf_role' + 'device_type', 'module_type', 'name', 'label', 'type', 'enabled', 'mgmt_only', 'description', 'bridge', + 'poe_mode', 'poe_type', 'rf_role' ] + def clean_device_type(self): + if device_type := self.cleaned_data['device_type']: + bridge = self.fields['bridge'] + bridge.queryset = bridge.queryset.filter(device_type=device_type) + + return device_type + + def clean_module_type(self): + if module_type := self.cleaned_data['module_type']: + bridge = self.fields['bridge'] + bridge.queryset = bridge.queryset.filter(module_type=module_type) + + return module_type + class FrontPortTemplateImportForm(forms.ModelForm): type = forms.ChoiceField( diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index 26aad4b68..40e3cfef9 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -1055,6 +1055,55 @@ console-ports: self.assertHttpStatus(response, 200) self.assertContains(response, "console-ports[0]: Must be a dictionary.") + @override_settings(EXEMPT_VIEW_PERMISSIONS=['*']) + def test_import_interfacebridge(self): + IMPORT_DATA = """ +manufacturer: Manufacturer 1 +model: TEST-4000 +slug: test-4000 +u_height: 1 +interfaces: + - name: Bridge + type: 1000base-t + - name: Bridge Interface 1 + type: 1000base-t + bridge: Bridge +""" + + # 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', + ) + + 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, "Imported 1 device types") + + device_type = DeviceType.objects.get(model='TEST-4000') + self.assertEqual(device_type.interfacetemplates.count(), 2) + + interfaces = InterfaceTemplate.objects.all().order_by('id') + self.assertEqual(interfaces[0].name, 'Bridge') + self.assertIsNone(interfaces[0].bridge) + self.assertEqual(interfaces[1].name, 'Bridge Interface 1') + self.assertEqual(interfaces[1].bridge.name, "Bridge") + def test_export_objects(self): url = reverse('dcim:devicetype_list') self.add_permissions('dcim.view_devicetype')