Closes #6014: Move virtual machine interfaces list to a separate view

This commit is contained in:
Jeremy Stretch 2021-03-26 20:19:19 -04:00
parent 1544823d73
commit 0364d8cd43
7 changed files with 141 additions and 83 deletions

View File

@ -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

View File

@ -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 %}
<li><a href="{{ object.cluster.get_absolute_url }}">{{ object.cluster }}</a></li>
{% endif %}
<li>{{ object }}</li>
{% endblock %}
{% block buttons %}
{% if perms.virtualization.add_vminterface %}
<a href="{% url 'virtualization:vminterface_add' %}?virtual_machine={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-primary">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add Interfaces
</a>
{% endif %}
{{ block.super }}
{% endblock %}
{% block tabs %}
<ul class="nav nav-tabs">
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
<a href="{{ object.get_absolute_url }}">Virtual Machine</a>
</li>
{% if perms.extras.view_configcontext %}
<li role="presentation"{% if active_tab == 'config-context' %} class="active"{% endif %}>
<a href="{% url 'virtualization:virtualmachine_configcontext' pk=object.pk %}">Config Context</a>
</li>
{% endif %}
{% if perms.extras.view_objectchange %}
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
<a href="{% url 'virtualization:virtualmachine_changelog' pk=object.pk %}">Change Log</a>
</li>
{% endif %}
</ul>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
@ -236,54 +202,6 @@
{% plugin_full_width_page object %}
</div>
</div>
<div class="row">
<div class="col-md-12">
<form method="post">
{% csrf_token %}
<input type="hidden" name="virtual_machine" value="{{ object.pk }}" />
<div class="panel panel-default">
<div class="panel-heading">
<strong>Interfaces</strong>
<div class="pull-right noprint">
{% if request.user.is_authenticated %}
<button type="button" class="btn btn-default btn-xs" data-toggle="modal" data-target="#VirtualMachineVMInterfaceTable_config" title="Configure table"><i class="mdi mdi-cog"></i> Configure</button>
{% endif %}
</div>
<div class="pull-right col-md-2 noprint">
<input class="form-control interface-filter" type="text" placeholder="Filter" title="Filter text (regular expressions supported)" style="height: 23px" />
</div>
</div>
{% include 'responsive_table.html' with table=vminterface_table %}
{% if perms.virtualization.add_vminterface or perms.virtualization.delete_vminterface %}
<div class="panel-footer noprint">
{% if interfaces and perms.virtualization.change_vminterface %}
<button type="submit" name="_rename" formaction="{% url 'virtualization:vminterface_bulk_rename' %}?return_url={{ object.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
</button>
<button type="submit" name="_edit" formaction="{% url 'virtualization:vminterface_bulk_edit' %}?return_url={{ object.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
</button>
{% endif %}
{% if interfaces and perms.virtualization.delete_vminterface %}
<button type="submit" name="_delete" formaction="{% url 'virtualization:vminterface_bulk_delete' %}?return_url={{ object.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
</button>
{% endif %}
{% if perms.virtualization.add_vminterface %}
<div class="pull-right">
<a href="{% url 'virtualization:vminterface_add' %}?virtual_machine={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-primary btn-xs">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add interfaces
</a>
</div>
<div class="clearfix"></div>
{% endif %}
</div>
{% endif %}
</div>
</form>
{% table_config_form vminterface_table %}
</div>
</div>
{% include 'secrets/inc/private_key_modal.html' %}
{% endblock %}

View File

@ -0,0 +1,45 @@
{% extends 'generic/object.html' %}
{% load buttons %}
{% load helpers %}
{% load custom_links %}
{% load plugins %}
{% block breadcrumbs %}
<li><a href="{% url 'virtualization:virtualmachine_list' %}">Virtual Machines</a></li>
<li><a href="{% url 'virtualization:virtualmachine_list' %}?cluster_id={{ object.cluster.pk }}">{{ object.cluster }}</a></li>
<li>{{ object }}</li>
{% endblock %}
{% block buttons %}
{% if perms.virtualization.add_vminterface %}
<a href="{% url 'virtualization:vminterface_add' %}?virtual_machine={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-primary">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add Interfaces
</a>
{% endif %}
{{ block.super }}
{% endblock %}
{% block tabs %}
<ul class="nav nav-tabs">
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
<a href="{{ object.get_absolute_url }}">Virtual Machine</a>
</li>
{% with interface_count=object.interfaces.count %}
{% if interface_count %}
<li role="presentation" {% if active_tab == 'interfaces' %} class="active"{% endif %}>
<a href="{% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}">Interfaces {% badge interface_count %}</a>
</li>
{% endif %}
{% endwith %}
{% if perms.extras.view_configcontext %}
<li role="presentation"{% if active_tab == 'config-context' %} class="active"{% endif %}>
<a href="{% url 'virtualization:virtualmachine_configcontext' pk=object.pk %}">Config Context</a>
</li>
{% endif %}
{% if perms.extras.view_objectchange %}
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
<a href="{% url 'virtualization:virtualmachine_changelog' pk=object.pk %}">Change Log</a>
</li>
{% endif %}
</ul>
{% endblock %}

View File

@ -0,0 +1,54 @@
{% extends 'virtualization/virtualmachine/base.html' %}
{% load render_table from django_tables2 %}
{% load helpers %}
{% load static %}
{% block content %}
<form method="post">
{% csrf_token %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Interfaces</strong>
<div class="pull-right noprint">
{% if request.user.is_authenticated %}
<button type="button" class="btn btn-default btn-xs" data-toggle="modal" data-target="#VirtualMachineVMInterfaceTable_config" title="Configure table"><i class="mdi mdi-cog"></i> Configure</button>
{% endif %}
</div>
<div class="pull-right col-md-2 noprint">
<input class="form-control interface-filter" type="text" placeholder="Filter" title="Filter text (regular expressions supported)" style="height: 23px" />
</div>
</div>
{% render_table interface_table 'inc/table.html' %}
<div class="panel-footer noprint">
{% if perms.virtualization.change_vminterface %}
<button type="submit" name="_rename" formaction="{% url 'virtualization:vminterface_bulk_rename' %}?return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" class="btn btn-warning btn-xs">
<span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
</button>
<button type="submit" name="_edit" formaction="{% url 'virtualization:vminterface_bulk_edit' %}?virtualmachine={{ object.pk }}&return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" class="btn btn-warning btn-xs">
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
</button>
{% endif %}
{% if perms.virtualization.delete_vminterface %}
<button type="submit" name="_delete" formaction="{% url 'virtualization:vminterface_bulk_delete' %}?return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" class="btn btn-danger btn-xs">
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
</button>
{% endif %}
{% if perms.virtualization.add_vminterface %}
<div class="pull-right">
<a href="{% url 'virtualization:vminterface_add' %}?virtualmachine={{ object.pk }}&return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}" class="btn btn-primary btn-xs">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add interfaces
</a>
</div>
{% endif %}
<div class="clearfix"></div>
</div>
</div>
</form>
{% table_config_form interface_table %}
{% endblock %}
{% block javascript %}
<script src="{% static 'js/connection_toggles.js' %}?v{{ settings.VERSION }}"></script>
<script src="{% static 'js/interface_filtering.js' %}?v{{ settings.VERSION }}"></script>
<script src="{% static 'js/tableconfig.js' %}?v{{ settings.VERSION }}"></script>
{% endblock %}

View File

@ -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

View File

@ -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/<int:pk>/', views.VirtualMachineView.as_view(), name='virtualmachine'),
path('virtual-machines/<int:pk>/interfaces/', views.VirtualMachineInterfacesView.as_view(), name='virtualmachine_interfaces'),
path('virtual-machines/<int:pk>/edit/', views.VirtualMachineEditView.as_view(), name='virtualmachine_edit'),
path('virtual-machines/<int:pk>/delete/', views.VirtualMachineDeleteView.as_view(), name='virtualmachine_delete'),
path('virtual-machines/<int:pk>/config-context/', views.VirtualMachineConfigContextView.as_view(), name='virtualmachine_configcontext'),

View File

@ -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'