Browse Source

Fixes #664: Re-implemented view for bulk creation of interfaces across multiple devices

Jeremy Stretch 8 years ago
parent
commit
bbac6e2ba6

+ 13 - 5
netbox/dcim/forms.py

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

+ 1 - 1
netbox/dcim/urls.py

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

+ 76 - 33
netbox/dcim/views.py

@@ -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 - 0
netbox/templates/dcim/device_bulk_add_component.html

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

+ 1 - 1
netbox/templates/dcim/inc/device_table.html

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

+ 0 - 23
netbox/templates/dcim/interface_add_multi.html

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