diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 4f1d7228d..6e95803f4 100644 --- a/netbox/dcim/forms.py +++ b/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): diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index 180689126..3ec018116 100644 --- a/netbox/dcim/urls.py +++ b/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\d+)/interfaces/add/$', views.interface_add, name='interface_add'), url(r'^devices/(?P\d+)/interfaces/edit/$', views.InterfaceBulkEditView.as_view(), name='interface_bulk_edit'), url(r'^devices/(?P\d+)/interfaces/delete/$', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 2d603d8e9..d31670446 100644 --- a/netbox/dcim/views.py +++ b/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 diff --git a/netbox/templates/dcim/device_bulk_add_component.html b/netbox/templates/dcim/device_bulk_add_component.html new file mode 100644 index 000000000..60d42484c --- /dev/null +++ b/netbox/templates/dcim/device_bulk_add_component.html @@ -0,0 +1,60 @@ +{% extends '_base.html' %} +{% load form_helpers %} + +{% block content %} +

Add {{ component_name|title }}

+
+ {% csrf_token %} + {% if request.POST.redirect_url %} + + {% endif %} + {% for field in form.hidden_fields %} + {{ field }} + {% endfor %} +
+
+
+
Selected Devices
+ + + + + + + {% for device in selected_devices %} + + + + + + {% endfor %} +
DeviceTypeRole
{{ device }}{{ device.device_type }}{{ device.device_role }}
+
+
+
+ {% if form.non_field_errors %} +
+
Errors
+
+ {{ form.non_field_errors }} +
+
+ {% endif %} +
+
{{ component_name|title }} to Add
+
+ {% for field in form.visible_fields %} + {% render_field field %} + {% endfor %} +
+
+
+
+ + Cancel +
+
+
+
+
+{% endblock %} diff --git a/netbox/templates/dcim/inc/device_table.html b/netbox/templates/dcim/inc/device_table.html index 480bbc933..08344706e 100644 --- a/netbox/templates/dcim/inc/device_table.html +++ b/netbox/templates/dcim/inc/device_table.html @@ -2,7 +2,7 @@ {% block extra_actions %} {% if perms.dcim.add_interface %} - {% endif %} diff --git a/netbox/templates/dcim/interface_add_multi.html b/netbox/templates/dcim/interface_add_multi.html deleted file mode 100644 index 3d56dc165..000000000 --- a/netbox/templates/dcim/interface_add_multi.html +++ /dev/null @@ -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 %} - - Device - Type - Role - - {% for device in selected_objects %} - - {{ device }} - {{ device.device_type }} - {{ device.device_role }} - - {% endfor %} -{% endblock %}