Browse Source

Added 'select all' option to object lists for bulk edit/delete

Jeremy Stretch 9 years ago
parent
commit
b4619fad7a

+ 5 - 3
netbox/circuits/tables.py

@@ -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')

+ 14 - 12
netbox/dcim/tables.py

@@ -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')

+ 9 - 7
netbox/ipam/tables.py

@@ -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')

+ 4 - 2
netbox/secrets/tables.py

@@ -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')

+ 1 - 0
netbox/templates/dcim/inc/device_table.html

@@ -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">

+ 1 - 0
netbox/templates/utilities/obj_table.html

@@ -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 - 0
netbox/utilities/tables.py

@@ -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" />')

+ 17 - 9
netbox/utilities/views.py

@@ -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)