From 1dbae5b64c757ca2a4bf92704b1511dd57ba7640 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 25 Jun 2020 14:18:29 -0400 Subject: [PATCH] Closes #4792: Add bulk rename capability for console and power ports --- docs/release-notes/version-2.9.md | 1 + netbox/dcim/forms.py | 44 +- netbox/dcim/urls.py | 6 +- netbox/dcim/views.py | 14 +- netbox/templates/dcim/device.html | 550 +++++++++++---------- netbox/templates/dcim/inc/consoleport.html | 7 + netbox/templates/dcim/inc/powerport.html | 7 + netbox/utilities/views.py | 15 +- 8 files changed, 328 insertions(+), 316 deletions(-) diff --git a/docs/release-notes/version-2.9.md b/docs/release-notes/version-2.9.md index 39d763bb5..799acbcfe 100644 --- a/docs/release-notes/version-2.9.md +++ b/docs/release-notes/version-2.9.md @@ -15,6 +15,7 @@ NetBox v2.9 replaces Django's built-in permissions framework with one that suppo * [#4615](https://github.com/netbox-community/netbox/issues/4615) - Add `label` field for all device components * [#4742](https://github.com/netbox-community/netbox/issues/4742) - Add tagging for cables, power panels, and rack reservations * [#4788](https://github.com/netbox-community/netbox/issues/4788) - Add dedicated views for all device components +* [#4792](https://github.com/netbox-community/netbox/issues/4792) - Add bulk rename capability for console and power ports ### Configuration Changes diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 02b6eaa6e..e4beaa56d 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -23,7 +23,7 @@ from tenancy.forms import TenancyFilterForm, TenancyForm from tenancy.models import Tenant, TenantGroup from utilities.forms import ( APISelect, APISelectMultiple, add_blank_choice, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, - BulkRenameForm, ColorSelect, CommentField, ConfirmationForm, CSVChoiceField, CSVModelChoiceField, CSVModelForm, + ColorSelect, CommentField, ConfirmationForm, CSVChoiceField, CSVModelChoiceField, CSVModelForm, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model, JSONField, NumericArrayField, SelectWithPK, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES, @@ -2375,13 +2375,6 @@ class ConsoleServerPortBulkEditForm( ] -class ConsoleServerPortBulkRenameForm(BulkRenameForm): - pk = forms.ModelMultipleChoiceField( - queryset=ConsoleServerPort.objects.all(), - widget=forms.MultipleHiddenInput() - ) - - class ConsoleServerPortBulkDisconnectForm(ConfirmationForm): pk = forms.ModelMultipleChoiceField( queryset=ConsoleServerPort.objects.all(), @@ -2610,13 +2603,6 @@ class PowerOutletBulkEditForm( self.fields['power_port'].widget.attrs['disabled'] = True -class PowerOutletBulkRenameForm(BulkRenameForm): - pk = forms.ModelMultipleChoiceField( - queryset=PowerOutlet.objects.all(), - widget=forms.MultipleHiddenInput - ) - - class PowerOutletBulkDisconnectForm(ConfirmationForm): pk = forms.ModelMultipleChoiceField( queryset=PowerOutlet.objects.all(), @@ -2922,13 +2908,6 @@ class InterfaceBulkEditForm( self.cleaned_data['tagged_vlans'] = [] -class InterfaceBulkRenameForm(BulkRenameForm): - pk = forms.ModelMultipleChoiceField( - queryset=Interface.objects.all(), - widget=forms.MultipleHiddenInput() - ) - - class InterfaceBulkDisconnectForm(ConfirmationForm): pk = forms.ModelMultipleChoiceField( queryset=Interface.objects.all(), @@ -3115,13 +3094,6 @@ class FrontPortBulkEditForm( ] -class FrontPortBulkRenameForm(BulkRenameForm): - pk = forms.ModelMultipleChoiceField( - queryset=FrontPort.objects.all(), - widget=forms.MultipleHiddenInput - ) - - class FrontPortBulkDisconnectForm(ConfirmationForm): pk = forms.ModelMultipleChoiceField( queryset=FrontPort.objects.all(), @@ -3245,13 +3217,6 @@ class RearPortBulkEditForm( ] -class RearPortBulkRenameForm(BulkRenameForm): - pk = forms.ModelMultipleChoiceField( - queryset=RearPort.objects.all(), - widget=forms.MultipleHiddenInput - ) - - class RearPortBulkDisconnectForm(ConfirmationForm): pk = forms.ModelMultipleChoiceField( queryset=RearPort.objects.all(), @@ -3354,13 +3319,6 @@ class DeviceBayBulkEditForm( ) -class DeviceBayBulkRenameForm(BulkRenameForm): - pk = forms.ModelMultipleChoiceField( - queryset=DeviceBay.objects.all(), - widget=forms.MultipleHiddenInput() - ) - - class DeviceBayCSVForm(CSVModelForm): device = CSVModelChoiceField( queryset=Device.objects.all(), diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index 2014427b7..43fa259bd 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -187,7 +187,8 @@ urlpatterns = [ path('console-ports/add/', views.ConsolePortCreateView.as_view(), name='consoleport_add'), path('console-ports/import/', views.ConsolePortBulkImportView.as_view(), name='consoleport_import'), path('console-ports/edit/', views.ConsolePortBulkEditView.as_view(), name='consoleport_bulk_edit'), - # TODO: Bulk rename, disconnect views for ConsolePorts + path('console-ports/rename/', views.ConsolePortBulkRenameView.as_view(), name='consoleport_bulk_rename'), + # TODO: Bulk disconnect view for ConsolePorts path('console-ports/delete/', views.ConsolePortBulkDeleteView.as_view(), name='consoleport_bulk_delete'), path('console-ports//', views.ConsolePortView.as_view(), name='consoleport'), path('console-ports//edit/', views.ConsolePortEditView.as_view(), name='consoleport_edit'), @@ -218,7 +219,8 @@ urlpatterns = [ path('power-ports/add/', views.PowerPortCreateView.as_view(), name='powerport_add'), path('power-ports/import/', views.PowerPortBulkImportView.as_view(), name='powerport_import'), path('power-ports/edit/', views.PowerPortBulkEditView.as_view(), name='powerport_bulk_edit'), - # TODO: Bulk rename, disconnect views for PowerPorts + path('power-ports/rename/', views.PowerPortBulkRenameView.as_view(), name='powerport_bulk_rename'), + # TODO: Bulk disconnect view for PowerPorts path('power-ports/delete/', views.PowerPortBulkDeleteView.as_view(), name='powerport_bulk_delete'), path('power-ports//', views.PowerPortView.as_view(), name='powerport'), path('power-ports//edit/', views.PowerPortEditView.as_view(), name='powerport_edit'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 1f3b61a42..7ca150e3d 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1199,6 +1199,10 @@ class ConsolePortBulkEditView(BulkEditView): form = forms.ConsolePortBulkEditForm +class ConsolePortBulkRenameView(BulkRenameView): + queryset = ConsolePort.objects.all() + + class ConsolePortBulkDeleteView(BulkDeleteView): queryset = ConsolePort.objects.all() filterset = filters.ConsolePortFilterSet @@ -1254,7 +1258,6 @@ class ConsoleServerPortBulkEditView(BulkEditView): class ConsoleServerPortBulkRenameView(BulkRenameView): queryset = ConsoleServerPort.objects.all() - form = forms.ConsoleServerPortBulkRenameForm class ConsoleServerPortBulkDisconnectView(BulkDisconnectView): @@ -1315,6 +1318,10 @@ class PowerPortBulkEditView(BulkEditView): form = forms.PowerPortBulkEditForm +class PowerPortBulkRenameView(BulkRenameView): + queryset = PowerPort.objects.all() + + class PowerPortBulkDeleteView(BulkDeleteView): queryset = PowerPort.objects.all() filterset = filters.PowerPortFilterSet @@ -1370,7 +1377,6 @@ class PowerOutletBulkEditView(BulkEditView): class PowerOutletBulkRenameView(BulkRenameView): queryset = PowerOutlet.objects.all() - form = forms.PowerOutletBulkRenameForm class PowerOutletBulkDisconnectView(BulkDisconnectView): @@ -1466,7 +1472,6 @@ class InterfaceBulkEditView(BulkEditView): class InterfaceBulkRenameView(BulkRenameView): queryset = Interface.objects.all() - form = forms.InterfaceBulkRenameForm class InterfaceBulkDisconnectView(BulkDisconnectView): @@ -1529,7 +1534,6 @@ class FrontPortBulkEditView(BulkEditView): class FrontPortBulkRenameView(BulkRenameView): queryset = FrontPort.objects.all() - form = forms.FrontPortBulkRenameForm class FrontPortBulkDisconnectView(BulkDisconnectView): @@ -1592,7 +1596,6 @@ class RearPortBulkEditView(BulkEditView): class RearPortBulkRenameView(BulkRenameView): queryset = RearPort.objects.all() - form = forms.RearPortBulkRenameForm class RearPortBulkDisconnectView(BulkDisconnectView): @@ -1722,7 +1725,6 @@ class DeviceBayBulkEditView(BulkEditView): class DeviceBayBulkRenameView(BulkRenameView): queryset = DeviceBay.objects.all() - form = forms.DeviceBayBulkRenameForm class DeviceBayBulkDeleteView(BulkDeleteView): diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index a42250a3d..c7f58aa4d 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -326,34 +326,79 @@ {% plugin_left_page device %}
- {% if console_ports or power_ports %} -
-
- Console / Power -
- - {% for cp in console_ports %} - {% include 'dcim/inc/consoleport.html' %} - {% endfor %} - {% for pp in power_ports %} - {% include 'dcim/inc/powerport.html' %} - {% endfor %} -
- {% if perms.dcim.add_interface or perms.dcim.add_consoleport or perms.dcim.add_powerport %} - + + {% endif %} + {% if power_ports %} +
+ {% csrf_token %} +
+
+ Power Ports +
+ + {% for pp in power_ports %} + {% include 'dcim/inc/powerport.html' %} + {% endfor %} +
+ +
+
{% endif %} {% if power_ports and poweroutlets %}
@@ -501,262 +546,242 @@
{% if device_bays or device.device_type.is_parent_device %} - {% if perms.dcim.delete_devicebay %} -
+ {% csrf_token %} - {% endif %} -
-
- Device Bays -
- - - - {% if perms.dcim.change_devicebay or perms.dcim.delete_devicebay %} - - {% endif %} - - - - - - - - - {% for devicebay in device_bays %} - {% include 'dcim/inc/devicebay.html' %} - {% empty %} +
+
+ Device Bays +
+
NameStatusDescriptionInstalled Device
+ - + {% if perms.dcim.change_devicebay or perms.dcim.delete_devicebay %} + + {% endif %} + + + + + - {% endfor %} - -
— No device bays defined —NameStatusDescriptionInstalled Device
- -
- {% if perms.dcim.delete_devicebay %} -
- {% endif %} + + + {% for devicebay in device_bays %} + {% include 'dcim/inc/devicebay.html' %} + {% empty %} + + — No device bays defined — + + {% endfor %} + + + +
+ {% endif %} {% if interfaces %} - {% if perms.dcim.change_interface or perms.dcim.delete_interface %} -
+ {% csrf_token %} - - {% endif %} -
-
- Interfaces -
- -
-
- -
-
- - - - {% if perms.dcim.change_interface or perms.dcim.delete_interface %} - - {% endif %} - - - - - - - - - - - - {% for iface in interfaces %} - {% include 'dcim/inc/interface.html' %} - {% endfor %} - -
NameLAGDescriptionMTUModeCableConnection
- + {% endif %} {% if consoleserverports %} - {% if perms.dcim.delete_consoleserverport %} -
+ {% csrf_token %} - - {% endif %} -
-
- Console Server Ports +
+
+ Console Server Ports +
+ + + + {% if perms.dcim.change_consoleserverport or perms.dcim.delete_consoleserverport %} + + {% endif %} + + + + + + + + + + {% for csp in consoleserverports %} + {% include 'dcim/inc/consoleserverport.html' %} + {% endfor %} + +
NameTypeDescriptionCableConnection
+
- - - - {% if perms.dcim.change_consoleserverport or perms.dcim.delete_consoleserverport %} - - {% endif %} - - - - - - - - - - {% for csp in consoleserverports %} - {% include 'dcim/inc/consoleserverport.html' %} - {% endfor %} - -
NameTypeDescriptionCableConnection
- -
- {% if perms.dcim.delete_consoleserverport %} - - {% endif %} + {% endif %} {% if poweroutlets %} - {% if perms.dcim.delete_poweroutlet %} -
+ {% csrf_token %} - - {% endif %} -
-
- Power Outlets +
+
+ Power Outlets +
+ + + + {% if perms.dcim.change_poweroutlet or perms.dcim.delete_poweroutlet %} + + {% endif %} + + + + + + + + + + + {% for po in poweroutlets %} + {% include 'dcim/inc/poweroutlet.html' %} + {% endfor %} + +
NameTypeInput/LegDescriptionCableConnection
+
- - - - {% if perms.dcim.change_poweroutlet or perms.dcim.delete_poweroutlet %} - - {% endif %} - - - - - - - - - - - {% for po in poweroutlets %} - {% include 'dcim/inc/poweroutlet.html' %} - {% endfor %} - -
NameTypeInput/LegDescriptionCableConnection
- -
- {% if perms.dcim.delete_poweroutlet %} - - {% endif %} + {% endif %} {% if front_ports %}
{% csrf_token %} -
Front Ports @@ -815,7 +840,6 @@ {% if rear_ports %} {% csrf_token %} -
Rear Ports diff --git a/netbox/templates/dcim/inc/consoleport.html b/netbox/templates/dcim/inc/consoleport.html index 02afd8f99..61b4fe045 100644 --- a/netbox/templates/dcim/inc/consoleport.html +++ b/netbox/templates/dcim/inc/consoleport.html @@ -1,5 +1,12 @@ + {# Checkbox #} + {% if perms.dcim.change_consoleport or perms.dcim.delete_consoleport %} + + + + {% endif %} + {# Name #} diff --git a/netbox/templates/dcim/inc/powerport.html b/netbox/templates/dcim/inc/powerport.html index c3293e959..58eed145a 100644 --- a/netbox/templates/dcim/inc/powerport.html +++ b/netbox/templates/dcim/inc/powerport.html @@ -1,5 +1,12 @@ + {# Checkbox #} + {% if perms.dcim.change_powerport or perms.dcim.delete_powerport %} + + + + {% endif %} + {# Name #} diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index 0fdb2f89e..5785da93f 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -28,7 +28,7 @@ from django_tables2 import RequestConfig from extras.models import CustomField, CustomFieldValue, ExportTemplate from extras.querysets import CustomFieldQueryset from utilities.exceptions import AbortTransaction -from utilities.forms import BootstrapMixin, CSVDataField, TableConfigForm +from utilities.forms import BootstrapMixin, BulkRenameForm, CSVDataField, TableConfigForm from utilities.permissions import get_permission_for_model, resolve_permission from utilities.utils import csv_format, prepare_cloned_fields from .error_handlers import handle_protectederror @@ -988,9 +988,20 @@ class BulkRenameView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View): An extendable view for renaming objects in bulk. """ queryset = None - form = None template_name = 'utilities/obj_bulk_rename.html' + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # Create a new Form class from BulkRenameForm + class _Form(BulkRenameForm): + pk = ModelMultipleChoiceField( + queryset=self.queryset, + widget=MultipleHiddenInput() + ) + + self.form = _Form + def get_required_permission(self): return get_permission_for_model(self.queryset.model, 'change')