Parcourir la source

Closes #743: Enabled bulk creation of all device components

Jeremy Stretch il y a 8 ans
Parent
commit
c94d111401

+ 16 - 12
netbox/dcim/forms.py

@@ -588,18 +588,6 @@ class DeviceBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
         nullable_fields = ['tenant', 'platform']
         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):
 class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
     model = Device
     model = Device
     site = FilterChoiceField(queryset=Site.objects.annotate(filter_count=Count('racks__devices')), to_field_name='slug')
     site = FilterChoiceField(queryset=Site.objects.annotate(filter_count=Count('racks__devices')), to_field_name='slug')
@@ -617,6 +605,22 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
 
 
 
 
 #
 #
+# Bulk device component creation
+#
+
+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 = ['pk', 'name_pattern', 'form_factor', 'mgmt_only', 'description']
+
+
+#
 # Console ports
 # Console ports
 #
 #
 
 

+ 15 - 10
netbox/dcim/urls.py

@@ -108,6 +108,7 @@ urlpatterns = [
     url(r'^devices/(?P<device>\d+)/services/assign/$', ServiceEditView.as_view(), name='service_assign'),
     url(r'^devices/(?P<device>\d+)/services/assign/$', ServiceEditView.as_view(), name='service_assign'),
 
 
     # Console ports
     # Console ports
+    url(r'^devices/console-ports/add/$', views.DeviceBulkAddConsolePortView.as_view(), name='device_bulk_add_consoleport'),
     url(r'^devices/(?P<pk>\d+)/console-ports/add/$', views.consoleport_add, name='consoleport_add'),
     url(r'^devices/(?P<pk>\d+)/console-ports/add/$', views.consoleport_add, name='consoleport_add'),
     url(r'^devices/(?P<pk>\d+)/console-ports/delete/$', views.ConsolePortBulkDeleteView.as_view(), name='consoleport_bulk_delete'),
     url(r'^devices/(?P<pk>\d+)/console-ports/delete/$', views.ConsolePortBulkDeleteView.as_view(), name='consoleport_bulk_delete'),
     url(r'^console-ports/(?P<pk>\d+)/connect/$', views.consoleport_connect, name='consoleport_connect'),
     url(r'^console-ports/(?P<pk>\d+)/connect/$', views.consoleport_connect, name='consoleport_connect'),
@@ -116,6 +117,7 @@ urlpatterns = [
     url(r'^console-ports/(?P<pk>\d+)/delete/$', views.ConsolePortDeleteView.as_view(), name='consoleport_delete'),
     url(r'^console-ports/(?P<pk>\d+)/delete/$', views.ConsolePortDeleteView.as_view(), name='consoleport_delete'),
 
 
     # Console server ports
     # Console server ports
+    url(r'^devices/console-server-ports/add/$', views.DeviceBulkAddConsoleServerPortView.as_view(), name='device_bulk_add_consoleserverport'),
     url(r'^devices/(?P<pk>\d+)/console-server-ports/add/$', views.consoleserverport_add, name='consoleserverport_add'),
     url(r'^devices/(?P<pk>\d+)/console-server-ports/add/$', views.consoleserverport_add, name='consoleserverport_add'),
     url(r'^devices/(?P<pk>\d+)/console-server-ports/delete/$', views.ConsoleServerPortBulkDeleteView.as_view(), name='consoleserverport_bulk_delete'),
     url(r'^devices/(?P<pk>\d+)/console-server-ports/delete/$', views.ConsoleServerPortBulkDeleteView.as_view(), name='consoleserverport_bulk_delete'),
     url(r'^console-server-ports/(?P<pk>\d+)/connect/$', views.consoleserverport_connect, name='consoleserverport_connect'),
     url(r'^console-server-ports/(?P<pk>\d+)/connect/$', views.consoleserverport_connect, name='consoleserverport_connect'),
@@ -124,6 +126,7 @@ urlpatterns = [
     url(r'^console-server-ports/(?P<pk>\d+)/delete/$', views.ConsoleServerPortDeleteView.as_view(), name='consoleserverport_delete'),
     url(r'^console-server-ports/(?P<pk>\d+)/delete/$', views.ConsoleServerPortDeleteView.as_view(), name='consoleserverport_delete'),
 
 
     # Power ports
     # Power ports
+    url(r'^devices/power-ports/add/$', views.DeviceBulkAddPowerPortView.as_view(), name='device_bulk_add_powerport'),
     url(r'^devices/(?P<pk>\d+)/power-ports/add/$', views.powerport_add, name='powerport_add'),
     url(r'^devices/(?P<pk>\d+)/power-ports/add/$', views.powerport_add, name='powerport_add'),
     url(r'^devices/(?P<pk>\d+)/power-ports/delete/$', views.PowerPortBulkDeleteView.as_view(), name='powerport_bulk_delete'),
     url(r'^devices/(?P<pk>\d+)/power-ports/delete/$', views.PowerPortBulkDeleteView.as_view(), name='powerport_bulk_delete'),
     url(r'^power-ports/(?P<pk>\d+)/connect/$', views.powerport_connect, name='powerport_connect'),
     url(r'^power-ports/(?P<pk>\d+)/connect/$', views.powerport_connect, name='powerport_connect'),
@@ -132,6 +135,7 @@ urlpatterns = [
     url(r'^power-ports/(?P<pk>\d+)/delete/$', views.PowerPortDeleteView.as_view(), name='powerport_delete'),
     url(r'^power-ports/(?P<pk>\d+)/delete/$', views.PowerPortDeleteView.as_view(), name='powerport_delete'),
 
 
     # Power outlets
     # Power outlets
+    url(r'^devices/power-outlets/add/$', views.DeviceBulkAddPowerOutletView.as_view(), name='device_bulk_add_poweroutlet'),
     url(r'^devices/(?P<pk>\d+)/power-outlets/add/$', views.poweroutlet_add, name='poweroutlet_add'),
     url(r'^devices/(?P<pk>\d+)/power-outlets/add/$', views.poweroutlet_add, name='poweroutlet_add'),
     url(r'^devices/(?P<pk>\d+)/power-outlets/delete/$', views.PowerOutletBulkDeleteView.as_view(), name='poweroutlet_bulk_delete'),
     url(r'^devices/(?P<pk>\d+)/power-outlets/delete/$', views.PowerOutletBulkDeleteView.as_view(), name='poweroutlet_bulk_delete'),
     url(r'^power-outlets/(?P<pk>\d+)/connect/$', views.poweroutlet_connect, name='poweroutlet_connect'),
     url(r'^power-outlets/(?P<pk>\d+)/connect/$', views.poweroutlet_connect, name='poweroutlet_connect'),
@@ -139,7 +143,18 @@ urlpatterns = [
     url(r'^power-outlets/(?P<pk>\d+)/edit/$', views.PowerOutletEditView.as_view(), name='poweroutlet_edit'),
     url(r'^power-outlets/(?P<pk>\d+)/edit/$', views.PowerOutletEditView.as_view(), name='poweroutlet_edit'),
     url(r'^power-outlets/(?P<pk>\d+)/delete/$', views.PowerOutletDeleteView.as_view(), name='poweroutlet_delete'),
     url(r'^power-outlets/(?P<pk>\d+)/delete/$', views.PowerOutletDeleteView.as_view(), name='poweroutlet_delete'),
 
 
+    # Interfaces
+    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'),
+    url(r'^devices/(?P<pk>\d+)/interface-connections/add/$', views.interfaceconnection_add, name='interfaceconnection_add'),
+    url(r'^interface-connections/(?P<pk>\d+)/delete/$', views.interfaceconnection_delete, name='interfaceconnection_delete'),
+    url(r'^interfaces/(?P<pk>\d+)/edit/$', views.InterfaceEditView.as_view(), name='interface_edit'),
+    url(r'^interfaces/(?P<pk>\d+)/delete/$', views.InterfaceDeleteView.as_view(), name='interface_delete'),
+
     # Device bays
     # Device bays
+    url(r'^devices/device-bays/add/$', views.DeviceBulkAddDeviceBayView.as_view(), name='device_bulk_add_devicebay'),
     url(r'^devices/(?P<pk>\d+)/bays/add/$', views.devicebay_add, name='devicebay_add'),
     url(r'^devices/(?P<pk>\d+)/bays/add/$', views.devicebay_add, name='devicebay_add'),
     url(r'^devices/(?P<pk>\d+)/bays/delete/$', views.DeviceBayBulkDeleteView.as_view(), name='devicebay_bulk_delete'),
     url(r'^devices/(?P<pk>\d+)/bays/delete/$', views.DeviceBayBulkDeleteView.as_view(), name='devicebay_bulk_delete'),
     url(r'^device-bays/(?P<pk>\d+)/edit/$', views.DeviceBayEditView.as_view(), name='devicebay_edit'),
     url(r'^device-bays/(?P<pk>\d+)/edit/$', views.DeviceBayEditView.as_view(), name='devicebay_edit'),
@@ -155,16 +170,6 @@ urlpatterns = [
     url(r'^interface-connections/$', views.InterfaceConnectionsListView.as_view(), name='interface_connections_list'),
     url(r'^interface-connections/$', views.InterfaceConnectionsListView.as_view(), name='interface_connections_list'),
     url(r'^interface-connections/import/$', views.InterfaceConnectionsBulkImportView.as_view(), name='interface_connections_import'),
     url(r'^interface-connections/import/$', views.InterfaceConnectionsBulkImportView.as_view(), name='interface_connections_import'),
 
 
-    # Interfaces
-    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'),
-    url(r'^devices/(?P<pk>\d+)/interface-connections/add/$', views.interfaceconnection_add, name='interfaceconnection_add'),
-    url(r'^interface-connections/(?P<pk>\d+)/delete/$', views.interfaceconnection_delete, name='interfaceconnection_delete'),
-    url(r'^interfaces/(?P<pk>\d+)/edit/$', views.InterfaceEditView.as_view(), name='interface_edit'),
-    url(r'^interfaces/(?P<pk>\d+)/delete/$', views.InterfaceDeleteView.as_view(), name='interface_delete'),
-
     # Modules
     # Modules
     url(r'^devices/(?P<device>\d+)/modules/add/$', views.ModuleEditView.as_view(), name='module_add'),
     url(r'^devices/(?P<device>\d+)/modules/add/$', views.ModuleEditView.as_view(), name='module_add'),
     url(r'^modules/(?P<pk>\d+)/edit/$', views.ModuleEditView.as_view(), name='module_edit'),
     url(r'^modules/(?P<pk>\d+)/edit/$', views.ModuleEditView.as_view(), name='module_edit'),

+ 41 - 15
netbox/dcim/views.py

@@ -688,13 +688,17 @@ def device_lldp_neighbors(request, pk):
     })
     })
 
 
 
 
+#
+# Bulk device component creation
+#
+
 class DeviceBulkAddComponentView(View):
 class DeviceBulkAddComponentView(View):
     """
     """
     Add one or more components (e.g. interfaces) to a selected set of Devices.
     Add one or more components (e.g. interfaces) to a selected set of Devices.
     """
     """
-    form = None
-    component_cls = None
-    component_form = None
+    form = forms.DeviceBulkAddComponentForm
+    model = None
+    model_form = None
 
 
     def get(self):
     def get(self):
         return redirect('dcim:device_list')
         return redirect('dcim:device_list')
@@ -722,18 +726,18 @@ class DeviceBulkAddComponentView(View):
                             'name': name,
                             'name': name,
                         }
                         }
                         component_data.update(data)
                         component_data.update(data)
-                        component_form = self.component_form(component_data)
+                        component_form = self.model_form(component_data)
                         if component_form.is_valid():
                         if component_form.is_valid():
                             new_components.append(component_form.save(commit=False))
                             new_components.append(component_form.save(commit=False))
                         else:
                         else:
-                            form.add_error('name_pattern', "Duplicate {} name for {}: {}".format(
-                                self.component_cls._meta.verbose_name, device, name
-                            ))
+                            for field, errors in component_form.errors.as_data().items():
+                                for e in errors:
+                                    form.add_error(field, u'{} {}: {}'.format(device, name, ', '.join(e)))
 
 
                 if not form.errors:
                 if not form.errors:
-                    self.component_cls.objects.bulk_create(new_components)
+                    self.model.objects.bulk_create(new_components)
                     messages.success(request, u"Added {} {} to {} devices.".format(
                     messages.success(request, u"Added {} {} to {} devices.".format(
-                        len(new_components), self.component_cls._meta.verbose_name_plural, len(form.cleaned_data['pk'])
+                        len(new_components), self.model._meta.verbose_name_plural, len(form.cleaned_data['pk'])
                     ))
                     ))
                     return redirect('dcim:device_list')
                     return redirect('dcim:device_list')
 
 
@@ -747,19 +751,41 @@ class DeviceBulkAddComponentView(View):
 
 
         return render(request, 'dcim/device_bulk_add_component.html', {
         return render(request, 'dcim/device_bulk_add_component.html', {
             'form': form,
             'form': form,
-            'component_name': self.component_cls._meta.verbose_name_plural,
+            'component_name': self.model._meta.verbose_name_plural,
             'selected_devices': selected_devices,
             'selected_devices': selected_devices,
             'cancel_url': reverse('dcim:device_list'),
             'cancel_url': reverse('dcim:device_list'),
         })
         })
 
 
 
 
+class DeviceBulkAddConsolePortView(DeviceBulkAddComponentView):
+    model = ConsolePort
+    model_form = forms.ConsolePortForm
+
+
+class DeviceBulkAddConsoleServerPortView(DeviceBulkAddComponentView):
+    model = ConsoleServerPort
+    model_form = forms.ConsoleServerPortForm
+
+
+class DeviceBulkAddPowerPortView(DeviceBulkAddComponentView):
+    model = PowerPort
+    model_form = forms.PowerPortForm
+
+
+class DeviceBulkAddPowerOutletView(DeviceBulkAddComponentView):
+    model = PowerOutlet
+    model_form = forms.PowerOutletForm
+
+
 class DeviceBulkAddInterfaceView(DeviceBulkAddComponentView):
 class DeviceBulkAddInterfaceView(DeviceBulkAddComponentView):
-    """
-    Add one or more components (e.g. interfaces) to a selected set of Devices.
-    """
     form = forms.DeviceBulkAddInterfaceForm
     form = forms.DeviceBulkAddInterfaceForm
-    component_cls = Interface
-    component_form = forms.InterfaceForm
+    model = Interface
+    model_form = forms.InterfaceForm
+
+
+class DeviceBulkAddDeviceBayView(DeviceBulkAddComponentView):
+    model = DeviceBay
+    model_form = forms.DeviceBayForm
 
 
 
 
 #
 #

+ 8 - 0
netbox/project-static/js/forms.js

@@ -51,6 +51,14 @@ $(document).ready(function() {
         $('#id_' + this.value).toggle('disabled');
         $('#id_' + this.value).toggle('disabled');
     });
     });
 
 
+    // Set formaction and submit using a link
+    $('a.formaction').click(function (event) {
+        event.preventDefault();
+        var form = $(this).closest('form');
+        form.attr('action', $(this).attr('href'));
+        form.submit();
+    });
+
     // API select widget
     // API select widget
     $('select[filter-for]').change(function () {
     $('select[filter-for]').change(function () {
 
 

+ 13 - 3
netbox/templates/dcim/inc/device_table.html

@@ -2,8 +2,18 @@
 
 
 {% block extra_actions %}
 {% block extra_actions %}
     {% if perms.dcim.add_interface %}
     {% if perms.dcim.add_interface %}
-        <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>
+        <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">
+                <li><a href="{% url 'dcim:device_bulk_add_consoleport' %}" class="formaction">Console Ports</a></li>
+                <li><a href="{% url 'dcim:device_bulk_add_consoleserverport' %}" class="formaction">Console Server Ports</a></li>
+                <li><a href="{% url 'dcim:device_bulk_add_powerport' %}" class="formaction">Power Ports</a></li>
+                <li><a href="{% url 'dcim:device_bulk_add_poweroutlet' %}" class="formaction">Power Outlets</a></li>
+                <li><a href="{% url 'dcim:device_bulk_add_interface' %}" class="formaction">Interfaces</a></li>
+                <li><a href="{% url 'dcim:device_bulk_add_devicebay' %}" class="formaction">Device Bays</a></li>
+            </ul>
+        </div>
     {% endif %}
     {% endif %}
 {% endblock %}
 {% endblock %}