mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-21 11:37:21 -06:00
Implemented bulk interface creation for virtual machines
This commit is contained in:
parent
ef2dd673ec
commit
700194b80d
@ -903,11 +903,12 @@ class DeviceBulkAddComponentForm(BootstrapMixin, forms.Form):
|
|||||||
name_pattern = ExpandableNameField(label='Name')
|
name_pattern = ExpandableNameField(label='Name')
|
||||||
|
|
||||||
|
|
||||||
class DeviceBulkAddInterfaceForm(forms.ModelForm, DeviceBulkAddComponentForm):
|
class DeviceBulkAddInterfaceForm(DeviceBulkAddComponentForm):
|
||||||
|
form_factor = forms.ChoiceField(choices=IFACE_FF_CHOICES)
|
||||||
class Meta:
|
enabled = forms.BooleanField(required=False, initial=True)
|
||||||
model = Interface
|
mtu = forms.IntegerField(required=False, min_value=1, max_value=32767, label='MTU')
|
||||||
fields = ['pk', 'name_pattern', 'form_factor', 'mgmt_only', 'description']
|
mgmt_only = forms.BooleanField(required=False, label='OOB Management')
|
||||||
|
description = forms.CharField(max_length=100, required=False)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -23,8 +23,8 @@ from extras.models import Graph, TopologyMap, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_S
|
|||||||
from utilities.forms import ConfirmationForm
|
from utilities.forms import ConfirmationForm
|
||||||
from utilities.paginator import EnhancedPaginator
|
from utilities.paginator import EnhancedPaginator
|
||||||
from utilities.views import (
|
from utilities.views import (
|
||||||
BulkDeleteView, BulkEditView, BulkImportView, ComponentCreateView, ComponentDeleteView, ComponentEditView,
|
BulkComponentCreateView, BulkDeleteView, BulkEditView, BulkImportView, ComponentCreateView, ComponentDeleteView,
|
||||||
ObjectDeleteView, ObjectEditView, ObjectListView,
|
ComponentEditView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
||||||
)
|
)
|
||||||
from . import filters, forms, tables
|
from . import filters, forms, tables
|
||||||
from .models import (
|
from .models import (
|
||||||
@ -1569,109 +1569,67 @@ class DeviceBayBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Bulk device component creation
|
# Bulk Device component creation
|
||||||
#
|
#
|
||||||
|
|
||||||
class DeviceBulkAddComponentView(View):
|
class DeviceBulkAddConsolePortView(PermissionRequiredMixin, BulkComponentCreateView):
|
||||||
"""
|
|
||||||
Add one or more components (e.g. interfaces) to a selected set of Devices.
|
|
||||||
"""
|
|
||||||
form = forms.DeviceBulkAddComponentForm
|
|
||||||
model = None
|
|
||||||
model_form = None
|
|
||||||
|
|
||||||
def get(self):
|
|
||||||
return redirect('dcim:device_list')
|
|
||||||
|
|
||||||
def post(self, request):
|
|
||||||
|
|
||||||
# Are we editing *all* objects in the queryset or just a selected subset?
|
|
||||||
if request.POST.get('_all'):
|
|
||||||
pk_list = [obj.pk for obj in filters.DeviceFilter(request.GET, Device.objects.all())]
|
|
||||||
else:
|
|
||||||
pk_list = [int(pk) for pk in request.POST.getlist('pk')]
|
|
||||||
|
|
||||||
if '_create' in request.POST:
|
|
||||||
form = self.form(request.POST)
|
|
||||||
if form.is_valid():
|
|
||||||
|
|
||||||
new_components = []
|
|
||||||
data = deepcopy(form.cleaned_data)
|
|
||||||
for device in data['pk']:
|
|
||||||
|
|
||||||
names = data['name_pattern']
|
|
||||||
for name in names:
|
|
||||||
component_data = {
|
|
||||||
'device': device.pk,
|
|
||||||
'name': name,
|
|
||||||
}
|
|
||||||
component_data.update(data)
|
|
||||||
component_form = self.model_form(component_data)
|
|
||||||
if component_form.is_valid():
|
|
||||||
new_components.append(component_form.save(commit=False))
|
|
||||||
else:
|
|
||||||
for field, errors in component_form.errors.as_data().items():
|
|
||||||
for e in errors:
|
|
||||||
form.add_error(field, '{} {}: {}'.format(device, name, ', '.join(e)))
|
|
||||||
|
|
||||||
if not form.errors:
|
|
||||||
self.model.objects.bulk_create(new_components)
|
|
||||||
messages.success(request, "Added {} {} to {} devices.".format(
|
|
||||||
len(new_components), self.model._meta.verbose_name_plural, len(form.cleaned_data['pk'])
|
|
||||||
))
|
|
||||||
return redirect('dcim:device_list')
|
|
||||||
|
|
||||||
else:
|
|
||||||
form = self.form(initial={'pk': pk_list})
|
|
||||||
|
|
||||||
selected_devices = Device.objects.filter(pk__in=pk_list)
|
|
||||||
if not selected_devices:
|
|
||||||
messages.warning(request, "No devices were selected.")
|
|
||||||
return redirect('dcim:device_list')
|
|
||||||
|
|
||||||
return render(request, 'dcim/device_bulk_add_component.html', {
|
|
||||||
'form': form,
|
|
||||||
'component_name': self.model._meta.verbose_name_plural,
|
|
||||||
'selected_devices': selected_devices,
|
|
||||||
'return_url': reverse('dcim:device_list'),
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceBulkAddConsolePortView(PermissionRequiredMixin, DeviceBulkAddComponentView):
|
|
||||||
permission_required = 'dcim.add_consoleport'
|
permission_required = 'dcim.add_consoleport'
|
||||||
|
parent_model = Device
|
||||||
|
parent_field = 'device'
|
||||||
|
form = forms.DeviceBulkAddComponentForm
|
||||||
model = ConsolePort
|
model = ConsolePort
|
||||||
model_form = forms.ConsolePortForm
|
model_form = forms.ConsolePortForm
|
||||||
|
table = tables.DeviceTable
|
||||||
|
|
||||||
|
|
||||||
class DeviceBulkAddConsoleServerPortView(PermissionRequiredMixin, DeviceBulkAddComponentView):
|
class DeviceBulkAddConsoleServerPortView(PermissionRequiredMixin, BulkComponentCreateView):
|
||||||
permission_required = 'dcim.add_consoleserverport'
|
permission_required = 'dcim.add_consoleserverport'
|
||||||
|
parent_model = Device
|
||||||
|
parent_field = 'device'
|
||||||
|
form = forms.DeviceBulkAddComponentForm
|
||||||
model = ConsoleServerPort
|
model = ConsoleServerPort
|
||||||
model_form = forms.ConsoleServerPortForm
|
model_form = forms.ConsoleServerPortForm
|
||||||
|
table = tables.DeviceTable
|
||||||
|
|
||||||
|
|
||||||
class DeviceBulkAddPowerPortView(PermissionRequiredMixin, DeviceBulkAddComponentView):
|
class DeviceBulkAddPowerPortView(PermissionRequiredMixin, BulkComponentCreateView):
|
||||||
permission_required = 'dcim.add_powerport'
|
permission_required = 'dcim.add_powerport'
|
||||||
|
parent_model = Device
|
||||||
|
parent_field = 'device'
|
||||||
|
form = forms.DeviceBulkAddComponentForm
|
||||||
model = PowerPort
|
model = PowerPort
|
||||||
model_form = forms.PowerPortForm
|
model_form = forms.PowerPortForm
|
||||||
|
table = tables.DeviceTable
|
||||||
|
|
||||||
|
|
||||||
class DeviceBulkAddPowerOutletView(PermissionRequiredMixin, DeviceBulkAddComponentView):
|
class DeviceBulkAddPowerOutletView(PermissionRequiredMixin, BulkComponentCreateView):
|
||||||
permission_required = 'dcim.add_poweroutlet'
|
permission_required = 'dcim.add_poweroutlet'
|
||||||
|
parent_model = Device
|
||||||
|
parent_field = 'device'
|
||||||
|
form = forms.DeviceBulkAddComponentForm
|
||||||
model = PowerOutlet
|
model = PowerOutlet
|
||||||
model_form = forms.PowerOutletForm
|
model_form = forms.PowerOutletForm
|
||||||
|
table = tables.DeviceTable
|
||||||
|
|
||||||
|
|
||||||
class DeviceBulkAddInterfaceView(PermissionRequiredMixin, DeviceBulkAddComponentView):
|
class DeviceBulkAddInterfaceView(PermissionRequiredMixin, BulkComponentCreateView):
|
||||||
permission_required = 'dcim.add_interface'
|
permission_required = 'dcim.add_interface'
|
||||||
|
parent_model = Device
|
||||||
|
parent_field = 'device'
|
||||||
form = forms.DeviceBulkAddInterfaceForm
|
form = forms.DeviceBulkAddInterfaceForm
|
||||||
model = Interface
|
model = Interface
|
||||||
model_form = forms.InterfaceForm
|
model_form = forms.InterfaceForm
|
||||||
|
table = tables.DeviceTable
|
||||||
|
|
||||||
|
|
||||||
class DeviceBulkAddDeviceBayView(PermissionRequiredMixin, DeviceBulkAddComponentView):
|
class DeviceBulkAddDeviceBayView(PermissionRequiredMixin, BulkComponentCreateView):
|
||||||
permission_required = 'dcim.add_devicebay'
|
permission_required = 'dcim.add_devicebay'
|
||||||
|
parent_model = Device
|
||||||
|
parent_field = 'device'
|
||||||
|
form = forms.DeviceBulkAddComponentForm
|
||||||
model = DeviceBay
|
model = DeviceBay
|
||||||
model_form = forms.DeviceBayForm
|
model_form = forms.DeviceBayForm
|
||||||
|
table = tables.DeviceTable
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -14,21 +14,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-7">
|
<div class="col-md-7">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading"><strong>Selected Devices</strong></div>
|
{% include 'inc/table.html' %}
|
||||||
<table class="panel-body table table-hover">
|
|
||||||
<tr>
|
|
||||||
<th>Device</th>
|
|
||||||
<th>Type</th>
|
|
||||||
<th>Role</th>
|
|
||||||
</tr>
|
|
||||||
{% for device in selected_devices %}
|
|
||||||
<tr>
|
|
||||||
<td><a href="{% url 'dcim:device' pk=device.pk %}">{{ device }}</a></td>
|
|
||||||
<td>{{ device.device_type.full_name }}</td>
|
|
||||||
<td>{{ device.device_role }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-5">
|
<div class="col-md-5">
|
@ -0,0 +1,14 @@
|
|||||||
|
{% extends 'utilities/obj_table.html' %}
|
||||||
|
|
||||||
|
{% block extra_actions %}
|
||||||
|
{% if perms.virtualization.change_virtualmachine %}
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-sm btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add Components <span class="caret"></span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
{% if perms.dcim.add_interface %}<li><a href="{% url 'virtualization:virtualmachine_bulk_add_interface' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="formaction">Interfaces</a></li>{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
@ -17,7 +17,7 @@
|
|||||||
<h1>{% block title %}Virtual Machines{% endblock %}</h1>
|
<h1>{% block title %}Virtual Machines{% endblock %}</h1>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
{% include 'utilities/obj_table.html' with bulk_edit_url='virtualization:virtualmachine_bulk_edit' bulk_delete_url='virtualization:virtualmachine_bulk_delete' %}
|
{% include 'virtualization/inc/virtualmachine_table.html' with bulk_edit_url='virtualization:virtualmachine_bulk_edit' bulk_delete_url='virtualization:virtualmachine_bulk_delete' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
{% include 'inc/search_panel.html' %}
|
{% include 'inc/search_panel.html' %}
|
||||||
|
@ -705,6 +705,9 @@ class BulkDeleteView(View):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class ComponentCreateView(View):
|
class ComponentCreateView(View):
|
||||||
|
"""
|
||||||
|
Add one or more components (e.g. interfaces, console ports, etc.) to a Device or VirtualMachine.
|
||||||
|
"""
|
||||||
parent_model = None
|
parent_model = None
|
||||||
parent_field = None
|
parent_field = None
|
||||||
model = None
|
model = None
|
||||||
@ -786,3 +789,82 @@ class ComponentDeleteView(ObjectDeleteView):
|
|||||||
|
|
||||||
def get_return_url(self, request, obj):
|
def get_return_url(self, request, obj):
|
||||||
return getattr(obj, self.parent_field).get_absolute_url()
|
return getattr(obj, self.parent_field).get_absolute_url()
|
||||||
|
|
||||||
|
|
||||||
|
class BulkComponentCreateView(View):
|
||||||
|
"""
|
||||||
|
Add one or more components (e.g. interfaces, console ports, etc.) to a set of Devices or VirtualMachines.
|
||||||
|
"""
|
||||||
|
parent_model = None
|
||||||
|
parent_field = None
|
||||||
|
form = None
|
||||||
|
model = None
|
||||||
|
model_form = None
|
||||||
|
filter = None
|
||||||
|
table = None
|
||||||
|
template_name = 'utilities/obj_bulk_add_component.html'
|
||||||
|
default_return_url = 'home'
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
|
||||||
|
# Are we editing *all* objects in the queryset or just a selected subset?
|
||||||
|
if request.POST.get('_all') and self.filter is not None:
|
||||||
|
pk_list = [obj.pk for obj in self.filter(request.GET, self.model.objects.only('pk')).qs]
|
||||||
|
else:
|
||||||
|
pk_list = [int(pk) for pk in request.POST.getlist('pk')]
|
||||||
|
|
||||||
|
# Determine URL to redirect users upon modification of objects
|
||||||
|
posted_return_url = request.POST.get('return_url')
|
||||||
|
if posted_return_url and is_safe_url(url=posted_return_url, host=request.get_host()):
|
||||||
|
return_url = posted_return_url
|
||||||
|
else:
|
||||||
|
return_url = reverse(self.default_return_url)
|
||||||
|
|
||||||
|
selected_objects = self.parent_model.objects.filter(pk__in=pk_list)
|
||||||
|
if not selected_objects:
|
||||||
|
messages.warning(request, "No {} were selected.".format(self.parent_model._meta.verbose_name_plural))
|
||||||
|
return redirect(return_url)
|
||||||
|
table = self.table(selected_objects)
|
||||||
|
|
||||||
|
if '_create' in request.POST:
|
||||||
|
form = self.form(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
|
||||||
|
new_components = []
|
||||||
|
data = deepcopy(form.cleaned_data)
|
||||||
|
for obj in data['pk']:
|
||||||
|
|
||||||
|
names = data['name_pattern']
|
||||||
|
for name in names:
|
||||||
|
component_data = {
|
||||||
|
self.parent_field: obj.pk,
|
||||||
|
'name': name,
|
||||||
|
}
|
||||||
|
component_data.update(data)
|
||||||
|
component_form = self.model_form(component_data)
|
||||||
|
if component_form.is_valid():
|
||||||
|
new_components.append(component_form.save(commit=False))
|
||||||
|
else:
|
||||||
|
for field, errors in component_form.errors.as_data().items():
|
||||||
|
for e in errors:
|
||||||
|
form.add_error(field, '{} {}: {}'.format(obj, name, ', '.join(e)))
|
||||||
|
|
||||||
|
if not form.errors:
|
||||||
|
self.model.objects.bulk_create(new_components)
|
||||||
|
messages.success(request, "Added {} {} to {} {}.".format(
|
||||||
|
len(new_components),
|
||||||
|
self.model._meta.verbose_name_plural,
|
||||||
|
len(form.cleaned_data['pk']),
|
||||||
|
self.parent_model._meta.verbose_name_plural
|
||||||
|
))
|
||||||
|
return redirect(return_url)
|
||||||
|
|
||||||
|
else:
|
||||||
|
form = self.form(initial={'pk': pk_list})
|
||||||
|
|
||||||
|
return render(request, self.template_name, {
|
||||||
|
'form': form,
|
||||||
|
'component_name': self.model._meta.verbose_name_plural,
|
||||||
|
'table': table,
|
||||||
|
'return_url': reverse('dcim:device_list'),
|
||||||
|
})
|
||||||
|
@ -301,3 +301,19 @@ class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
nullable_fields = ['mtu', 'description']
|
nullable_fields = ['mtu', 'description']
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Bulk VirtualMachine component creation
|
||||||
|
#
|
||||||
|
|
||||||
|
class VirtualMachineBulkAddComponentForm(BootstrapMixin, forms.Form):
|
||||||
|
pk = forms.ModelMultipleChoiceField(queryset=VirtualMachine.objects.all(), widget=forms.MultipleHiddenInput)
|
||||||
|
name_pattern = ExpandableNameField(label='Name')
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualMachineBulkAddInterfaceForm(VirtualMachineBulkAddComponentForm):
|
||||||
|
form_factor = forms.ChoiceField(choices=VIFACE_FF_CHOICES)
|
||||||
|
enabled = forms.BooleanField(required=False, initial=True)
|
||||||
|
mtu = forms.IntegerField(required=False, min_value=1, max_value=32767, label='MTU')
|
||||||
|
description = forms.CharField(max_length=100, required=False)
|
||||||
|
@ -45,7 +45,7 @@ urlpatterns = [
|
|||||||
url(r'^virtual-machines/(?P<virtualmachine>\d+)/services/assign/$', ServiceCreateView.as_view(), name='virtualmachine_service_assign'),
|
url(r'^virtual-machines/(?P<virtualmachine>\d+)/services/assign/$', ServiceCreateView.as_view(), name='virtualmachine_service_assign'),
|
||||||
|
|
||||||
# VM interfaces
|
# VM interfaces
|
||||||
# url(r'^virtual-machines/interfaces/add/$', views.VMBulkAddInterfaceView.as_view(), name='vm_bulk_add_interface'),
|
url(r'^virtual-machines/interfaces/add/$', views.VirtualMachineBulkAddInterfaceView.as_view(), name='virtualmachine_bulk_add_interface'),
|
||||||
url(r'^virtual-machines/(?P<pk>\d+)/interfaces/add/$', views.InterfaceCreateView.as_view(), name='interface_add'),
|
url(r'^virtual-machines/(?P<pk>\d+)/interfaces/add/$', views.InterfaceCreateView.as_view(), name='interface_add'),
|
||||||
url(r'^virtual-machines/(?P<pk>\d+)/interfaces/edit/$', views.InterfaceBulkEditView.as_view(), name='interface_bulk_edit'),
|
url(r'^virtual-machines/(?P<pk>\d+)/interfaces/edit/$', views.InterfaceBulkEditView.as_view(), name='interface_bulk_edit'),
|
||||||
url(r'^virtual-machines/(?P<pk>\d+)/interfaces/delete/$', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'),
|
url(r'^virtual-machines/(?P<pk>\d+)/interfaces/delete/$', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'),
|
||||||
|
@ -11,8 +11,8 @@ from dcim.models import Device, Interface
|
|||||||
from dcim.tables import DeviceTable
|
from dcim.tables import DeviceTable
|
||||||
from ipam.models import Service
|
from ipam.models import Service
|
||||||
from utilities.views import (
|
from utilities.views import (
|
||||||
BulkDeleteView, BulkEditView, BulkImportView, ComponentCreateView, ComponentDeleteView, ComponentEditView,
|
BulkComponentCreateView, BulkDeleteView, BulkEditView, BulkImportView, ComponentCreateView, ComponentDeleteView,
|
||||||
ObjectDeleteView, ObjectEditView, ObjectListView,
|
ComponentEditView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
||||||
)
|
)
|
||||||
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
||||||
from . import filters
|
from . import filters
|
||||||
@ -344,3 +344,17 @@ class InterfaceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
cls = Interface
|
cls = Interface
|
||||||
parent_cls = VirtualMachine
|
parent_cls = VirtualMachine
|
||||||
table = tables.InterfaceTable
|
table = tables.InterfaceTable
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Bulk Device component creation
|
||||||
|
#
|
||||||
|
|
||||||
|
class VirtualMachineBulkAddInterfaceView(PermissionRequiredMixin, BulkComponentCreateView):
|
||||||
|
permission_required = 'dcim.add_interface'
|
||||||
|
parent_model = VirtualMachine
|
||||||
|
parent_field = 'virtual_machine'
|
||||||
|
form = forms.VirtualMachineBulkAddInterfaceForm
|
||||||
|
model = Interface
|
||||||
|
model_form = forms.InterfaceForm
|
||||||
|
table = tables.VirtualMachineTable
|
||||||
|
Loading…
Reference in New Issue
Block a user