Skip to content

Commit

Permalink
Closes #743: Enabled bulk creation of all device components
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremystretch committed Dec 16, 2016
1 parent 6f1532a commit c94d111
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 40 deletions.
28 changes: 16 additions & 12 deletions netbox/dcim/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -588,18 +588,6 @@ 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 All @@ -616,6 +604,22 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
mac_address = forms.CharField(required=False, label='MAC address')


#
# 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
#
Expand Down
25 changes: 15 additions & 10 deletions netbox/dcim/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
url(r'^devices/(?P<device>\d+)/services/assign/$', ServiceEditView.as_view(), name='service_assign'),

# 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/delete/$', views.ConsolePortBulkDeleteView.as_view(), name='consoleport_bulk_delete'),
url(r'^console-ports/(?P<pk>\d+)/connect/$', views.consoleport_connect, name='consoleport_connect'),
Expand All @@ -116,6 +117,7 @@
url(r'^console-ports/(?P<pk>\d+)/delete/$', views.ConsolePortDeleteView.as_view(), name='consoleport_delete'),

# 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/delete/$', views.ConsoleServerPortBulkDeleteView.as_view(), name='consoleserverport_bulk_delete'),
url(r'^console-server-ports/(?P<pk>\d+)/connect/$', views.consoleserverport_connect, name='consoleserverport_connect'),
Expand All @@ -124,6 +126,7 @@
url(r'^console-server-ports/(?P<pk>\d+)/delete/$', views.ConsoleServerPortDeleteView.as_view(), name='consoleserverport_delete'),

# 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/delete/$', views.PowerPortBulkDeleteView.as_view(), name='powerport_bulk_delete'),
url(r'^power-ports/(?P<pk>\d+)/connect/$', views.powerport_connect, name='powerport_connect'),
Expand All @@ -132,14 +135,26 @@
url(r'^power-ports/(?P<pk>\d+)/delete/$', views.PowerPortDeleteView.as_view(), name='powerport_delete'),

# 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/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+)/disconnect/$', views.poweroutlet_disconnect, name='poweroutlet_disconnect'),
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'),

# 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
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/delete/$', views.DeviceBayBulkDeleteView.as_view(), name='devicebay_bulk_delete'),
url(r'^device-bays/(?P<pk>\d+)/edit/$', views.DeviceBayEditView.as_view(), name='devicebay_edit'),
Expand All @@ -155,16 +170,6 @@
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'),

# 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
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'),
Expand Down
56 changes: 41 additions & 15 deletions netbox/dcim/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -688,13 +688,17 @@ def device_lldp_neighbors(request, pk):
})


#
# Bulk device component creation
#

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
form = forms.DeviceBulkAddComponentForm
model = None
model_form = None

def get(self):
return redirect('dcim:device_list')
Expand Down Expand Up @@ -722,18 +726,18 @@ def post(self, request):
'name': name,
}
component_data.update(data)
component_form = self.component_form(component_data)
component_form = self.model_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
))
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:
self.component_cls.objects.bulk_create(new_components)
self.model.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'])
len(new_components), self.model._meta.verbose_name_plural, len(form.cleaned_data['pk'])
))
return redirect('dcim:device_list')

Expand All @@ -747,19 +751,41 @@ def post(self, request):

return render(request, 'dcim/device_bulk_add_component.html', {
'form': form,
'component_name': self.component_cls._meta.verbose_name_plural,
'component_name': self.model._meta.verbose_name_plural,
'selected_devices': selected_devices,
'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):
"""
Add one or more components (e.g. interfaces) to a selected set of Devices.
"""
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


#
Expand Down
8 changes: 8 additions & 0 deletions netbox/project-static/js/forms.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ $(document).ready(function() {
$('#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
$('select[filter-for]').change(function () {

Expand Down
16 changes: 13 additions & 3 deletions netbox/templates/dcim/inc/device_table.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,18 @@

{% block extra_actions %}
{% 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 %}
{% endblock %}

0 comments on commit c94d111

Please sign in to comment.