Merge pull request #4117 from netbox-community/4116-component-bulk-actions

Closes #4116: Enable bulk edit and delete functions for device component list views
This commit is contained in:
Jeremy Stretch 2020-02-06 22:03:25 -05:00 committed by GitHub
commit 1e61fcb485
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 221 additions and 17 deletions

View File

@ -3,6 +3,7 @@
## Enhancements
* [#4113](https://github.com/netbox-community/netbox/issues/4113) - Add bulk edit functionality for device type components
* [#4116](https://github.com/netbox-community/netbox/issues/4116) - Enable bulk edit and delete functions for device component list views
## Bug Fixes

View File

@ -2371,6 +2371,27 @@ class ConsolePortCreateForm(BootstrapMixin, forms.Form):
)
class ConsolePortBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=ConsolePort.objects.all(),
widget=forms.MultipleHiddenInput()
)
type = forms.ChoiceField(
choices=add_blank_choice(ConsolePortTypeChoices),
required=False,
widget=StaticSelect2()
)
description = forms.CharField(
max_length=100,
required=False
)
class Meta:
nullable_fields = (
'description',
)
class ConsolePortCSVForm(forms.ModelForm):
device = FlexibleModelChoiceField(
queryset=Device.objects.all(),
@ -2544,6 +2565,37 @@ class PowerPortCreateForm(BootstrapMixin, forms.Form):
)
class PowerPortBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=PowerPort.objects.all(),
widget=forms.MultipleHiddenInput()
)
type = forms.ChoiceField(
choices=add_blank_choice(PowerPortTypeChoices),
required=False,
widget=StaticSelect2()
)
maximum_draw = forms.IntegerField(
min_value=1,
required=False,
help_text="Maximum draw in watts"
)
allocated_draw = forms.IntegerField(
min_value=1,
required=False,
help_text="Allocated draw in watts"
)
description = forms.CharField(
max_length=100,
required=False
)
class Meta:
nullable_fields = (
'description',
)
class PowerPortCSVForm(forms.ModelForm):
device = FlexibleModelChoiceField(
queryset=Device.objects.all(),
@ -2695,6 +2747,7 @@ class PowerOutletBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
)
device = forms.ModelChoiceField(
queryset=Device.objects.all(),
required=False,
widget=forms.HiddenInput()
)
type = forms.ChoiceField(
@ -2726,6 +2779,9 @@ class PowerOutletBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
if 'device' in self.initial:
device = Device.objects.filter(pk=self.initial['device']).first()
self.fields['power_port'].queryset = PowerPort.objects.filter(device=device)
else:
self.fields['power_port'].choices = ()
self.fields['power_port'].widget.attrs['disabled'] = True
class PowerOutletBulkRenameForm(BulkRenameForm):
@ -2969,6 +3025,7 @@ class InterfaceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
)
device = forms.ModelChoiceField(
queryset=Device.objects.all(),
required=False,
widget=forms.HiddenInput()
)
type = forms.ChoiceField(
@ -3044,6 +3101,9 @@ class InterfaceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
device__in=[device, device.get_vc_master()],
type=InterfaceTypeChoices.TYPE_LAG
)
else:
self.fields['lag'].choices = ()
self.fields['lag'].widget.attrs['disabled'] = True
def clean(self):

View File

@ -1070,7 +1070,6 @@ class ConsolePortTestCase(StandardTestCases.Views):
# Disable inapplicable views
test_get_object = None
test_create_object = None
test_bulk_edit_objects = None
def test_bulk_create_objects(self):
return self._test_bulk_create_objects(expected_count=3)
@ -1101,6 +1100,11 @@ class ConsolePortTestCase(StandardTestCases.Views):
'tags': 'Alpha,Bravo,Charlie',
}
cls.bulk_edit_data = {
'type': ConsolePortTypeChoices.TYPE_RJ45,
'description': 'New description',
}
cls.csv_data = (
"device,name",
"Device 1,Console Port 4",
@ -1164,7 +1168,6 @@ class PowerPortTestCase(StandardTestCases.Views):
# Disable inapplicable views
test_get_object = None
test_bulk_edit_objects = None
test_create_object = None
def test_bulk_create_objects(self):
@ -1200,6 +1203,13 @@ class PowerPortTestCase(StandardTestCases.Views):
'tags': 'Alpha,Bravo,Charlie',
}
cls.bulk_edit_data = {
'type': PowerPortTypeChoices.TYPE_IEC_C14,
'maximum_draw': 100,
'allocated_draw': 50,
'description': 'New description',
}
cls.csv_data = (
"device,name",
"Device 1,Power Port 4",

View File

@ -178,7 +178,8 @@ urlpatterns = [
path('console-ports/', views.ConsolePortListView.as_view(), name='consoleport_list'),
path('console-ports/add/', views.ConsolePortCreateView.as_view(), name='consoleport_add'),
path('console-ports/import/', views.ConsolePortBulkImportView.as_view(), name='consoleport_import'),
# TODO: Bulk edit, rename, disconnect views for ConsolePorts
path('console-ports/edit/', views.ConsolePortBulkEditView.as_view(), name='consoleport_bulk_edit'),
# TODO: Bulk rename, disconnect views for ConsolePorts
path('console-ports/delete/', views.ConsolePortBulkDeleteView.as_view(), name='consoleport_bulk_delete'),
path('console-ports/<int:termination_a_id>/connect/<str:termination_b_type>/', views.CableCreateView.as_view(), name='consoleport_connect', kwargs={'termination_a_type': ConsolePort}),
path('console-ports/<int:pk>/edit/', views.ConsolePortEditView.as_view(), name='consoleport_edit'),
@ -204,7 +205,8 @@ urlpatterns = [
path('power-ports/', views.PowerPortListView.as_view(), name='powerport_list'),
path('power-ports/add/', views.PowerPortCreateView.as_view(), name='powerport_add'),
path('power-ports/import/', views.PowerPortBulkImportView.as_view(), name='powerport_import'),
# TODO: Bulk edit, rename, disconnect views for PowerPorts
path('power-ports/edit/', views.PowerPortBulkEditView.as_view(), name='powerport_bulk_edit'),
# TODO: Bulk rename, disconnect views for PowerPorts
path('power-ports/delete/', views.PowerPortBulkDeleteView.as_view(), name='powerport_bulk_delete'),
path('power-ports/<int:termination_a_id>/connect/<str:termination_b_type>/', views.CableCreateView.as_view(), name='powerport_connect', kwargs={'termination_a_type': PowerPort}),
path('power-ports/<int:pk>/edit/', views.PowerPortEditView.as_view(), name='powerport_edit'),

View File

@ -1224,7 +1224,7 @@ class ConsolePortListView(PermissionRequiredMixin, ObjectListView):
filterset = filters.ConsolePortFilterSet
filterset_form = forms.ConsolePortFilterForm
table = tables.ConsolePortDetailTable
template_name = 'dcim/device_component_list.html'
template_name = 'dcim/consoleport_list.html'
class ConsolePortCreateView(PermissionRequiredMixin, ComponentCreateView):
@ -1253,6 +1253,13 @@ class ConsolePortBulkImportView(PermissionRequiredMixin, BulkImportView):
default_return_url = 'dcim:consoleport_list'
class ConsolePortBulkEditView(PermissionRequiredMixin, BulkEditView):
permission_required = 'dcim.change_consoleport'
queryset = ConsolePort.objects.all()
table = tables.ConsolePortTable
form = forms.ConsolePortBulkEditForm
class ConsolePortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'dcim.delete_consoleport'
queryset = ConsolePort.objects.all()
@ -1270,7 +1277,7 @@ class ConsoleServerPortListView(PermissionRequiredMixin, ObjectListView):
filterset = filters.ConsoleServerPortFilterSet
filterset_form = forms.ConsoleServerPortFilterForm
table = tables.ConsoleServerPortDetailTable
template_name = 'dcim/device_component_list.html'
template_name = 'dcim/consoleserverport_list.html'
class ConsoleServerPortCreateView(PermissionRequiredMixin, ComponentCreateView):
@ -1335,7 +1342,7 @@ class PowerPortListView(PermissionRequiredMixin, ObjectListView):
filterset = filters.PowerPortFilterSet
filterset_form = forms.PowerPortFilterForm
table = tables.PowerPortDetailTable
template_name = 'dcim/device_component_list.html'
template_name = 'dcim/powerport_list.html'
class PowerPortCreateView(PermissionRequiredMixin, ComponentCreateView):
@ -1364,6 +1371,13 @@ class PowerPortBulkImportView(PermissionRequiredMixin, BulkImportView):
default_return_url = 'dcim:powerport_list'
class PowerPortBulkEditView(PermissionRequiredMixin, BulkEditView):
permission_required = 'dcim.change_powerport'
queryset = PowerPort.objects.all()
table = tables.PowerPortTable
form = forms.PowerPortBulkEditForm
class PowerPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'dcim.delete_powerport'
queryset = PowerPort.objects.all()
@ -1381,7 +1395,7 @@ class PowerOutletListView(PermissionRequiredMixin, ObjectListView):
filterset = filters.PowerOutletFilterSet
filterset_form = forms.PowerOutletFilterForm
table = tables.PowerOutletDetailTable
template_name = 'dcim/device_component_list.html'
template_name = 'dcim/poweroutlet_list.html'
class PowerOutletCreateView(PermissionRequiredMixin, ComponentCreateView):
@ -1446,7 +1460,7 @@ class InterfaceListView(PermissionRequiredMixin, ObjectListView):
filterset = filters.InterfaceFilterSet
filterset_form = forms.InterfaceFilterForm
table = tables.InterfaceDetailTable
template_name = 'dcim/device_component_list.html'
template_name = 'dcim/interface_list.html'
class InterfaceView(PermissionRequiredMixin, View):
@ -1548,7 +1562,7 @@ class FrontPortListView(PermissionRequiredMixin, ObjectListView):
filterset = filters.FrontPortFilterSet
filterset_form = forms.FrontPortFilterForm
table = tables.FrontPortDetailTable
template_name = 'dcim/device_component_list.html'
template_name = 'dcim/frontport_list.html'
class FrontPortCreateView(PermissionRequiredMixin, ComponentCreateView):
@ -1613,7 +1627,7 @@ class RearPortListView(PermissionRequiredMixin, ObjectListView):
filterset = filters.RearPortFilterSet
filterset_form = forms.RearPortFilterForm
table = tables.RearPortDetailTable
template_name = 'dcim/device_component_list.html'
template_name = 'dcim/rearport_list.html'
class RearPortCreateView(PermissionRequiredMixin, ComponentCreateView):
@ -1680,7 +1694,7 @@ class DeviceBayListView(PermissionRequiredMixin, ObjectListView):
filterset = filters.DeviceBayFilterSet
filterset_form = forms.DeviceBayFilterForm
table = tables.DeviceBayDetailTable
template_name = 'dcim/device_component_list.html'
template_name = 'dcim/devicebay_list.html'
class DeviceBayCreateView(PermissionRequiredMixin, ComponentCreateView):

View File

@ -1,16 +1,14 @@
{% extends '_base.html' %}
{% load buttons %}
{% load helpers %}
{% block content %}
<div class="pull-right noprint">
{% export_button content_type %}
</div>
<h1>{% block title %}{{ table.Meta.model|model_name|capfirst }}s{% endblock %}</h1>
<h1>{% block title %}Console Ports{% endblock %}</h1>
<div class="row">
<div class="col-md-9">
{% include 'responsive_table.html' %}
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:consoleport_bulk_edit' bulk_delete_url='dcim:consoleport_bulk_delete' %}
</div>
<div class="col-md-3 noprint">
{% include 'inc/search_panel.html' %}

View File

@ -0,0 +1,17 @@
{% extends '_base.html' %}
{% load buttons %}
{% block content %}
<div class="pull-right noprint">
{% export_button content_type %}
</div>
<h1>{% block title %}Console Server Ports{% endblock %}</h1>
<div class="row">
<div class="col-md-9">
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:consoleserverport_bulk_edit' bulk_delete_url='dcim:consoleserverport_bulk_delete' %}
</div>
<div class="col-md-3 noprint">
{% include 'inc/search_panel.html' %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,17 @@
{% extends '_base.html' %}
{% load buttons %}
{% block content %}
<div class="pull-right noprint">
{% export_button content_type %}
</div>
<h1>{% block title %}Device Bays{% endblock %}</h1>
<div class="row">
<div class="col-md-9">
{% include 'utilities/obj_table.html' with bulk_delete_url='dcim:devicebay_bulk_delete' %}
</div>
<div class="col-md-3 noprint">
{% include 'inc/search_panel.html' %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,17 @@
{% extends '_base.html' %}
{% load buttons %}
{% block content %}
<div class="pull-right noprint">
{% export_button content_type %}
</div>
<h1>{% block title %}Front Ports{% endblock %}</h1>
<div class="row">
<div class="col-md-9">
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:frontport_bulk_edit' bulk_delete_url='dcim:frontport_bulk_delete' %}
</div>
<div class="col-md-3 noprint">
{% include 'inc/search_panel.html' %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,17 @@
{% extends '_base.html' %}
{% load buttons %}
{% block content %}
<div class="pull-right noprint">
{% export_button content_type %}
</div>
<h1>{% block title %}Interfaces{% endblock %}</h1>
<div class="row">
<div class="col-md-9">
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:interface_bulk_edit' bulk_delete_url='dcim:interface_bulk_delete' %}
</div>
<div class="col-md-3 noprint">
{% include 'inc/search_panel.html' %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,17 @@
{% extends '_base.html' %}
{% load buttons %}
{% block content %}
<div class="pull-right noprint">
{% export_button content_type %}
</div>
<h1>{% block title %}Power Outlets{% endblock %}</h1>
<div class="row">
<div class="col-md-9">
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:poweroutlet_bulk_edit' bulk_delete_url='dcim:poweroutlet_bulk_delete' %}
</div>
<div class="col-md-3 noprint">
{% include 'inc/search_panel.html' %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,17 @@
{% extends '_base.html' %}
{% load buttons %}
{% block content %}
<div class="pull-right noprint">
{% export_button content_type %}
</div>
<h1>{% block title %}Power Ports{% endblock %}</h1>
<div class="row">
<div class="col-md-9">
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:powerport_bulk_edit' bulk_delete_url='dcim:powerport_bulk_delete' %}
</div>
<div class="col-md-3 noprint">
{% include 'inc/search_panel.html' %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,17 @@
{% extends '_base.html' %}
{% load buttons %}
{% block content %}
<div class="pull-right noprint">
{% export_button content_type %}
</div>
<h1>{% block title %}Rear Ports{% endblock %}</h1>
<div class="row">
<div class="col-md-9">
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:rearport_bulk_edit' bulk_delete_url='dcim:rearport_bulk_delete' %}
</div>
<div class="col-md-3 noprint">
{% include 'inc/search_panel.html' %}
</div>
</div>
{% endblock %}

View File

@ -239,7 +239,7 @@
<a href="{% url 'dcim:poweroutlet_import' %}" class="btn btn-xs btn-info" title="Import"><i class="fa fa-download"></i></a>
</div>
{% endif %}
<a href="{% url 'dcim:poweroutlet_list' %}">Power Outlet</a>
<a href="{% url 'dcim:poweroutlet_list' %}">Power Outlets</a>
</li>
<li{% if not perms.dcim.view_devicebay %} class="disabled"{% endif %}>
{% if perms.dcim.add_devicebay %}