mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-22 20:12:00 -06:00
Fixes #664: Re-implemented view for bulk creation of interfaces across multiple devices
This commit is contained in:
parent
76c6fbbfba
commit
bbac6e2ba6
@ -584,6 +584,18 @@ class DeviceBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
||||
nullable_fields = ['tenant', 'platform']
|
||||
|
||||
|
||||
class DeviceBulkAddComponentForm(forms.Form, BootstrapMixin):
|
||||
pk = forms.ModelMultipleChoiceField(queryset=Device.objects.all(), widget=forms.MultipleHiddenInput)
|
||||
name_pattern = ExpandableNameField(label='Name')
|
||||
|
||||
|
||||
class DeviceBulkAddInterfaceForm(forms.ModelForm, DeviceBulkAddComponentForm):
|
||||
|
||||
class Meta:
|
||||
model = Interface
|
||||
fields = ['name_pattern', 'form_factor', 'mgmt_only', 'description']
|
||||
|
||||
|
||||
class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||
model = Device
|
||||
site = FilterChoiceField(queryset=Site.objects.annotate(filter_count=Count('racks__devices')), to_field_name='slug')
|
||||
@ -1014,10 +1026,6 @@ class InterfaceCreateForm(forms.ModelForm, BootstrapMixin):
|
||||
fields = ['name_pattern', 'form_factor', 'mac_address', 'mgmt_only', 'description']
|
||||
|
||||
|
||||
class InterfaceBulkCreateForm(InterfaceCreateForm, BootstrapMixin):
|
||||
pk = forms.ModelMultipleChoiceField(queryset=Device.objects.all(), widget=forms.MultipleHiddenInput)
|
||||
|
||||
|
||||
class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(queryset=Interface.objects.all(), widget=forms.MultipleHiddenInput)
|
||||
form_factor = forms.ChoiceField(choices=add_blank_choice(IFACE_FF_CHOICES), required=False)
|
||||
@ -1250,7 +1258,7 @@ class IPAddressForm(BootstrapMixin, CustomFieldForm):
|
||||
|
||||
|
||||
#
|
||||
# Interfaces
|
||||
# Modules
|
||||
#
|
||||
|
||||
class ModuleForm(forms.ModelForm, BootstrapMixin):
|
||||
|
@ -154,7 +154,7 @@ urlpatterns = [
|
||||
url(r'^interface-connections/import/$', views.InterfaceConnectionsBulkImportView.as_view(), name='interface_connections_import'),
|
||||
|
||||
# Interfaces
|
||||
url(r'^devices/interfaces/add/$', views.InterfaceBulkAddView.as_view(), name='interface_add_multi'),
|
||||
url(r'^devices/interfaces/add/$', views.DeviceBulkAddInterfaceView.as_view(), name='device_bulk_add_interface'),
|
||||
url(r'^devices/(?P<pk>\d+)/interfaces/add/$', views.interface_add, name='interface_add'),
|
||||
url(r'^devices/(?P<pk>\d+)/interfaces/edit/$', views.InterfaceBulkEditView.as_view(), name='interface_bulk_edit'),
|
||||
url(r'^devices/(?P<pk>\d+)/interfaces/delete/$', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'),
|
||||
|
@ -1,3 +1,4 @@
|
||||
from copy import deepcopy
|
||||
import re
|
||||
from natsort import natsorted
|
||||
from operator import attrgetter
|
||||
@ -7,6 +8,7 @@ from django.contrib.auth.decorators import permission_required
|
||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import transaction
|
||||
from django.db.models import Count
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
@ -686,6 +688,80 @@ def device_lldp_neighbors(request, pk):
|
||||
})
|
||||
|
||||
|
||||
class DeviceBulkAddComponentView(View):
|
||||
"""
|
||||
Add one or more components (e.g. interfaces) to a selected set of Devices.
|
||||
"""
|
||||
form = None
|
||||
component_cls = None
|
||||
component_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 = [int(pk) for pk in request.POST.get('pk_all').split(',') if pk]
|
||||
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.component_form(component_data)
|
||||
if component_form.is_valid():
|
||||
new_components.append(component_form.save(commit=False))
|
||||
else:
|
||||
form.add_error('name_pattern', "Duplicate {} name for {}: {}".format(
|
||||
self.component_cls._meta.verbose_name, device, name
|
||||
))
|
||||
|
||||
if not form.errors:
|
||||
self.component_cls.objects.bulk_create(new_components)
|
||||
messages.success(request, u"Added {} {} to {} devices.".format(
|
||||
len(new_components), self.component_cls._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, u"No devices were selected.")
|
||||
return redirect('dcim:device_list')
|
||||
|
||||
return render(request, 'dcim/device_bulk_add_component.html', {
|
||||
'form': form,
|
||||
'component_name': self.component_cls._meta.verbose_name_plural,
|
||||
'selected_devices': selected_devices,
|
||||
'cancel_url': reverse('dcim:device_list'),
|
||||
})
|
||||
|
||||
|
||||
class DeviceBulkAddInterfaceView(DeviceBulkAddComponentView):
|
||||
"""
|
||||
Add one or more components (e.g. interfaces) to a selected set of Devices.
|
||||
"""
|
||||
form = forms.DeviceBulkAddInterfaceForm
|
||||
component_cls = Interface
|
||||
component_form = forms.InterfaceForm
|
||||
|
||||
|
||||
#
|
||||
# Console ports
|
||||
#
|
||||
@ -1236,39 +1312,6 @@ class InterfaceDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
||||
model = Interface
|
||||
|
||||
|
||||
class InterfaceBulkAddView(PermissionRequiredMixin, BulkEditView):
|
||||
permission_required = 'dcim.add_interface'
|
||||
cls = Device
|
||||
form = forms.InterfaceBulkCreateForm
|
||||
template_name = 'dcim/interface_add_multi.html'
|
||||
default_redirect_url = 'dcim:device_list'
|
||||
|
||||
def update_objects(self, pk_list, form, fields):
|
||||
|
||||
selected_devices = Device.objects.filter(pk__in=pk_list)
|
||||
interfaces = []
|
||||
|
||||
for device in selected_devices:
|
||||
for name in form.cleaned_data['name_pattern']:
|
||||
iface_form = forms.InterfaceForm({
|
||||
'device': device.pk,
|
||||
'name': name,
|
||||
'mac_address': form.cleaned_data['mac_address'],
|
||||
'form_factor': form.cleaned_data['form_factor'],
|
||||
'mgmt_only': form.cleaned_data['mgmt_only'],
|
||||
'description': form.cleaned_data['description'],
|
||||
})
|
||||
if iface_form.is_valid():
|
||||
interfaces.append(iface_form.save(commit=False))
|
||||
else:
|
||||
form.add_error(None, "Duplicate interface {} found for device {}".format(name, device))
|
||||
|
||||
if not form.errors:
|
||||
Interface.objects.bulk_create(interfaces)
|
||||
messages.success(self.request, u"Added {} interfaces to {} devices.".format(len(interfaces),
|
||||
len(selected_devices)))
|
||||
|
||||
|
||||
class InterfaceBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
permission_required = 'dcim.change_interface'
|
||||
cls = Interface
|
||||
|
60
netbox/templates/dcim/device_bulk_add_component.html
Normal file
60
netbox/templates/dcim/device_bulk_add_component.html
Normal file
@ -0,0 +1,60 @@
|
||||
{% extends '_base.html' %}
|
||||
{% load form_helpers %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Add {{ component_name|title }}</h1>
|
||||
<form action="." method="post" class="form form-horizontal">
|
||||
{% csrf_token %}
|
||||
{% if request.POST.redirect_url %}
|
||||
<input type="hidden" name="redirect_url" value="{{ request.POST.redirect_url }}" />
|
||||
{% endif %}
|
||||
{% for field in form.hidden_fields %}
|
||||
{{ field }}
|
||||
{% endfor %}
|
||||
<div class="row">
|
||||
<div class="col-md-7">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>Selected Devices</strong></div>
|
||||
<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 }}</td>
|
||||
<td>{{ device.device_role }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
{% if form.non_field_errors %}
|
||||
<div class="panel panel-danger">
|
||||
<div class="panel-heading"><strong>Errors</strong></div>
|
||||
<div class="panel-body">
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>{{ component_name|title }} to Add</strong></div>
|
||||
<div class="panel-body">
|
||||
{% for field in form.visible_fields %}
|
||||
{% render_field field %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group text-right">
|
||||
<div class="col-md-12">
|
||||
<button type="submit" name="_create" class="btn btn-primary">Create</button>
|
||||
<a href="{{ cancel_url }}" class="btn btn-default">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
@ -2,7 +2,7 @@
|
||||
|
||||
{% block extra_actions %}
|
||||
{% if perms.dcim.add_interface %}
|
||||
<button type="submit" name="_edit" formaction="{% url 'dcim:interface_add_multi' %}" class="btn btn-primary btn-sm">
|
||||
<button type="submit" name="_edit" formaction="{% url 'dcim:device_bulk_add_interface' %}" class="btn btn-primary btn-sm">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add Interfaces
|
||||
</button>
|
||||
{% endif %}
|
||||
|
@ -1,23 +0,0 @@
|
||||
{% extends 'utilities/bulk_edit_form.html' %}
|
||||
{% load form_helpers %}
|
||||
|
||||
{% block title %}Add Interfaces{% endblock %}
|
||||
|
||||
{% block selected_objects_title %}Selected Devices{% endblock %}
|
||||
|
||||
{% block form_title %}Interface(s) to Add{% endblock %}
|
||||
|
||||
{% block selected_objects_table %}
|
||||
<tr>
|
||||
<th>Device</th>
|
||||
<th>Type</th>
|
||||
<th>Role</th>
|
||||
</tr>
|
||||
{% for device in selected_objects %}
|
||||
<tr>
|
||||
<td><a href="{% url 'dcim:device' pk=device.pk %}">{{ device }}</a></td>
|
||||
<td>{{ device.device_type }}</td>
|
||||
<td>{{ device.device_role }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
Loading…
Reference in New Issue
Block a user