Răsfoiți Sursa

Closes #4792: Add bulk rename capability for console and power ports

Jeremy Stretch 5 ani în urmă
părinte
comite
1dbae5b64c

+ 1 - 0
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
 * [#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
 * [#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
 * [#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
 ### Configuration Changes
 
 

+ 1 - 43
netbox/dcim/forms.py

@@ -23,7 +23,7 @@ from tenancy.forms import TenancyFilterForm, TenancyForm
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup
 from utilities.forms import (
 from utilities.forms import (
     APISelect, APISelectMultiple, add_blank_choice, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
     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,
     DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model, JSONField,
     NumericArrayField, SelectWithPK, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField,
     NumericArrayField, SelectWithPK, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField,
     BOOLEAN_WITH_BLANK_CHOICES,
     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):
 class ConsoleServerPortBulkDisconnectForm(ConfirmationForm):
     pk = forms.ModelMultipleChoiceField(
     pk = forms.ModelMultipleChoiceField(
         queryset=ConsoleServerPort.objects.all(),
         queryset=ConsoleServerPort.objects.all(),
@@ -2610,13 +2603,6 @@ class PowerOutletBulkEditForm(
             self.fields['power_port'].widget.attrs['disabled'] = True
             self.fields['power_port'].widget.attrs['disabled'] = True
 
 
 
 
-class PowerOutletBulkRenameForm(BulkRenameForm):
-    pk = forms.ModelMultipleChoiceField(
-        queryset=PowerOutlet.objects.all(),
-        widget=forms.MultipleHiddenInput
-    )
-
-
 class PowerOutletBulkDisconnectForm(ConfirmationForm):
 class PowerOutletBulkDisconnectForm(ConfirmationForm):
     pk = forms.ModelMultipleChoiceField(
     pk = forms.ModelMultipleChoiceField(
         queryset=PowerOutlet.objects.all(),
         queryset=PowerOutlet.objects.all(),
@@ -2922,13 +2908,6 @@ class InterfaceBulkEditForm(
             self.cleaned_data['tagged_vlans'] = []
             self.cleaned_data['tagged_vlans'] = []
 
 
 
 
-class InterfaceBulkRenameForm(BulkRenameForm):
-    pk = forms.ModelMultipleChoiceField(
-        queryset=Interface.objects.all(),
-        widget=forms.MultipleHiddenInput()
-    )
-
-
 class InterfaceBulkDisconnectForm(ConfirmationForm):
 class InterfaceBulkDisconnectForm(ConfirmationForm):
     pk = forms.ModelMultipleChoiceField(
     pk = forms.ModelMultipleChoiceField(
         queryset=Interface.objects.all(),
         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):
 class FrontPortBulkDisconnectForm(ConfirmationForm):
     pk = forms.ModelMultipleChoiceField(
     pk = forms.ModelMultipleChoiceField(
         queryset=FrontPort.objects.all(),
         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):
 class RearPortBulkDisconnectForm(ConfirmationForm):
     pk = forms.ModelMultipleChoiceField(
     pk = forms.ModelMultipleChoiceField(
         queryset=RearPort.objects.all(),
         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):
 class DeviceBayCSVForm(CSVModelForm):
     device = CSVModelChoiceField(
     device = CSVModelChoiceField(
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),

+ 4 - 2
netbox/dcim/urls.py

@@ -187,7 +187,8 @@ urlpatterns = [
     path('console-ports/add/', views.ConsolePortCreateView.as_view(), name='consoleport_add'),
     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/import/', views.ConsolePortBulkImportView.as_view(), name='consoleport_import'),
     path('console-ports/edit/', views.ConsolePortBulkEditView.as_view(), name='consoleport_bulk_edit'),
     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/delete/', views.ConsolePortBulkDeleteView.as_view(), name='consoleport_bulk_delete'),
     path('console-ports/<int:pk>/', views.ConsolePortView.as_view(), name='consoleport'),
     path('console-ports/<int:pk>/', views.ConsolePortView.as_view(), name='consoleport'),
     path('console-ports/<int:pk>/edit/', views.ConsolePortEditView.as_view(), name='consoleport_edit'),
     path('console-ports/<int:pk>/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/add/', views.PowerPortCreateView.as_view(), name='powerport_add'),
     path('power-ports/import/', views.PowerPortBulkImportView.as_view(), name='powerport_import'),
     path('power-ports/import/', views.PowerPortBulkImportView.as_view(), name='powerport_import'),
     path('power-ports/edit/', views.PowerPortBulkEditView.as_view(), name='powerport_bulk_edit'),
     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/delete/', views.PowerPortBulkDeleteView.as_view(), name='powerport_bulk_delete'),
     path('power-ports/<int:pk>/', views.PowerPortView.as_view(), name='powerport'),
     path('power-ports/<int:pk>/', views.PowerPortView.as_view(), name='powerport'),
     path('power-ports/<int:pk>/edit/', views.PowerPortEditView.as_view(), name='powerport_edit'),
     path('power-ports/<int:pk>/edit/', views.PowerPortEditView.as_view(), name='powerport_edit'),

+ 8 - 6
netbox/dcim/views.py

@@ -1199,6 +1199,10 @@ class ConsolePortBulkEditView(BulkEditView):
     form = forms.ConsolePortBulkEditForm
     form = forms.ConsolePortBulkEditForm
 
 
 
 
+class ConsolePortBulkRenameView(BulkRenameView):
+    queryset = ConsolePort.objects.all()
+
+
 class ConsolePortBulkDeleteView(BulkDeleteView):
 class ConsolePortBulkDeleteView(BulkDeleteView):
     queryset = ConsolePort.objects.all()
     queryset = ConsolePort.objects.all()
     filterset = filters.ConsolePortFilterSet
     filterset = filters.ConsolePortFilterSet
@@ -1254,7 +1258,6 @@ class ConsoleServerPortBulkEditView(BulkEditView):
 
 
 class ConsoleServerPortBulkRenameView(BulkRenameView):
 class ConsoleServerPortBulkRenameView(BulkRenameView):
     queryset = ConsoleServerPort.objects.all()
     queryset = ConsoleServerPort.objects.all()
-    form = forms.ConsoleServerPortBulkRenameForm
 
 
 
 
 class ConsoleServerPortBulkDisconnectView(BulkDisconnectView):
 class ConsoleServerPortBulkDisconnectView(BulkDisconnectView):
@@ -1315,6 +1318,10 @@ class PowerPortBulkEditView(BulkEditView):
     form = forms.PowerPortBulkEditForm
     form = forms.PowerPortBulkEditForm
 
 
 
 
+class PowerPortBulkRenameView(BulkRenameView):
+    queryset = PowerPort.objects.all()
+
+
 class PowerPortBulkDeleteView(BulkDeleteView):
 class PowerPortBulkDeleteView(BulkDeleteView):
     queryset = PowerPort.objects.all()
     queryset = PowerPort.objects.all()
     filterset = filters.PowerPortFilterSet
     filterset = filters.PowerPortFilterSet
@@ -1370,7 +1377,6 @@ class PowerOutletBulkEditView(BulkEditView):
 
 
 class PowerOutletBulkRenameView(BulkRenameView):
 class PowerOutletBulkRenameView(BulkRenameView):
     queryset = PowerOutlet.objects.all()
     queryset = PowerOutlet.objects.all()
-    form = forms.PowerOutletBulkRenameForm
 
 
 
 
 class PowerOutletBulkDisconnectView(BulkDisconnectView):
 class PowerOutletBulkDisconnectView(BulkDisconnectView):
@@ -1466,7 +1472,6 @@ class InterfaceBulkEditView(BulkEditView):
 
 
 class InterfaceBulkRenameView(BulkRenameView):
 class InterfaceBulkRenameView(BulkRenameView):
     queryset = Interface.objects.all()
     queryset = Interface.objects.all()
-    form = forms.InterfaceBulkRenameForm
 
 
 
 
 class InterfaceBulkDisconnectView(BulkDisconnectView):
 class InterfaceBulkDisconnectView(BulkDisconnectView):
@@ -1529,7 +1534,6 @@ class FrontPortBulkEditView(BulkEditView):
 
 
 class FrontPortBulkRenameView(BulkRenameView):
 class FrontPortBulkRenameView(BulkRenameView):
     queryset = FrontPort.objects.all()
     queryset = FrontPort.objects.all()
-    form = forms.FrontPortBulkRenameForm
 
 
 
 
 class FrontPortBulkDisconnectView(BulkDisconnectView):
 class FrontPortBulkDisconnectView(BulkDisconnectView):
@@ -1592,7 +1596,6 @@ class RearPortBulkEditView(BulkEditView):
 
 
 class RearPortBulkRenameView(BulkRenameView):
 class RearPortBulkRenameView(BulkRenameView):
     queryset = RearPort.objects.all()
     queryset = RearPort.objects.all()
-    form = forms.RearPortBulkRenameForm
 
 
 
 
 class RearPortBulkDisconnectView(BulkDisconnectView):
 class RearPortBulkDisconnectView(BulkDisconnectView):
@@ -1722,7 +1725,6 @@ class DeviceBayBulkEditView(BulkEditView):
 
 
 class DeviceBayBulkRenameView(BulkRenameView):
 class DeviceBayBulkRenameView(BulkRenameView):
     queryset = DeviceBay.objects.all()
     queryset = DeviceBay.objects.all()
-    form = forms.DeviceBayBulkRenameForm
 
 
 
 
 class DeviceBayBulkDeleteView(BulkDeleteView):
 class DeviceBayBulkDeleteView(BulkDeleteView):

+ 285 - 261
netbox/templates/dcim/device.html

@@ -326,34 +326,79 @@
             {% plugin_left_page device %}
             {% plugin_left_page device %}
         </div>
         </div>
         <div class="col-md-6">
         <div class="col-md-6">
-            {% if console_ports or power_ports %}
-                <div class="panel panel-default">
-                    <div class="panel-heading">
-                        <strong>Console / Power</strong>
+            {% if console_ports %}
+                <form method="post">
+                    {% csrf_token %}
+                    <div class="panel panel-default">
+                        <div class="panel-heading">
+                            <strong>Console Ports</strong>
+                        </div>
+                        <table class="table table-hover panel-body component-list">
+                            {% for cp in console_ports %}
+                                {% include 'dcim/inc/consoleport.html' %}
+                            {% endfor %}
+                        </table>
+                        <div class="panel-footer noprint">
+                            {% if console_ports and perms.dcim.change_consoleport %}
+                                <button type="submit" name="_rename" formaction="{% url 'dcim:consoleport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
+                                    <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
+                                </button>
+                                <button type="submit" name="_edit" formaction="{% url 'dcim:consoleport_bulk_edit' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
+                                    <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
+                                </button>
+                            {% endif %}
+                            {% if console_ports and perms.dcim.delete_consoleport %}
+                                <button type="submit" name="_delete" formaction="{% url 'dcim:consoleport_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
+                                    <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
+                                </button>
+                            {% endif %}
+                            {% if console_ports and perms.dcim.change_consoleport %}
+                                <div class="pull-right">
+                                    <a href="{% url 'dcim:consoleport_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-xs btn-primary">
+                                        <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add console port
+                                    </a>
+                                </div>
+                            {% endif %}
+                        </div>
                     </div>
                     </div>
-                    <table class="table table-hover panel-body component-list">
-                        {% for cp in console_ports %}
-                            {% include 'dcim/inc/consoleport.html' %}
-                        {% endfor %}
-                        {% for pp in power_ports %}
-                            {% include 'dcim/inc/powerport.html' %}
-                        {% endfor %}
-                    </table>
-                    {% if perms.dcim.add_interface or perms.dcim.add_consoleport or perms.dcim.add_powerport %}
-                        <div class="panel-footer text-right noprint">
-                            {% if perms.dcim.add_consoleport %}
-                                <a href="{% url 'dcim:consoleport_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-xs btn-primary">
-                                    <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add console port
-                                </a>
+                </form>
+            {% endif %}
+            {% if power_ports %}
+                <form method="post">
+                    {% csrf_token %}
+                    <div class="panel panel-default">
+                        <div class="panel-heading">
+                            <strong>Power Ports</strong>
+                        </div>
+                        <table class="table table-hover panel-body component-list">
+                            {% for pp in power_ports %}
+                                {% include 'dcim/inc/powerport.html' %}
+                            {% endfor %}
+                        </table>
+                        <div class="panel-footer noprint">
+                            {% if power_ports and perms.dcim.change_powerport %}
+                                <button type="submit" name="_rename" formaction="{% url 'dcim:powerport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
+                                    <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
+                                </button>
+                                <button type="submit" name="_edit" formaction="{% url 'dcim:powerport_bulk_edit' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
+                                    <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
+                                </button>
                             {% endif %}
                             {% endif %}
-                            {% if perms.dcim.add_powerport %}
-                                <a href="{% url 'dcim:powerport_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-xs btn-primary">
-                                    <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add power port
-                                </a>
+                            {% if power_ports and perms.dcim.delete_powerport %}
+                                <button type="submit" name="_delete" formaction="{% url 'dcim:powerport_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
+                                    <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
+                                </button>
+                            {% endif %}
+                            {% if power_ports and perms.dcim.change_powerport %}
+                                <div class="pull-right">
+                                    <a href="{% url 'dcim:powerport_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-xs btn-primary">
+                                        <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add power port
+                                    </a>
+                                </div>
                             {% endif %}
                             {% endif %}
                         </div>
                         </div>
-                    {% endif %}
-                </div>
+                    </div>
+                </form>
             {% endif %}
             {% endif %}
             {% if power_ports and poweroutlets %}
             {% if power_ports and poweroutlets %}
                 <div class="panel panel-default">
                 <div class="panel panel-default">
@@ -501,262 +546,242 @@
     <div class="row">
     <div class="row">
         <div class="col-md-12">
         <div class="col-md-12">
             {% if device_bays or device.device_type.is_parent_device %}
             {% if device_bays or device.device_type.is_parent_device %}
-                {% if perms.dcim.delete_devicebay %}
-                    <form method="post">
+                <form method="post">
                     {% csrf_token %}
                     {% csrf_token %}
-                {% endif %}
-                <div class="panel panel-default">
-                    <div class="panel-heading">
-                        <strong>Device Bays</strong>
-                    </div>
-                    <table class="table table-hover table-headings panel-body component-list">
-                        <thead>
-                            <tr>
-                                {% if perms.dcim.change_devicebay or perms.dcim.delete_devicebay %}
-                                    <th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
-                                {% endif %}
-                                <th>Name</th>
-                                <th>Status</th>
-                                <th>Description</th>
-                                <th colspan="2">Installed Device</th>
-                                <th></th>
-                            </tr>
-                        </thead>
-                        <tbody>
-                            {% for devicebay in device_bays %}
-                                {% include 'dcim/inc/devicebay.html' %}
-                            {% empty %}
+                    <div class="panel panel-default">
+                        <div class="panel-heading">
+                            <strong>Device Bays</strong>
+                        </div>
+                        <table class="table table-hover table-headings panel-body component-list">
+                            <thead>
                                 <tr>
                                 <tr>
-                                    <td colspan="5" class="text-center text-muted">&mdash; No device bays defined &mdash;</td>
+                                    {% if perms.dcim.change_devicebay or perms.dcim.delete_devicebay %}
+                                        <th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
+                                    {% endif %}
+                                    <th>Name</th>
+                                    <th>Status</th>
+                                    <th>Description</th>
+                                    <th colspan="2">Installed Device</th>
+                                    <th></th>
                                 </tr>
                                 </tr>
-                            {% endfor %}
-                        </tbody>
-                    </table>
-                    <div class="panel-footer noprint">
-                        {% if device_bays and perms.dcim.change_devicebay %}
-                            <button type="submit" name="_rename" formaction="{% url 'dcim:devicebay_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
-                                <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
-                            </button>
-                        {% endif %}
-                        {% if device_bays and perms.dcim.delete_devicebay %}
-                            <button type="submit" formaction="{% url 'dcim:devicebay_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
-                                <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete selected
-                            </button>
-                        {% endif %}
-                        {% if perms.dcim.add_devicebay %}
-                            <div class="pull-right">
-                                <a href="{% url 'dcim:devicebay_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs">
-                                    <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add device bays
-                                </a>
-                            </div>
-                            <div class="clearfix"></div>
-                        {% endif %}
-                     </div>
-                </div>
-                {% if perms.dcim.delete_devicebay %}
-                    </form>
-                {% endif %}
+                            </thead>
+                            <tbody>
+                                {% for devicebay in device_bays %}
+                                    {% include 'dcim/inc/devicebay.html' %}
+                                {% empty %}
+                                    <tr>
+                                        <td colspan="5" class="text-center text-muted">&mdash; No device bays defined &mdash;</td>
+                                    </tr>
+                                {% endfor %}
+                            </tbody>
+                        </table>
+                        <div class="panel-footer noprint">
+                            {% if device_bays and perms.dcim.change_devicebay %}
+                                <button type="submit" name="_rename" formaction="{% url 'dcim:devicebay_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
+                                    <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
+                                </button>
+                            {% endif %}
+                            {% if device_bays and perms.dcim.delete_devicebay %}
+                                <button type="submit" formaction="{% url 'dcim:devicebay_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
+                                    <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete selected
+                                </button>
+                            {% endif %}
+                            {% if perms.dcim.add_devicebay %}
+                                <div class="pull-right">
+                                    <a href="{% url 'dcim:devicebay_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs">
+                                        <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add device bays
+                                    </a>
+                                </div>
+                                <div class="clearfix"></div>
+                            {% endif %}
+                         </div>
+                    </div>
+                </form>
             {% endif %}
             {% endif %}
             {% if interfaces %}
             {% if interfaces %}
-                {% if perms.dcim.change_interface or perms.dcim.delete_interface %}
-                    <form method="post">
+                <form method="post">
                     {% csrf_token %}
                     {% csrf_token %}
-                    <input type="hidden" name="device" value="{{ device.pk }}" />
-                {% endif %}
-                <div class="panel panel-default">
-                    <div class="panel-heading">
-                        <strong>Interfaces</strong>
-                        <div class="pull-right noprint">
-                            <button class="btn btn-default btn-xs toggle-ips" selected="selected">
-                                <span class="glyphicon glyphicon-check" aria-hidden="true"></span> Show IPs
-                            </button>
-                        </div>
-                        <div class="col-md-2 pull-right noprint">
-                            <input class="form-control interface-filter" type="text" placeholder="Filter" title="RegEx-enabled" style="height: 23px" />
+                    <div class="panel panel-default">
+                        <div class="panel-heading">
+                            <strong>Interfaces</strong>
+                            <div class="pull-right noprint">
+                                <button class="btn btn-default btn-xs toggle-ips" selected="selected">
+                                    <span class="glyphicon glyphicon-check" aria-hidden="true"></span> Show IPs
+                                </button>
+                            </div>
+                            <div class="col-md-2 pull-right noprint">
+                                <input class="form-control interface-filter" type="text" placeholder="Filter" title="Filter text (regular expressions supported)" style="height: 23px" />
+                            </div>
                         </div>
                         </div>
+                        <table id="interfaces_table" class="table table-hover table-headings panel-body component-list">
+                            <thead>
+                                <tr>
+                                    {% if perms.dcim.change_interface or perms.dcim.delete_interface %}
+                                        <th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
+                                    {% endif %}
+                                    <th>Name</th>
+                                    <th>LAG</th>
+                                    <th>Description</th>
+                                    <th>MTU</th>
+                                    <th>Mode</th>
+                                    <th>Cable</th>
+                                    <th colspan="2">Connection</th>
+                                    <th></th>
+                                </tr>
+                            </thead>
+                            <tbody>
+                                {% for iface in interfaces %}
+                                    {% include 'dcim/inc/interface.html' %}
+                                {% endfor %}
+                            </tbody>
+                        </table>
+                        <div class="panel-footer noprint">
+                            {% if interfaces and perms.dcim.change_interface %}
+                                <button type="submit" name="_rename" formaction="{% url 'dcim:interface_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
+                                    <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
+                                </button>
+                                <button type="submit" name="_edit" formaction="{% url 'dcim:interface_bulk_edit' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
+                                    <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
+                                </button>
+                            {% endif %}
+                            {% if interfaces and perms.dcim.change_interface %}
+                                <button type="submit" name="_disconnect" formaction="{% url 'dcim:interface_bulk_disconnect' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
+                                    <span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
+                                </button>
+                            {% endif %}
+                            {% if interfaces and perms.dcim.delete_interface %}
+                                <button type="submit" name="_delete" formaction="{% url 'dcim:interface_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
+                                    <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
+                                </button>
+                            {% endif %}
+                            {% if perms.dcim.add_interface %}
+                                <div class="pull-right">
+                                    <a href="{% url 'dcim:interface_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs">
+                                        <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add interfaces
+                                    </a>
+                                </div>
+                                <div class="clearfix"></div>
+                            {% endif %}
+                         </div>
                     </div>
                     </div>
-                    <table id="interfaces_table" class="table table-hover table-headings panel-body component-list">
-                        <thead>
-                            <tr>
-                                {% if perms.dcim.change_interface or perms.dcim.delete_interface %}
-                                    <th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
-                                {% endif %}
-                                <th>Name</th>
-                                <th>LAG</th>
-                                <th>Description</th>
-                                <th>MTU</th>
-                                <th>Mode</th>
-                                <th>Cable</th>
-                                <th colspan="2">Connection</th>
-                                <th></th>
-                            </tr>
-                        </thead>
-                        <tbody>
-                            {% for iface in interfaces %}
-                                {% include 'dcim/inc/interface.html' %}
-                            {% endfor %}
-                        </tbody>
-                    </table>
-                    <div class="panel-footer noprint">
-                        {% if interfaces and perms.dcim.change_interface %}
-                            <button type="submit" name="_rename" formaction="{% url 'dcim:interface_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
-                                <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
-                            </button>
-                            <button type="submit" name="_edit" formaction="{% url 'dcim:interface_bulk_edit' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
-                                <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
-                            </button>
-                        {% endif %}
-                        {% if interfaces and perms.dcim.change_interface %}
-                            <button type="submit" name="_disconnect" formaction="{% url 'dcim:interface_bulk_disconnect' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
-                                <span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
-                            </button>
-                        {% endif %}
-                        {% if interfaces and perms.dcim.delete_interface %}
-                            <button type="submit" name="_delete" formaction="{% url 'dcim:interface_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
-                                <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
-                            </button>
-                        {% endif %}
-                        {% if perms.dcim.add_interface %}
-                            <div class="pull-right">
-                                <a href="{% url 'dcim:interface_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs">
-                                    <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add interfaces
-                                </a>
-                            </div>
-                            <div class="clearfix"></div>
-                        {% endif %}
-                     </div>
-                </div>
-                {% if perms.dcim.delete_interface %}
-                    </form>
-                {% endif %}
+                </form>
             {% endif %}
             {% endif %}
             {% if consoleserverports %}
             {% if consoleserverports %}
-                {% if perms.dcim.delete_consoleserverport %}
-                    <form method="post">
+                <form method="post">
                     {% csrf_token %}
                     {% csrf_token %}
-                    <input type="hidden" name="device" value="{{ device.pk }}" />
-                {% endif %}
-                <div class="panel panel-default">
-                    <div class="panel-heading">
-                        <strong>Console Server Ports</strong>
-                    </div>
-                    <table class="table table-hover table-headings panel-body component-list">
-                        <thead>
-                            <tr>
-                                {% if perms.dcim.change_consoleserverport or perms.dcim.delete_consoleserverport %}
-                                    <th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
-                                {% endif %}
-                                <th>Name</th>
-                                <th>Type</th>
-                                <th>Description</th>
-                                <th>Cable</th>
-                                <th colspan="2">Connection</th>
-                                <th></th>
-                            </tr>
-                        </thead>
-                        <tbody>
-                            {% for csp in consoleserverports %}
-                                {% include 'dcim/inc/consoleserverport.html' %}
-                            {% endfor %}
-                        </tbody>
-                    </table>
-                    <div class="panel-footer noprint">
-                        {% if consoleserverports and perms.dcim.change_consoleport %}
-                            <button type="submit" name="_rename" formaction="{% url 'dcim:consoleserverport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
-                                <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
-                            </button>
-                            <button type="submit" name="_edit" formaction="{% url 'dcim:consoleserverport_bulk_edit' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
-                                <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
-                            </button>
-                            <button type="submit" name="_disconnect" formaction="{% url 'dcim:consoleserverport_bulk_disconnect' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
-                                <span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
-                            </button>
-                        {% endif %}
-                        {% if consoleserverports and perms.dcim.delete_consoleserverport %}
-                            <button type="submit" formaction="{% url 'dcim:consoleserverport_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
-                                <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
-                            </button>
-                        {% endif %}
-                        {% if perms.dcim.add_consoleserverport %}
-                            <div class="pull-right">
-                                <a href="{% url 'dcim:consoleserverport_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs">
-                                    <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add console server ports
-                                </a>
-                            </div>
-                            <div class="clearfix"></div>
-                        {% endif %}
+                    <div class="panel panel-default">
+                        <div class="panel-heading">
+                            <strong>Console Server Ports</strong>
+                        </div>
+                        <table class="table table-hover table-headings panel-body component-list">
+                            <thead>
+                                <tr>
+                                    {% if perms.dcim.change_consoleserverport or perms.dcim.delete_consoleserverport %}
+                                        <th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
+                                    {% endif %}
+                                    <th>Name</th>
+                                    <th>Type</th>
+                                    <th>Description</th>
+                                    <th>Cable</th>
+                                    <th colspan="2">Connection</th>
+                                    <th></th>
+                                </tr>
+                            </thead>
+                            <tbody>
+                                {% for csp in consoleserverports %}
+                                    {% include 'dcim/inc/consoleserverport.html' %}
+                                {% endfor %}
+                            </tbody>
+                        </table>
+                        <div class="panel-footer noprint">
+                            {% if consoleserverports and perms.dcim.change_consoleport %}
+                                <button type="submit" name="_rename" formaction="{% url 'dcim:consoleserverport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
+                                    <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
+                                </button>
+                                <button type="submit" name="_edit" formaction="{% url 'dcim:consoleserverport_bulk_edit' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
+                                    <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
+                                </button>
+                                <button type="submit" name="_disconnect" formaction="{% url 'dcim:consoleserverport_bulk_disconnect' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
+                                    <span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
+                                </button>
+                            {% endif %}
+                            {% if consoleserverports and perms.dcim.delete_consoleserverport %}
+                                <button type="submit" formaction="{% url 'dcim:consoleserverport_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
+                                    <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
+                                </button>
+                            {% endif %}
+                            {% if perms.dcim.add_consoleserverport %}
+                                <div class="pull-right">
+                                    <a href="{% url 'dcim:consoleserverport_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs">
+                                        <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add console server ports
+                                    </a>
+                                </div>
+                                <div class="clearfix"></div>
+                            {% endif %}
+                        </div>
                     </div>
                     </div>
-                </div>
-                {% if perms.dcim.delete_consoleserverport %}
-                    </form>
-                {% endif %}
+                </form>
             {% endif %}
             {% endif %}
             {% if poweroutlets %}
             {% if poweroutlets %}
-                {% if perms.dcim.delete_poweroutlet %}
-                    <form method="post">
+                <form method="post">
                     {% csrf_token %}
                     {% csrf_token %}
-                    <input type="hidden" name="device" value="{{ device.pk }}" />
-                {% endif %}
-                <div class="panel panel-default">
-                    <div class="panel-heading">
-                        <strong>Power Outlets</strong>
-                    </div>
-                    <table class="table table-hover table-headings panel-body component-list">
-                        <thead>
-                            <tr>
-                                {% if perms.dcim.change_poweroutlet or perms.dcim.delete_poweroutlet %}
-                                    <th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
-                                {% endif %}
-                                <th>Name</th>
-                            <th>Type</th>
-                                <th>Input/Leg</th>
-                                <th>Description</th>
-                                <th>Cable</th>
-                                <th colspan="3">Connection</th>
-                                <th></th>
-                            </tr>
-                        </thead>
-                        <tbody>
-                            {% for po in poweroutlets %}
-                                {% include 'dcim/inc/poweroutlet.html' %}
-                            {% endfor %}
-                        </tbody>
-                    </table>
-                    <div class="panel-footer noprint">
-                        {% if poweroutlets and perms.dcim.change_powerport %}
-                            <button type="submit" name="_rename" formaction="{% url 'dcim:poweroutlet_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
-                                <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
-                            </button>
-                            <button type="submit" name="_edit" formaction="{% url 'dcim:poweroutlet_bulk_edit' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
-                                <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
-                            </button>
-                            <button type="submit" name="_disconnect" formaction="{% url 'dcim:poweroutlet_bulk_disconnect' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
-                                <span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
-                            </button>
-                        {% endif %}
-                        {% if poweroutlets and perms.dcim.delete_poweroutlet %}
-                            <button type="submit" formaction="{% url 'dcim:poweroutlet_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
-                                <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
-                            </button>
-                        {% endif %}
-                        {% if perms.dcim.add_poweroutlet %}
-                            <div class="pull-right">
-                                <a href="{% url 'dcim:poweroutlet_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs">
-                                    <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add power outlets
-                                </a>
-                            </div>
-                            <div class="clearfix"></div>
-                        {% endif %}
+                    <div class="panel panel-default">
+                        <div class="panel-heading">
+                            <strong>Power Outlets</strong>
+                        </div>
+                        <table class="table table-hover table-headings panel-body component-list">
+                            <thead>
+                                <tr>
+                                    {% if perms.dcim.change_poweroutlet or perms.dcim.delete_poweroutlet %}
+                                        <th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
+                                    {% endif %}
+                                    <th>Name</th>
+                                <th>Type</th>
+                                    <th>Input/Leg</th>
+                                    <th>Description</th>
+                                    <th>Cable</th>
+                                    <th colspan="3">Connection</th>
+                                    <th></th>
+                                </tr>
+                            </thead>
+                            <tbody>
+                                {% for po in poweroutlets %}
+                                    {% include 'dcim/inc/poweroutlet.html' %}
+                                {% endfor %}
+                            </tbody>
+                        </table>
+                        <div class="panel-footer noprint">
+                            {% if poweroutlets and perms.dcim.change_powerport %}
+                                <button type="submit" name="_rename" formaction="{% url 'dcim:poweroutlet_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
+                                    <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
+                                </button>
+                                <button type="submit" name="_edit" formaction="{% url 'dcim:poweroutlet_bulk_edit' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
+                                    <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
+                                </button>
+                                <button type="submit" name="_disconnect" formaction="{% url 'dcim:poweroutlet_bulk_disconnect' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
+                                    <span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
+                                </button>
+                            {% endif %}
+                            {% if poweroutlets and perms.dcim.delete_poweroutlet %}
+                                <button type="submit" formaction="{% url 'dcim:poweroutlet_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
+                                    <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
+                                </button>
+                            {% endif %}
+                            {% if perms.dcim.add_poweroutlet %}
+                                <div class="pull-right">
+                                    <a href="{% url 'dcim:poweroutlet_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs">
+                                        <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add power outlets
+                                    </a>
+                                </div>
+                                <div class="clearfix"></div>
+                            {% endif %}
+                        </div>
                     </div>
                     </div>
-                </div>
-                {% if perms.dcim.delete_poweroutlet %}
-                    </form>
-                {% endif %}
+                </form>
             {% endif %}
             {% endif %}
             {% if front_ports %}
             {% if front_ports %}
                 <form method="post">
                 <form method="post">
                     {% csrf_token %}
                     {% csrf_token %}
-                    <input type="hidden" name="device" value="{{ device.pk }}" />
                     <div class="panel panel-default">
                     <div class="panel panel-default">
                         <div class="panel-heading">
                         <div class="panel-heading">
                             <strong>Front Ports</strong>
                             <strong>Front Ports</strong>
@@ -815,7 +840,6 @@
             {% if rear_ports %}
             {% if rear_ports %}
                 <form method="post">
                 <form method="post">
                     {% csrf_token %}
                     {% csrf_token %}
-                    <input type="hidden" name="device" value="{{ device.pk }}" />
                     <div class="panel panel-default">
                     <div class="panel panel-default">
                         <div class="panel-heading">
                         <div class="panel-heading">
                             <strong>Rear Ports</strong>
                             <strong>Rear Ports</strong>

+ 7 - 0
netbox/templates/dcim/inc/consoleport.html

@@ -1,5 +1,12 @@
 <tr class="consoleport{% if cp.cable %} {{ cp.cable.get_status_class }}{% endif %}">
 <tr class="consoleport{% if cp.cable %} {{ cp.cable.get_status_class }}{% endif %}">
 
 
+    {# Checkbox #}
+    {% if perms.dcim.change_consoleport or perms.dcim.delete_consoleport %}
+        <td class="pk">
+            <input name="pk" type="checkbox" value="{{ cp.pk }}" />
+        </td>
+    {% endif %}
+
     {# Name #}
     {# Name #}
     <td>
     <td>
         <i class="fa fa-fw fa-keyboard-o"></i>
         <i class="fa fa-fw fa-keyboard-o"></i>

+ 7 - 0
netbox/templates/dcim/inc/powerport.html

@@ -1,5 +1,12 @@
 <tr class="powerport{% if pp.cable %} {{ pp.cable.get_status_class }}{% endif %}">
 <tr class="powerport{% if pp.cable %} {{ pp.cable.get_status_class }}{% endif %}">
 
 
+    {# Checkbox #}
+    {% if perms.dcim.change_powerport or perms.dcim.delete_powerport %}
+        <td class="pk">
+            <input name="pk" type="checkbox" value="{{ pp.pk }}" />
+        </td>
+    {% endif %}
+
     {# Name #}
     {# Name #}
     <td>
     <td>
         <i class="fa fa-fw fa-bolt"></i>
         <i class="fa fa-fw fa-bolt"></i>

+ 13 - 2
netbox/utilities/views.py

@@ -28,7 +28,7 @@ from django_tables2 import RequestConfig
 from extras.models import CustomField, CustomFieldValue, ExportTemplate
 from extras.models import CustomField, CustomFieldValue, ExportTemplate
 from extras.querysets import CustomFieldQueryset
 from extras.querysets import CustomFieldQueryset
 from utilities.exceptions import AbortTransaction
 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.permissions import get_permission_for_model, resolve_permission
 from utilities.utils import csv_format, prepare_cloned_fields
 from utilities.utils import csv_format, prepare_cloned_fields
 from .error_handlers import handle_protectederror
 from .error_handlers import handle_protectederror
@@ -988,9 +988,20 @@ class BulkRenameView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
     An extendable view for renaming objects in bulk.
     An extendable view for renaming objects in bulk.
     """
     """
     queryset = None
     queryset = None
-    form = None
     template_name = 'utilities/obj_bulk_rename.html'
     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):
     def get_required_permission(self):
         return get_permission_for_model(self.queryset.model, 'change')
         return get_permission_for_model(self.queryset.model, 'change')