Skip to content

Commit

Permalink
Fixes #664: Re-implemented view for bulk creation of interfaces acros…
Browse files Browse the repository at this point in the history
…s multiple devices
  • Loading branch information
jeremystretch committed Nov 2, 2016
1 parent 76c6fbb commit bbac6e2
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 63 deletions.
18 changes: 13 additions & 5 deletions netbox/dcim/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,18 @@ class Meta:
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')
Expand Down Expand Up @@ -1014,10 +1026,6 @@ class Meta:
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)
Expand Down Expand Up @@ -1250,7 +1258,7 @@ def __init__(self, device, *args, **kwargs):


#
# Interfaces
# Modules
#

class ModuleForm(forms.ModelForm, BootstrapMixin):
Expand Down
2 changes: 1 addition & 1 deletion netbox/dcim/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@
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'),
Expand Down
109 changes: 76 additions & 33 deletions netbox/dcim/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from copy import deepcopy
import re
from natsort import natsorted
from operator import attrgetter
Expand All @@ -7,6 +8,7 @@
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
Expand Down Expand Up @@ -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
#
Expand Down Expand Up @@ -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
Expand Down
60 changes: 60 additions & 0 deletions netbox/templates/dcim/device_bulk_add_component.html
Original file line number Diff line number Diff line change
@@ -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 changes: 1 addition & 1 deletion netbox/templates/dcim/inc/device_table.html
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}
Expand Down
23 changes: 0 additions & 23 deletions netbox/templates/dcim/interface_add_multi.html

This file was deleted.

0 comments on commit bbac6e2

Please sign in to comment.