mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Added 'select all' option to object lists for bulk edit/delete
This commit is contained in:
@ -1,6 +1,8 @@
|
||||
import django_tables2 as tables
|
||||
from django_tables2.utils import Accessor
|
||||
|
||||
from utilities.tables import ToggleColumn
|
||||
|
||||
from .models import Circuit, CircuitType, Provider
|
||||
|
||||
|
||||
@ -16,7 +18,7 @@ CIRCUITTYPE_EDIT_LINK = """
|
||||
#
|
||||
|
||||
class ProviderTable(tables.Table):
|
||||
pk = tables.CheckBoxColumn(visible=False, default='')
|
||||
pk = ToggleColumn()
|
||||
name = tables.LinkColumn('circuits:provider', args=[Accessor('slug')], verbose_name='Name')
|
||||
asn = tables.Column(verbose_name='ASN')
|
||||
circuit_count = tables.Column(accessor=Accessor('count_circuits'), verbose_name='Circuits')
|
||||
@ -35,7 +37,7 @@ class ProviderTable(tables.Table):
|
||||
#
|
||||
|
||||
class CircuitTypeTable(tables.Table):
|
||||
pk = tables.CheckBoxColumn(visible=False, default='')
|
||||
pk = ToggleColumn()
|
||||
name = tables.LinkColumn(verbose_name='Name')
|
||||
circuit_count = tables.Column(verbose_name='Circuits')
|
||||
slug = tables.Column(verbose_name='Slug')
|
||||
@ -55,7 +57,7 @@ class CircuitTypeTable(tables.Table):
|
||||
#
|
||||
|
||||
class CircuitTable(tables.Table):
|
||||
pk = tables.CheckBoxColumn(visible=False, default='')
|
||||
pk = ToggleColumn()
|
||||
cid = tables.LinkColumn('circuits:circuit', args=[Accessor('pk')], verbose_name='ID')
|
||||
type = tables.Column(verbose_name='Type')
|
||||
provider = tables.LinkColumn('circuits:provider', args=[Accessor('provider.slug')], verbose_name='Provider')
|
||||
|
@ -1,6 +1,8 @@
|
||||
import django_tables2 as tables
|
||||
from django_tables2.utils import Accessor
|
||||
|
||||
from utilities.tables import ToggleColumn
|
||||
|
||||
from .models import (
|
||||
ConsolePort, ConsolePortTemplate, ConsoleServerPortTemplate, Device, DeviceRole, DeviceType, InterfaceTemplate,
|
||||
Manufacturer, Platform, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, Site,
|
||||
@ -75,7 +77,7 @@ class SiteTable(tables.Table):
|
||||
#
|
||||
|
||||
class RackGroupTable(tables.Table):
|
||||
pk = tables.CheckBoxColumn(visible=False, default='')
|
||||
pk = ToggleColumn()
|
||||
name = tables.LinkColumn(verbose_name='Name')
|
||||
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
|
||||
rack_count = tables.Column(verbose_name='Racks')
|
||||
@ -96,7 +98,7 @@ class RackGroupTable(tables.Table):
|
||||
#
|
||||
|
||||
class RackTable(tables.Table):
|
||||
pk = tables.CheckBoxColumn(visible=False, default='')
|
||||
pk = ToggleColumn()
|
||||
name = tables.LinkColumn('dcim:rack', args=[Accessor('pk')], verbose_name='Name')
|
||||
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
|
||||
group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
|
||||
@ -118,7 +120,7 @@ class RackTable(tables.Table):
|
||||
#
|
||||
|
||||
class ManufacturerTable(tables.Table):
|
||||
pk = tables.CheckBoxColumn(visible=False, default='')
|
||||
pk = ToggleColumn()
|
||||
name = tables.LinkColumn(verbose_name='Name')
|
||||
devicetype_count = tables.Column(verbose_name='Device Types')
|
||||
slug = tables.Column(verbose_name='Slug')
|
||||
@ -138,7 +140,7 @@ class ManufacturerTable(tables.Table):
|
||||
#
|
||||
|
||||
class DeviceTypeTable(tables.Table):
|
||||
pk = tables.CheckBoxColumn(visible=False, default='')
|
||||
pk = ToggleColumn()
|
||||
model = tables.LinkColumn('dcim:devicetype', args=[Accessor('pk')], verbose_name='Device Type')
|
||||
|
||||
class Meta:
|
||||
@ -155,7 +157,7 @@ class DeviceTypeTable(tables.Table):
|
||||
#
|
||||
|
||||
class ConsolePortTemplateTable(tables.Table):
|
||||
pk = tables.CheckBoxColumn(visible=False, default='')
|
||||
pk = ToggleColumn()
|
||||
|
||||
class Meta:
|
||||
model = ConsolePortTemplate
|
||||
@ -168,7 +170,7 @@ class ConsolePortTemplateTable(tables.Table):
|
||||
|
||||
|
||||
class ConsoleServerPortTemplateTable(tables.Table):
|
||||
pk = tables.CheckBoxColumn(visible=False, default='')
|
||||
pk = ToggleColumn()
|
||||
|
||||
class Meta:
|
||||
model = ConsoleServerPortTemplate
|
||||
@ -181,7 +183,7 @@ class ConsoleServerPortTemplateTable(tables.Table):
|
||||
|
||||
|
||||
class PowerPortTemplateTable(tables.Table):
|
||||
pk = tables.CheckBoxColumn(visible=False, default='')
|
||||
pk = ToggleColumn()
|
||||
|
||||
class Meta:
|
||||
model = PowerPortTemplate
|
||||
@ -194,7 +196,7 @@ class PowerPortTemplateTable(tables.Table):
|
||||
|
||||
|
||||
class PowerOutletTemplateTable(tables.Table):
|
||||
pk = tables.CheckBoxColumn(visible=False, default='')
|
||||
pk = ToggleColumn()
|
||||
|
||||
class Meta:
|
||||
model = PowerOutletTemplate
|
||||
@ -207,7 +209,7 @@ class PowerOutletTemplateTable(tables.Table):
|
||||
|
||||
|
||||
class InterfaceTemplateTable(tables.Table):
|
||||
pk = tables.CheckBoxColumn(visible=False, default='')
|
||||
pk = ToggleColumn()
|
||||
|
||||
class Meta:
|
||||
model = InterfaceTemplate
|
||||
@ -224,7 +226,7 @@ class InterfaceTemplateTable(tables.Table):
|
||||
#
|
||||
|
||||
class DeviceRoleTable(tables.Table):
|
||||
pk = tables.CheckBoxColumn(visible=False, default='')
|
||||
pk = ToggleColumn()
|
||||
name = tables.LinkColumn(verbose_name='Name')
|
||||
device_count = tables.Column(verbose_name='Devices')
|
||||
slug = tables.Column(verbose_name='Slug')
|
||||
@ -245,7 +247,7 @@ class DeviceRoleTable(tables.Table):
|
||||
#
|
||||
|
||||
class PlatformTable(tables.Table):
|
||||
pk = tables.CheckBoxColumn(visible=False, default='')
|
||||
pk = ToggleColumn()
|
||||
name = tables.LinkColumn(verbose_name='Name')
|
||||
device_count = tables.Column(verbose_name='Devices')
|
||||
slug = tables.Column(verbose_name='Slug')
|
||||
@ -265,7 +267,7 @@ class PlatformTable(tables.Table):
|
||||
#
|
||||
|
||||
class DeviceTable(tables.Table):
|
||||
pk = tables.CheckBoxColumn(visible=False, default='')
|
||||
pk = ToggleColumn()
|
||||
status = tables.TemplateColumn(template_code=STATUS_ICON, verbose_name='')
|
||||
name = tables.TemplateColumn(template_code=DEVICE_LINK, verbose_name='Name')
|
||||
site = tables.Column(accessor=Accessor('rack.site'), verbose_name='Site')
|
||||
|
@ -1,6 +1,8 @@
|
||||
import django_tables2 as tables
|
||||
from django_tables2.utils import Accessor
|
||||
|
||||
from utilities.tables import ToggleColumn
|
||||
|
||||
from .models import Aggregate, IPAddress, Prefix, RIR, Role, VLAN, VRF
|
||||
|
||||
|
||||
@ -54,7 +56,7 @@ STATUS_LABEL = """
|
||||
#
|
||||
|
||||
class VRFTable(tables.Table):
|
||||
pk = tables.CheckBoxColumn(visible=False, default='')
|
||||
pk = ToggleColumn()
|
||||
name = tables.LinkColumn('ipam:vrf', args=[Accessor('pk')], verbose_name='Name')
|
||||
rd = tables.Column(verbose_name='RD')
|
||||
description = tables.Column(sortable=False, verbose_name='Description')
|
||||
@ -73,7 +75,7 @@ class VRFTable(tables.Table):
|
||||
#
|
||||
|
||||
class RIRTable(tables.Table):
|
||||
pk = tables.CheckBoxColumn(visible=False, default='')
|
||||
pk = ToggleColumn()
|
||||
name = tables.LinkColumn(verbose_name='Name')
|
||||
aggregate_count = tables.Column(verbose_name='Aggregates')
|
||||
slug = tables.Column(verbose_name='Slug')
|
||||
@ -93,7 +95,7 @@ class RIRTable(tables.Table):
|
||||
#
|
||||
|
||||
class AggregateTable(tables.Table):
|
||||
pk = tables.CheckBoxColumn(visible=False, default='')
|
||||
pk = ToggleColumn()
|
||||
prefix = tables.LinkColumn('ipam:aggregate', args=[Accessor('pk')], verbose_name='Aggregate')
|
||||
rir = tables.Column(verbose_name='RIR')
|
||||
child_count = tables.Column(verbose_name='Prefixes')
|
||||
@ -115,7 +117,7 @@ class AggregateTable(tables.Table):
|
||||
#
|
||||
|
||||
class RoleTable(tables.Table):
|
||||
pk = tables.CheckBoxColumn(visible=False, default='')
|
||||
pk = ToggleColumn()
|
||||
name = tables.Column(verbose_name='Name')
|
||||
prefix_count = tables.Column(accessor=Accessor('count_prefixes'), orderable=False, verbose_name='Prefixes')
|
||||
vlan_count = tables.Column(accessor=Accessor('count_vlans'), orderable=False, verbose_name='VLANs')
|
||||
@ -136,7 +138,7 @@ class RoleTable(tables.Table):
|
||||
#
|
||||
|
||||
class PrefixTable(tables.Table):
|
||||
pk = tables.CheckBoxColumn(visible=False, default='')
|
||||
pk = ToggleColumn()
|
||||
status = tables.TemplateColumn(STATUS_LABEL, verbose_name='Status')
|
||||
prefix = tables.TemplateColumn(PREFIX_LINK, verbose_name='Prefix')
|
||||
vrf = tables.Column(orderable=False, default='Global', verbose_name='VRF')
|
||||
@ -173,7 +175,7 @@ class PrefixBriefTable(tables.Table):
|
||||
#
|
||||
|
||||
class IPAddressTable(tables.Table):
|
||||
pk = tables.CheckBoxColumn(visible=False, default='')
|
||||
pk = ToggleColumn()
|
||||
address = tables.LinkColumn('ipam:ipaddress', args=[Accessor('pk')], verbose_name='IP Address')
|
||||
vrf = tables.Column(orderable=False, default='Global', verbose_name='VRF')
|
||||
device = tables.LinkColumn('dcim:device', args=[Accessor('interface.device.pk')], orderable=False,
|
||||
@ -212,7 +214,7 @@ class IPAddressBriefTable(tables.Table):
|
||||
#
|
||||
|
||||
class VLANTable(tables.Table):
|
||||
pk = tables.CheckBoxColumn(visible=False, default='')
|
||||
pk = ToggleColumn()
|
||||
vid = tables.LinkColumn('ipam:vlan', args=[Accessor('pk')], verbose_name='ID')
|
||||
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
|
||||
name = tables.Column(verbose_name='Name')
|
||||
|
@ -1,6 +1,8 @@
|
||||
import django_tables2 as tables
|
||||
from django_tables2.utils import Accessor
|
||||
|
||||
from utilities.tables import ToggleColumn
|
||||
|
||||
from .models import SecretRole, Secret
|
||||
|
||||
|
||||
@ -16,7 +18,7 @@ SECRETROLE_EDIT_LINK = """
|
||||
#
|
||||
|
||||
class SecretRoleTable(tables.Table):
|
||||
pk = tables.CheckBoxColumn(visible=False, default='')
|
||||
pk = ToggleColumn()
|
||||
name = tables.LinkColumn(verbose_name='Name')
|
||||
secret_count = tables.Column(verbose_name='Secrets')
|
||||
slug = tables.Column(verbose_name='Slug')
|
||||
@ -36,7 +38,7 @@ class SecretRoleTable(tables.Table):
|
||||
#
|
||||
|
||||
class SecretTable(tables.Table):
|
||||
pk = tables.CheckBoxColumn(visible=False, default='')
|
||||
pk = ToggleColumn()
|
||||
device = tables.LinkColumn('secrets:secret', args=[Accessor('pk')], verbose_name='Device')
|
||||
role = tables.Column(verbose_name='Role')
|
||||
name = tables.Column(verbose_name='Name')
|
||||
|
@ -4,6 +4,7 @@
|
||||
<form method="post" class="form form-horizontal">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="redirect_url" value="{{ request.path }}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" />
|
||||
<input type="hidden" name="pk_all" value="{% for row in table.rows %}{{ row.record.pk }}{% if not forloop.last %},{% endif %}{% endfor %}" />
|
||||
{% render_table table table_template|default:'table.html' %}
|
||||
{% if perms.dcim.add_interface %}
|
||||
<button type="submit" name="_edit" formaction="{% url 'dcim:interface_bulk_add' %}" class="btn btn-primary btn-sm">
|
||||
|
@ -4,6 +4,7 @@
|
||||
<form method="post" class="form form-horizontal">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="redirect_url" value="{{ request.path }}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" />
|
||||
<input type="hidden" name="pk_all" value="{% for row in table.rows %}{{ row.record.pk }}{% if not forloop.last %},{% endif %}{% endfor %}" />
|
||||
{% render_table table table_template|default:'table.html' %}
|
||||
{% if bulk_edit_url and table.model|user_can_change:request.user %}
|
||||
<button type="submit" name="_edit" formaction="{% url bulk_edit_url %}" class="btn btn-warning btn-sm">
|
||||
|
12
netbox/utilities/tables.py
Normal file
12
netbox/utilities/tables.py
Normal file
@ -0,0 +1,12 @@
|
||||
import django_tables2 as tables
|
||||
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
|
||||
class ToggleColumn(tables.CheckBoxColumn):
|
||||
default = ''
|
||||
visible = False
|
||||
|
||||
@property
|
||||
def header(self):
|
||||
return mark_safe('<input type="checkbox" name="_all" title="Select all" />')
|
@ -260,10 +260,14 @@ class BulkEditView(View):
|
||||
else:
|
||||
redirect_url = reverse(self.default_redirect_url)
|
||||
|
||||
if request.POST.get('_all'):
|
||||
pk_list = request.POST.get('pk_all').split(',')
|
||||
else:
|
||||
pk_list = request.POST.getlist('pk')
|
||||
|
||||
if '_apply' in request.POST:
|
||||
form = self.form(request.POST)
|
||||
if form.is_valid():
|
||||
pk_list = [obj.pk for obj in form.cleaned_data['pk']]
|
||||
updated_count = self.update_objects(pk_list, form)
|
||||
msg = 'Updated {} {}'.format(updated_count, self.cls._meta.verbose_name_plural)
|
||||
messages.success(self.request, msg)
|
||||
@ -272,9 +276,9 @@ class BulkEditView(View):
|
||||
return redirect(redirect_url)
|
||||
|
||||
else:
|
||||
form = self.form(initial={'pk': request.POST.getlist('pk')})
|
||||
form = self.form(initial={'pk': pk_list})
|
||||
|
||||
selected_objects = self.cls.objects.filter(pk__in=request.POST.getlist('pk'))
|
||||
selected_objects = self.cls.objects.filter(pk__in=pk_list)
|
||||
if not selected_objects:
|
||||
messages.warning(request, "No {} were selected.".format(self.cls._meta.verbose_name_plural))
|
||||
return redirect(redirect_url)
|
||||
@ -313,17 +317,21 @@ class BulkDeleteView(View):
|
||||
else:
|
||||
redirect_url = reverse(self.default_redirect_url)
|
||||
|
||||
if request.POST.get('_all'):
|
||||
pk_list = request.POST.get('pk_all').split(',')
|
||||
else:
|
||||
pk_list = request.POST.getlist('pk')
|
||||
|
||||
if '_confirm' in request.POST:
|
||||
form = self.form(request.POST)
|
||||
if form.is_valid():
|
||||
|
||||
# Delete objects
|
||||
objects_to_delete = self.cls.objects.filter(pk__in=[v.id for v in form.cleaned_data['pk']])
|
||||
queryset = self.cls.objects.filter(pk__in=pk_list)
|
||||
try:
|
||||
deleted_count = objects_to_delete.count()
|
||||
objects_to_delete.delete()
|
||||
deleted_count = queryset.delete()[0]
|
||||
except ProtectedError, e:
|
||||
handle_protectederror(list(objects_to_delete), request, e)
|
||||
handle_protectederror(list(queryset), request, e)
|
||||
return redirect(redirect_url)
|
||||
|
||||
msg = 'Deleted {} {}'.format(deleted_count, self.cls._meta.verbose_name_plural)
|
||||
@ -332,9 +340,9 @@ class BulkDeleteView(View):
|
||||
return redirect(redirect_url)
|
||||
|
||||
else:
|
||||
form = self.form(initial={'pk': request.POST.getlist('pk')})
|
||||
form = self.form(initial={'pk': pk_list})
|
||||
|
||||
selected_objects = self.cls.objects.filter(pk__in=form.initial.get('pk'))
|
||||
selected_objects = self.cls.objects.filter(pk__in=pk_list)
|
||||
if not selected_objects:
|
||||
messages.warning(request, "No {} were selected for deletion.".format(self.cls._meta.verbose_name_plural))
|
||||
return redirect(redirect_url)
|
||||
|
Reference in New Issue
Block a user