diff --git a/docs/release-notes/version-2.11.md b/docs/release-notes/version-2.11.md index c80b74296..360fbff8a 100644 --- a/docs/release-notes/version-2.11.md +++ b/docs/release-notes/version-2.11.md @@ -95,6 +95,7 @@ A new cloud model has been introduced for representing the boundary of a network * [#5873](https://github.com/netbox-community/netbox/issues/5873) - Use numeric IDs in all object URLs * [#5990](https://github.com/netbox-community/netbox/issues/5990) - Deprecated `display_field` parameter for custom script ObjectVar and MultiObjectVar fields * [#5995](https://github.com/netbox-community/netbox/issues/5995) - Dropped backward compatibility for `queryset` parameter on ObjectVar and MultiObjectVar (use `model` instead) +* [#6014](https://github.com/netbox-community/netbox/issues/6014) - Moved virtual machine interfaces list to a separate view ### REST API Changes diff --git a/netbox/templates/virtualization/virtualmachine.html b/netbox/templates/virtualization/virtualmachine.html index 43f1fc5e7..71196b45a 100644 --- a/netbox/templates/virtualization/virtualmachine.html +++ b/netbox/templates/virtualization/virtualmachine.html @@ -1,44 +1,10 @@ -{% extends 'generic/object.html' %} +{% extends 'virtualization/virtualmachine/base.html' %} {% load buttons %} {% load custom_links %} {% load static %} {% load helpers %} {% load plugins %} -{% block breadcrumbs %} - {% if object.cluster %} -
  • {{ object.cluster }}
  • - {% endif %} -
  • {{ object }}
  • -{% endblock %} - -{% block buttons %} - {% if perms.virtualization.add_vminterface %} - - Add Interfaces - - {% endif %} - {{ block.super }} -{% endblock %} - -{% block tabs %} - -{% endblock %} - {% block content %}
    @@ -236,54 +202,6 @@ {% plugin_full_width_page object %}
    -
    -
    -
    - {% csrf_token %} - -
    -
    - Interfaces -
    - {% if request.user.is_authenticated %} - - {% endif %} -
    -
    - -
    -
    - {% include 'responsive_table.html' with table=vminterface_table %} - {% if perms.virtualization.add_vminterface or perms.virtualization.delete_vminterface %} - - {% endif %} -
    -
    - {% table_config_form vminterface_table %} -
    -
    {% include 'secrets/inc/private_key_modal.html' %} {% endblock %} diff --git a/netbox/templates/virtualization/virtualmachine/base.html b/netbox/templates/virtualization/virtualmachine/base.html new file mode 100644 index 000000000..00212aadd --- /dev/null +++ b/netbox/templates/virtualization/virtualmachine/base.html @@ -0,0 +1,45 @@ +{% extends 'generic/object.html' %} +{% load buttons %} +{% load helpers %} +{% load custom_links %} +{% load plugins %} + +{% block breadcrumbs %} +
  • Virtual Machines
  • +
  • {{ object.cluster }}
  • +
  • {{ object }}
  • +{% endblock %} + +{% block buttons %} + {% if perms.virtualization.add_vminterface %} + + Add Interfaces + + {% endif %} + {{ block.super }} +{% endblock %} + +{% block tabs %} + +{% endblock %} diff --git a/netbox/templates/virtualization/virtualmachine/interfaces.html b/netbox/templates/virtualization/virtualmachine/interfaces.html new file mode 100644 index 000000000..15d07310c --- /dev/null +++ b/netbox/templates/virtualization/virtualmachine/interfaces.html @@ -0,0 +1,54 @@ +{% extends 'virtualization/virtualmachine/base.html' %} +{% load render_table from django_tables2 %} +{% load helpers %} +{% load static %} + +{% block content %} +
    + {% csrf_token %} +
    +
    + Interfaces +
    + {% if request.user.is_authenticated %} + + {% endif %} +
    +
    + +
    +
    + {% render_table interface_table 'inc/table.html' %} + +
    +
    + {% table_config_form interface_table %} +{% endblock %} + +{% block javascript %} + + + +{% endblock %} diff --git a/netbox/virtualization/tests/test_views.py b/netbox/virtualization/tests/test_views.py index cb0055789..0e1dee9e4 100644 --- a/netbox/virtualization/tests/test_views.py +++ b/netbox/virtualization/tests/test_views.py @@ -1,3 +1,5 @@ +from django.test import override_settings +from django.urls import reverse from netaddr import EUI from dcim.choices import InterfaceModeChoices @@ -196,6 +198,19 @@ class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase): 'comments': 'New comments', } + @override_settings(EXEMPT_VIEW_PERMISSIONS=['*']) + def test_device_interfaces(self): + virtualmachine = VirtualMachine.objects.first() + vminterfaces = ( + VMInterface(virtual_machine=virtualmachine, name='Interface 1'), + VMInterface(virtual_machine=virtualmachine, name='Interface 2'), + VMInterface(virtual_machine=virtualmachine, name='Interface 3'), + ) + VMInterface.objects.bulk_create(vminterfaces) + + url = reverse('virtualization:virtualmachine_interfaces', kwargs={'pk': virtualmachine.pk}) + self.assertHttpStatus(self.client.get(url), 200) + class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase): model = VMInterface diff --git a/netbox/virtualization/urls.py b/netbox/virtualization/urls.py index 17750e806..457b73024 100644 --- a/netbox/virtualization/urls.py +++ b/netbox/virtualization/urls.py @@ -51,6 +51,7 @@ urlpatterns = [ path('virtual-machines/edit/', views.VirtualMachineBulkEditView.as_view(), name='virtualmachine_bulk_edit'), path('virtual-machines/delete/', views.VirtualMachineBulkDeleteView.as_view(), name='virtualmachine_bulk_delete'), path('virtual-machines//', views.VirtualMachineView.as_view(), name='virtualmachine'), + path('virtual-machines//interfaces/', views.VirtualMachineInterfacesView.as_view(), name='virtualmachine_interfaces'), path('virtual-machines//edit/', views.VirtualMachineEditView.as_view(), name='virtualmachine_edit'), path('virtual-machines//delete/', views.VirtualMachineDeleteView.as_view(), name='virtualmachine_delete'), path('virtual-machines//config-context/', views.VirtualMachineConfigContextView.as_view(), name='virtualmachine_configcontext'), diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index a874d2a92..75f2368fb 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -322,6 +322,30 @@ class VirtualMachineView(generic.ObjectView): } +class VirtualMachineInterfacesView(generic.ObjectView): + queryset = VirtualMachine.objects.all() + template_name = 'virtualization/virtualmachine/interfaces.html' + + def get_extra_context(self, request, instance): + interfaces = instance.interfaces.restrict(request.user, 'view').prefetch_related( + Prefetch('ip_addresses', queryset=IPAddress.objects.restrict(request.user)), + 'tags', + ) + interface_table = tables.VirtualMachineVMInterfaceTable( + data=interfaces, + user=request.user, + orderable=False + ) + if request.user.has_perm('virtualization.change_vminterface') or \ + request.user.has_perm('virtualization.delete_vminterface'): + interface_table.columns.show('pk') + + return { + 'interface_table': interface_table, + 'active_tab': 'interfaces', + } + + class VirtualMachineConfigContextView(ObjectConfigContextView): queryset = VirtualMachine.objects.annotate_config_context_data() base_template = 'virtualization/virtualmachine.html'