diff --git a/CHANGELOG.md b/CHANGELOG.md index c8a762249..d46f2f3c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ NetBox now supports modeling physical cables for console, power, and interface c * [#2569](https://github.com/digitalocean/netbox/issues/2569) - Added LSH fiber type; removed SC duplex/simplex designations * [#2571](https://github.com/digitalocean/netbox/issues/2571) - Enforce deletion of attached cable when deleting a termination point * [#2572](https://github.com/digitalocean/netbox/issues/2572) - Add button to disconnect cable from circuit termination +* [#2573](https://github.com/digitalocean/netbox/issues/2573) - Fix bulk console/power/interface disconnections ## API Changes diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index 982d8de94..da9be6f72 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -169,13 +169,13 @@ urlpatterns = [ # Console server ports url(r'^devices/console-server-ports/add/$', views.DeviceBulkAddConsoleServerPortView.as_view(), name='device_bulk_add_consoleserverport'), url(r'^devices/(?P\d+)/console-server-ports/add/$', views.ConsoleServerPortCreateView.as_view(), name='consoleserverport_add'), - url(r'^devices/(?P\d+)/console-server-ports/disconnect/$', views.ConsoleServerPortBulkDisconnectView.as_view(), name='consoleserverport_bulk_disconnect'), url(r'^devices/(?P\d+)/console-server-ports/delete/$', views.ConsoleServerPortBulkDeleteView.as_view(), name='consoleserverport_bulk_delete'), url(r'^console-server-ports/(?P\d+)/connect/$', views.CableCreateView.as_view(), name='consoleserverport_connect', kwargs={'termination_a_type': ConsoleServerPort}), url(r'^console-server-ports/(?P\d+)/edit/$', views.ConsoleServerPortEditView.as_view(), name='consoleserverport_edit'), url(r'^console-server-ports/(?P\d+)/delete/$', views.ConsoleServerPortDeleteView.as_view(), name='consoleserverport_delete'), url(r'^console-server-ports/(?P\d+)/trace/$', views.CableTraceView.as_view(), name='consoleserverport_trace', kwargs={'model': ConsoleServerPort}), url(r'^console-server-ports/rename/$', views.ConsoleServerPortBulkRenameView.as_view(), name='consoleserverport_bulk_rename'), + url(r'^console-server-ports/disconnect/$', views.ConsoleServerPortBulkDisconnectView.as_view(), name='consoleserverport_bulk_disconnect'), # Power ports url(r'^devices/power-ports/add/$', views.DeviceBulkAddPowerPortView.as_view(), name='device_bulk_add_powerport'), @@ -189,19 +189,18 @@ urlpatterns = [ # Power outlets url(r'^devices/power-outlets/add/$', views.DeviceBulkAddPowerOutletView.as_view(), name='device_bulk_add_poweroutlet'), url(r'^devices/(?P\d+)/power-outlets/add/$', views.PowerOutletCreateView.as_view(), name='poweroutlet_add'), - url(r'^devices/(?P\d+)/power-outlets/disconnect/$', views.PowerOutletBulkDisconnectView.as_view(), name='poweroutlet_bulk_disconnect'), url(r'^devices/(?P\d+)/power-outlets/delete/$', views.PowerOutletBulkDeleteView.as_view(), name='poweroutlet_bulk_delete'), url(r'^power-outlets/(?P\d+)/connect/$', views.CableCreateView.as_view(), name='poweroutlet_connect', kwargs={'termination_a_type': PowerOutlet}), url(r'^power-outlets/(?P\d+)/edit/$', views.PowerOutletEditView.as_view(), name='poweroutlet_edit'), url(r'^power-outlets/(?P\d+)/delete/$', views.PowerOutletDeleteView.as_view(), name='poweroutlet_delete'), url(r'^power-outlets/(?P\d+)/trace/$', views.CableTraceView.as_view(), name='poweroutlet_trace', kwargs={'model': PowerOutlet}), url(r'^power-outlets/rename/$', views.PowerOutletBulkRenameView.as_view(), name='poweroutlet_bulk_rename'), + url(r'^power-outlets/disconnect/$', views.PowerOutletBulkDisconnectView.as_view(), name='poweroutlet_bulk_disconnect'), # Interfaces url(r'^devices/interfaces/add/$', views.DeviceBulkAddInterfaceView.as_view(), name='device_bulk_add_interface'), url(r'^devices/(?P\d+)/interfaces/add/$', views.InterfaceCreateView.as_view(), name='interface_add'), url(r'^devices/(?P\d+)/interfaces/edit/$', views.InterfaceBulkEditView.as_view(), name='interface_bulk_edit'), - url(r'^devices/(?P\d+)/interfaces/disconnect/$', views.InterfaceBulkDisconnectView.as_view(), name='interface_bulk_disconnect'), url(r'^devices/(?P\d+)/interfaces/delete/$', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'), url(r'^interfaces/(?P\d+)/connect/$', views.CableCreateView.as_view(), name='interface_connect', kwargs={'termination_a_type': Interface}), url(r'^interfaces/(?P\d+)/$', views.InterfaceView.as_view(), name='interface'), @@ -211,6 +210,7 @@ urlpatterns = [ url(r'^interfaces/(?P\d+)/changelog/$', ObjectChangeLogView.as_view(), name='interface_changelog', kwargs={'model': Interface}), url(r'^interfaces/(?P\d+)/trace/$', views.CableTraceView.as_view(), name='interface_trace', kwargs={'model': Interface}), url(r'^interfaces/rename/$', views.InterfaceBulkRenameView.as_view(), name='interface_bulk_rename'), + url(r'^interfaces/disconnect/$', views.InterfaceBulkDisconnectView.as_view(), name='interface_bulk_disconnect'), # Front ports # url(r'^devices/front-ports/add/$', views.DeviceBulkAddFrontPortView.as_view(), name='device_bulk_add_frontport'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index d61cd8bd5..c24740471 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -74,7 +74,7 @@ class BulkRenameView(GetReturnURLMixin, View): }) -class BulkDisconnectView(View): +class BulkDisconnectView(GetReturnURLMixin, View): """ An extendable view for disconnection console/power/interface components in bulk. """ @@ -82,22 +82,30 @@ class BulkDisconnectView(View): form = None template_name = 'dcim/bulk_disconnect.html' - def disconnect_objects(self, objects): - raise NotImplementedError() + def post(self, request): - def post(self, request, pk): - - device = get_object_or_404(Device, pk=pk) selected_objects = [] + return_url = self.get_return_url(request) if '_confirm' in request.POST: form = self.form(request.POST) + if form.is_valid(): - count = self.disconnect_objects(form.cleaned_data['pk']) - messages.success(request, "Disconnected {} {} on {}".format( - count, self.model._meta.verbose_name_plural, device + + with transaction.atomic(): + + count = 0 + for obj in self.model.objects.filter(pk__in=form.cleaned_data['pk']): + if obj.cable is None: + continue + obj.cable.delete() + count += 1 + + messages.success(request, "Disconnected {} {}".format( + count, self.model._meta.verbose_name_plural )) - return redirect(device.get_absolute_url()) + + return redirect(return_url) else: form = self.form(initial={'pk': request.POST.getlist('pk')}) @@ -105,10 +113,9 @@ class BulkDisconnectView(View): return render(request, self.template_name, { 'form': form, - 'device': device, 'obj_type_plural': self.model._meta.verbose_name_plural, 'selected_objects': selected_objects, - 'return_url': device.get_absolute_url(), + 'return_url': return_url, }) @@ -1139,14 +1146,6 @@ class ConsoleServerPortBulkDisconnectView(PermissionRequiredMixin, BulkDisconnec model = ConsoleServerPort form = forms.ConsoleServerPortBulkDisconnectForm - def disconnect_objects(self, consoleserverports): - return ConsolePort.objects.filter( - connected_endpoint__in=consoleserverports - ).update( - connected_endpoint=None, - connection_status=None - ) - class ConsoleServerPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_consoleserverport' @@ -1223,11 +1222,6 @@ class PowerOutletBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView) model = PowerOutlet form = forms.PowerOutletBulkDisconnectForm - def disconnect_objects(self, poweroutlets): - return PowerPort.objects.filter(connected_endpoint__in=poweroutlets).update( - connected_endpoint=None, connection_status=None - ) - class PowerOutletBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_poweroutlet' @@ -1308,11 +1302,6 @@ class InterfaceBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView): model = Interface form = forms.InterfaceBulkDisconnectForm - def disconnect_objects(self, interfaces): - return Interface.objects.filter(_connected_interface__in=interfaces).update( - _connected_interface=None, connection_status=None - ) - class InterfaceBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_interface' diff --git a/netbox/templates/dcim/bulk_disconnect.html b/netbox/templates/dcim/bulk_disconnect.html index 82cc86a7a..e82d880ad 100644 --- a/netbox/templates/dcim/bulk_disconnect.html +++ b/netbox/templates/dcim/bulk_disconnect.html @@ -4,7 +4,7 @@ {% block title %}Disconnect {{ obj_type_plural|bettertitle }}{% endblock %} {% block message %} -

Are you sure you want to disconnect all {{ selected_objects|length }} of these {{ obj_type_plural }} on {{ device }}?

+

Are you sure you want to disconnect these {{ selected_objects|length }} {{ obj_type_plural }}?

    {% for obj in selected_objects %}
  • {{ obj }}
  • diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index fb633c612..5a6bcecb4 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -530,7 +530,7 @@ {% endif %} {% if interfaces and perms.dcim.change_interface %} - {% endif %} @@ -585,7 +585,7 @@ - {% endif %} @@ -640,7 +640,7 @@ - {% endif %}