Просмотр исходного кода

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

Jeremy Stretch 5 лет назад
Родитель
Сommit
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
 * [#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
 

+ 1 - 43
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(),

+ 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/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/<int:pk>/', views.ConsolePortView.as_view(), name='consoleport'),
     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/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/<int:pk>/', views.PowerPortView.as_view(), name='powerport'),
     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
 
 
+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):

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

@@ -326,34 +326,79 @@
             {% plugin_left_page device %}
         </div>
         <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>
-                    <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 %}
-                            {% 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 %}
                         </div>
-                    {% endif %}
-                </div>
+                    </div>
+                </form>
             {% endif %}
             {% if power_ports and poweroutlets %}
                 <div class="panel panel-default">
@@ -501,262 +546,242 @@
     <div class="row">
         <div class="col-md-12">
             {% if device_bays or device.device_type.is_parent_device %}
-                {% if perms.dcim.delete_devicebay %}
-                    <form method="post">
+                <form method="post">
                     {% 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>
-                                    <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>
-                            {% 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 %}
             {% if interfaces %}
-                {% if perms.dcim.change_interface or perms.dcim.delete_interface %}
-                    <form method="post">
+                <form method="post">
                     {% 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>
+                        <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>
-                    <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 %}
             {% if consoleserverports %}
-                {% if perms.dcim.delete_consoleserverport %}
-                    <form method="post">
+                <form method="post">
                     {% 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>
-                {% if perms.dcim.delete_consoleserverport %}
-                    </form>
-                {% endif %}
+                </form>
             {% endif %}
             {% if poweroutlets %}
-                {% if perms.dcim.delete_poweroutlet %}
-                    <form method="post">
+                <form method="post">
                     {% 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>
-                {% if perms.dcim.delete_poweroutlet %}
-                    </form>
-                {% endif %}
+                </form>
             {% endif %}
             {% if front_ports %}
                 <form method="post">
                     {% csrf_token %}
-                    <input type="hidden" name="device" value="{{ device.pk }}" />
                     <div class="panel panel-default">
                         <div class="panel-heading">
                             <strong>Front Ports</strong>
@@ -815,7 +840,6 @@
             {% if rear_ports %}
                 <form method="post">
                     {% csrf_token %}
-                    <input type="hidden" name="device" value="{{ device.pk }}" />
                     <div class="panel panel-default">
                         <div class="panel-heading">
                             <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 %}">
 
+    {# 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 #}
     <td>
         <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 %}">
 
+    {# 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 #}
     <td>
         <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.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')