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