diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index c8d445c1c..4d8fc9d81 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -2963,12 +2963,6 @@ class InterfaceBulkDisconnectForm(ConfirmationForm): class InterfaceCSVForm(CSVModelForm): device = CSVModelChoiceField( queryset=Device.objects.all(), - required=False, - to_field_name='name' - ) - virtual_machine = CSVModelChoiceField( - queryset=VirtualMachine.objects.all(), - required=False, to_field_name='name' ) lag = CSVModelChoiceField( diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index 189f98923..82b2414ad 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -598,17 +598,11 @@ class InterfaceImportTable(BaseTable): viewname='dcim:device', args=[Accessor('device.pk')] ) - virtual_machine = tables.LinkColumn( - viewname='virtualization:virtualmachine', - args=[Accessor('virtual_machine.pk')], - verbose_name='Virtual Machine' - ) class Meta(BaseTable.Meta): model = Interface fields = ( - 'device', 'virtual_machine', 'name', 'description', 'lag', 'type', 'enabled', 'mac_address', 'mtu', - 'mgmt_only', 'mode', + 'device', 'name', 'description', 'lag', 'type', 'enabled', 'mac_address', 'mtu', 'mgmt_only', 'mode', ) empty_text = False diff --git a/netbox/templates/inc/nav_menu.html b/netbox/templates/inc/nav_menu.html index 65d7baa67..4704ef613 100644 --- a/netbox/templates/inc/nav_menu.html +++ b/netbox/templates/inc/nav_menu.html @@ -373,6 +373,11 @@ Virtual Machines + {% if perms.virtualization.add_vminterface %} +
+ +
+ {% endif %} Interfaces
  • diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index 1a304931c..b2cb25e94 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -715,6 +715,29 @@ class VMInterfaceCreateForm(BootstrapMixin, forms.Form): self.fields['tagged_vlans'].widget.add_additional_query_param('site_id', site.pk) +class VMInterfaceCSVForm(CSVModelForm): + virtual_machine = CSVModelChoiceField( + queryset=VirtualMachine.objects.all(), + to_field_name='name' + ) + mode = CSVChoiceField( + choices=InterfaceModeChoices, + required=False, + help_text='IEEE 802.1Q operational mode (for L2 interfaces)' + ) + + class Meta: + model = VMInterface + fields = VMInterface.csv_headers + + def clean_enabled(self): + # Make sure enabled is True when it's not included in the uploaded data + if 'enabled' not in self.data: + return True + else: + return self.cleaned_data['enabled'] + + class VMInterfaceBulkEditForm(BootstrapMixin, BulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=VMInterface.objects.all(), diff --git a/netbox/virtualization/tests/test_views.py b/netbox/virtualization/tests/test_views.py index a7db2477f..408558779 100644 --- a/netbox/virtualization/tests/test_views.py +++ b/netbox/virtualization/tests/test_views.py @@ -189,16 +189,9 @@ class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase): } -# TODO: Update base class to DeviceComponentViewTestCase -# Blocked by #4721 class VMInterfaceTestCase( - ViewTestCases.ListObjectsViewTestCase, ViewTestCases.GetObjectViewTestCase, - ViewTestCases.EditObjectViewTestCase, - ViewTestCases.DeleteObjectViewTestCase, - ViewTestCases.BulkCreateObjectsViewTestCase, - ViewTestCases.BulkEditObjectsViewTestCase, - ViewTestCases.BulkDeleteObjectsViewTestCase, + ViewTestCases.DeviceComponentViewTestCase, ): model = VMInterface @@ -257,6 +250,13 @@ class VMInterfaceTestCase( 'tags': [t.pk for t in tags], } + cls.csv_data = ( + "virtual_machine,name", + "Virtual Machine 2,Interface 4", + "Virtual Machine 2,Interface 5", + "Virtual Machine 2,Interface 6", + ) + cls.bulk_edit_data = { 'virtual_machine': virtualmachines[1].pk, 'enabled': False, diff --git a/netbox/virtualization/urls.py b/netbox/virtualization/urls.py index f55a0b1ed..04e11682e 100644 --- a/netbox/virtualization/urls.py +++ b/netbox/virtualization/urls.py @@ -53,6 +53,7 @@ urlpatterns = [ # VM interfaces path('interfaces/', views.InterfaceListView.as_view(), name='vminterface_list'), path('interfaces/add/', views.InterfaceCreateView.as_view(), name='vminterface_add'), + path('interfaces/import/', views.InterfaceBulkImportView.as_view(), name='vminterface_import'), path('interfaces/edit/', views.InterfaceBulkEditView.as_view(), name='vminterface_bulk_edit'), path('interfaces/delete/', views.InterfaceBulkDeleteView.as_view(), name='vminterface_bulk_delete'), path('interfaces//', views.InterfaceView.as_view(), name='vminterface'), diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index d1d1e6145..3d1c9901d 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -349,6 +349,13 @@ class InterfaceDeleteView(ObjectDeleteView): queryset = VMInterface.objects.all() +class InterfaceBulkImportView(BulkImportView): + queryset = VMInterface.objects.all() + model_form = forms.VMInterfaceCSVForm + table = tables.VMInterfaceTable + default_return_url = 'virtualization:vminterface_list' + + class InterfaceBulkEditView(BulkEditView): queryset = VMInterface.objects.all() table = tables.VMInterfaceTable