Re-implemented method for bulk editing/deleting all objects within a filtered queryset

This commit is contained in:
Jeremy Stretch 2017-01-20 16:42:11 -05:00
parent 3bfc1ebcea
commit 39d083eae7
10 changed files with 74 additions and 21 deletions

View File

@ -67,6 +67,7 @@ class ProviderBulkImportView(PermissionRequiredMixin, BulkImportView):
class ProviderBulkEditView(PermissionRequiredMixin, BulkEditView):
permission_required = 'circuits.change_provider'
cls = Provider
filter = filters.ProviderFilter
form = forms.ProviderBulkEditForm
template_name = 'circuits/provider_bulk_edit.html'
default_redirect_url = 'circuits:provider_list'
@ -75,6 +76,7 @@ class ProviderBulkEditView(PermissionRequiredMixin, BulkEditView):
class ProviderBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'circuits.delete_provider'
cls = Provider
filter = filters.ProviderFilter
default_redirect_url = 'circuits:provider_list'
@ -156,6 +158,7 @@ class CircuitBulkImportView(PermissionRequiredMixin, BulkImportView):
class CircuitBulkEditView(PermissionRequiredMixin, BulkEditView):
permission_required = 'circuits.change_circuit'
cls = Circuit
filter = filters.CircuitFilter
form = forms.CircuitBulkEditForm
template_name = 'circuits/circuit_bulk_edit.html'
default_redirect_url = 'circuits:circuit_list'
@ -164,6 +167,7 @@ class CircuitBulkEditView(PermissionRequiredMixin, BulkEditView):
class CircuitBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'circuits.delete_circuit'
cls = Circuit
filter = filters.CircuitFilter
default_redirect_url = 'circuits:circuit_list'

View File

@ -177,6 +177,7 @@ class SiteBulkImportView(PermissionRequiredMixin, BulkImportView):
class SiteBulkEditView(PermissionRequiredMixin, BulkEditView):
permission_required = 'dcim.change_site'
cls = Site
filter = filters.SiteFilter
form = forms.SiteBulkEditForm
template_name = 'dcim/site_bulk_edit.html'
default_redirect_url = 'dcim:site_list'
@ -207,6 +208,7 @@ class RackGroupEditView(PermissionRequiredMixin, ObjectEditView):
class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'dcim.delete_rackgroup'
cls = RackGroup
filter = filters.RackGroupFilter
default_redirect_url = 'dcim:rackgroup_list'
@ -294,6 +296,7 @@ class RackBulkImportView(PermissionRequiredMixin, BulkImportView):
class RackBulkEditView(PermissionRequiredMixin, BulkEditView):
permission_required = 'dcim.change_rack'
cls = Rack
filter = filters.RackFilter
form = forms.RackBulkEditForm
template_name = 'dcim/rack_bulk_edit.html'
default_redirect_url = 'dcim:rack_list'
@ -302,6 +305,7 @@ class RackBulkEditView(PermissionRequiredMixin, BulkEditView):
class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'dcim.delete_rack'
cls = Rack
filter = filters.RackFilter
default_redirect_url = 'dcim:rack_list'
@ -410,6 +414,7 @@ class DeviceTypeDeleteView(PermissionRequiredMixin, ObjectDeleteView):
class DeviceTypeBulkEditView(PermissionRequiredMixin, BulkEditView):
permission_required = 'dcim.change_devicetype'
cls = DeviceType
filter = filters.DeviceTypeFilter
form = forms.DeviceTypeBulkEditForm
template_name = 'dcim/devicetype_bulk_edit.html'
default_redirect_url = 'dcim:devicetype_list'
@ -418,6 +423,7 @@ class DeviceTypeBulkEditView(PermissionRequiredMixin, BulkEditView):
class DeviceTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'dcim.delete_devicetype'
cls = DeviceType
filter = filters.DeviceTypeFilter
default_redirect_url = 'dcim:devicetype_list'
@ -703,6 +709,7 @@ class ChildDeviceBulkImportView(PermissionRequiredMixin, BulkImportView):
class DeviceBulkEditView(PermissionRequiredMixin, BulkEditView):
permission_required = 'dcim.change_device'
cls = Device
filter = filters.DeviceFilter
form = forms.DeviceBulkEditForm
template_name = 'dcim/device_bulk_edit.html'
default_redirect_url = 'dcim:device_list'
@ -711,6 +718,7 @@ class DeviceBulkEditView(PermissionRequiredMixin, BulkEditView):
class DeviceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'dcim.delete_device'
cls = Device
filter = filters.DeviceFilter
default_redirect_url = 'dcim:device_list'
@ -1245,7 +1253,7 @@ class DeviceBulkAddComponentView(View):
# Are we editing *all* objects in the queryset or just a selected subset?
if request.POST.get('_all'):
pk_list = [int(pk) for pk in request.POST.get('pk_all').split(',') if pk]
pk_list = [obj.pk for obj in filters.DeviceFilter(request.GET, Device.objects.all())]
else:
pk_list = [int(pk) for pk in request.POST.getlist('pk')]

View File

@ -138,6 +138,7 @@ class VRFBulkImportView(PermissionRequiredMixin, BulkImportView):
class VRFBulkEditView(PermissionRequiredMixin, BulkEditView):
permission_required = 'ipam.change_vrf'
cls = VRF
filter = filters.VRFFilter
form = forms.VRFBulkEditForm
template_name = 'ipam/vrf_bulk_edit.html'
default_redirect_url = 'ipam:vrf_list'
@ -146,6 +147,7 @@ class VRFBulkEditView(PermissionRequiredMixin, BulkEditView):
class VRFBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'ipam.delete_vrf'
cls = VRF
filter = filters.VRFFilter
default_redirect_url = 'ipam:vrf_list'
@ -250,6 +252,7 @@ class RIREditView(PermissionRequiredMixin, ObjectEditView):
class RIRBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'ipam.delete_rir'
cls = RIR
filter = filters.RIRFilter
default_redirect_url = 'ipam:rir_list'
@ -328,6 +331,7 @@ class AggregateBulkImportView(PermissionRequiredMixin, BulkImportView):
class AggregateBulkEditView(PermissionRequiredMixin, BulkEditView):
permission_required = 'ipam.change_aggregate'
cls = Aggregate
filter = filters.AggregateFilter
form = forms.AggregateBulkEditForm
template_name = 'ipam/aggregate_bulk_edit.html'
default_redirect_url = 'ipam:aggregate_list'
@ -336,6 +340,7 @@ class AggregateBulkEditView(PermissionRequiredMixin, BulkEditView):
class AggregateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'ipam.delete_aggregate'
cls = Aggregate
filter = filters.AggregateFilter
default_redirect_url = 'ipam:aggregate_list'
@ -460,6 +465,7 @@ class PrefixBulkImportView(PermissionRequiredMixin, BulkImportView):
class PrefixBulkEditView(PermissionRequiredMixin, BulkEditView):
permission_required = 'ipam.change_prefix'
cls = Prefix
filter = filters.PrefixFilter
form = forms.PrefixBulkEditForm
template_name = 'ipam/prefix_bulk_edit.html'
default_redirect_url = 'ipam:prefix_list'
@ -468,6 +474,7 @@ class PrefixBulkEditView(PermissionRequiredMixin, BulkEditView):
class PrefixBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'ipam.delete_prefix'
cls = Prefix
filter = filters.PrefixFilter
default_redirect_url = 'ipam:prefix_list'
@ -648,6 +655,7 @@ class IPAddressBulkImportView(PermissionRequiredMixin, BulkImportView):
class IPAddressBulkEditView(PermissionRequiredMixin, BulkEditView):
permission_required = 'ipam.change_ipaddress'
cls = IPAddress
filter = filters.IPAddressFilter
form = forms.IPAddressBulkEditForm
template_name = 'ipam/ipaddress_bulk_edit.html'
default_redirect_url = 'ipam:ipaddress_list'
@ -656,6 +664,7 @@ class IPAddressBulkEditView(PermissionRequiredMixin, BulkEditView):
class IPAddressBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'ipam.delete_ipaddress'
cls = IPAddress
filter = filters.IPAddressFilter
default_redirect_url = 'ipam:ipaddress_list'
@ -684,6 +693,7 @@ class VLANGroupEditView(PermissionRequiredMixin, ObjectEditView):
class VLANGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'ipam.delete_vlangroup'
cls = VLANGroup
filter = filters.VLANGroupFilter
default_redirect_url = 'ipam:vlangroup_list'
@ -737,6 +747,7 @@ class VLANBulkImportView(PermissionRequiredMixin, BulkImportView):
class VLANBulkEditView(PermissionRequiredMixin, BulkEditView):
permission_required = 'ipam.change_vlan'
cls = VLAN
filter = filters.VLANFilter
form = forms.VLANBulkEditForm
template_name = 'ipam/vlan_bulk_edit.html'
default_redirect_url = 'ipam:vlan_list'
@ -745,6 +756,7 @@ class VLANBulkEditView(PermissionRequiredMixin, BulkEditView):
class VLANBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'ipam.delete_vlan'
cls = VLAN
filter = filters.VLANFilter
default_redirect_url = 'ipam:vlan_list'

View File

@ -9,6 +9,14 @@ $(document).ready(function() {
$('#select_all').prop('checked', false);
}
});
// Enable hidden buttons when "select all" is checked
$('#select_all').click(function (event) {
if ($(this).is(':checked')) {
$('#select_all_box').find('button').prop('disabled', '');
} else {
$('#select_all_box').find('button').prop('disabled', 'disabled');
}
});
// Uncheck the "toggle all" checkbox if an item is unchecked
$('input:checkbox[name=pk]').click(function (event) {
if (!$(this).attr('checked')) {

View File

@ -202,6 +202,7 @@ def secret_import(request):
class SecretBulkEditView(PermissionRequiredMixin, BulkEditView):
permission_required = 'secrets.change_secret'
cls = Secret
filter = filters.SecretFilter
form = forms.SecretBulkEditForm
template_name = 'secrets/secret_bulk_edit.html'
default_redirect_url = 'secrets:secret_list'
@ -210,4 +211,5 @@ class SecretBulkEditView(PermissionRequiredMixin, BulkEditView):
class SecretBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'secrets.delete_secret'
cls = Secret
filter = filters.SecretFilter
default_redirect_url = 'secrets:secret_list'

View File

@ -7,12 +7,12 @@
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add Components <span class="caret"></span>
</button>
<ul class="dropdown-menu">
{% if perms.dcim.add_consoleport %}<li><a href="{% url 'dcim:device_bulk_add_consoleport' %}" class="formaction">Console Ports</a></li>{% endif %}
{% if perms.dcim.add_consoleserverport %}<li><a href="{% url 'dcim:device_bulk_add_consoleserverport' %}" class="formaction">Console Server Ports</a></li>{% endif %}
{% if perms.dcim.add_powerport %}<li><a href="{% url 'dcim:device_bulk_add_powerport' %}" class="formaction">Power Ports</a></li>{% endif %}
{% if perms.dcim.add_poweroutlet %}<li><a href="{% url 'dcim:device_bulk_add_poweroutlet' %}" class="formaction">Power Outlets</a></li>{% endif %}
{% if perms.dcim.add_interface %}<li><a href="{% url 'dcim:device_bulk_add_interface' %}" class="formaction">Interfaces</a></li>{% endif %}
{% if perms.dcim.add_devicebay %}<li><a href="{% url 'dcim:device_bulk_add_devicebay' %}" class="formaction">Device Bays</a></li>{% endif %}
{% if perms.dcim.add_consoleport %}<li><a href="{% url 'dcim:device_bulk_add_consoleport' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="formaction">Console Ports</a></li>{% endif %}
{% if perms.dcim.add_consoleserverport %}<li><a href="{% url 'dcim:device_bulk_add_consoleserverport' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="formaction">Console Server Ports</a></li>{% endif %}
{% if perms.dcim.add_powerport %}<li><a href="{% url 'dcim:device_bulk_add_powerport' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="formaction">Power Ports</a></li>{% endif %}
{% if perms.dcim.add_poweroutlet %}<li><a href="{% url 'dcim:device_bulk_add_poweroutlet' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="formaction">Power Outlets</a></li>{% endif %}
{% if perms.dcim.add_interface %}<li><a href="{% url 'dcim:device_bulk_add_interface' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="formaction">Interfaces</a></li>{% endif %}
{% if perms.dcim.add_devicebay %}<li><a href="{% url 'dcim:device_bulk_add_devicebay' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="formaction">Device Bays</a></li>{% endif %}
</ul>
</div>
{% endif %}

View File

@ -5,7 +5,7 @@
{% block message %}
<p>
Are you sure you want to delete these {{ obj_type_plural|default:"objects" }}{% if parent_obj %} from <a href="{{ parent_obj.get_absolute_url }}">{{ parent_obj }}</a>{% endif %}?
Are you sure you want to delete these {{ selected_objects|length }} {{ obj_type_plural|default:"objects" }}{% if parent_obj %} from <a href="{{ parent_obj.get_absolute_url }}">{{ parent_obj }}</a>{% endif %}?
</p>
<ul>
{% for obj in selected_objects %}

View File

@ -4,26 +4,39 @@
<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 obj in table.data.queryset %}{{ obj.pk|default:'' }}{% if not forloop.last %},{% endif %}{% endfor %}" />
{% if table.paginator.num_pages > 1 %}
<div id="select_all_box" class="hidden alert alert-info">
<div class="checkbox-inline">
<label for="select_all">
<input type="checkbox" id="select_all" name="_all" />
Select <strong>all {{ table.rows|length }} {{ table.data.verbose_name_plural }}</strong> matching query
</label>
<div id="select_all_box" class="hidden panel panel-default">
<div class="panel-body">
<div class="checkbox-inline">
<label for="select_all">
<input type="checkbox" id="select_all" name="_all" />
Select <strong>all {{ table.rows|length }} {{ table.data.verbose_name_plural }}</strong> matching query
</label>
</div>
<div class="pull-right">
{% if bulk_edit_url and table.model|user_can_change:request.user %}
<button type="submit" name="_edit" formaction="{% url bulk_edit_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-warning btn-sm" disabled="disabled">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit All
</button>
{% endif %}
{% if bulk_delete_url and table.model|user_can_delete:request.user %}
<button type="submit" name="_delete" formaction="{% url bulk_delete_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-danger btn-sm" disabled="disabled">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete All
</button>
{% endif %}
</div>
</div>
</div>
{% endif %}
{% render_table table table_template|default:'table.html' %}
{% block extra_actions %}{% endblock %}
{% 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">
<button type="submit" name="_edit" formaction="{% url bulk_edit_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-warning btn-sm">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit Selected
</button>
{% endif %}
{% if bulk_delete_url and table.model|user_can_delete:request.user %}
<button type="submit" name="_delete" formaction="{% url bulk_delete_url %}" class="btn btn-danger btn-sm">
<button type="submit" name="_delete" formaction="{% url bulk_delete_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-danger btn-sm">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete Selected
</button>
{% endif %}

View File

@ -105,6 +105,7 @@ class TenantBulkImportView(PermissionRequiredMixin, BulkImportView):
class TenantBulkEditView(PermissionRequiredMixin, BulkEditView):
permission_required = 'tenancy.change_tenant'
cls = Tenant
filter = filters.TenantFilter
form = forms.TenantBulkEditForm
template_name = 'tenancy/tenant_bulk_edit.html'
default_redirect_url = 'tenancy:tenant_list'
@ -113,4 +114,5 @@ class TenantBulkEditView(PermissionRequiredMixin, BulkEditView):
class TenantBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'tenancy.delete_tenant'
cls = Tenant
filter = filters.TenantFilter
default_redirect_url = 'tenancy:tenant_list'

View File

@ -397,12 +397,14 @@ class BulkEditView(View):
cls: The model of the objects being edited
parent_cls: The model of the parent object (if any)
filter: FilterSet to apply when deleting by QuerySet
form: The form class used to edit objects in bulk
template_name: The name of the template
default_redirect_url: Name of the URL to which the user is redirected after editing the objects
"""
cls = None
parent_cls = None
filter = None
form = None
template_name = None
default_redirect_url = None
@ -430,8 +432,8 @@ class BulkEditView(View):
raise ImproperlyConfigured('No redirect URL has been provided.')
# Are we editing *all* objects in the queryset or just a selected subset?
if request.POST.get('_all'):
pk_list = [int(pk) for pk in request.POST.get('pk_all').split(',') if pk]
if request.POST.get('_all') and self.filter is not None:
pk_list = [obj.pk for obj in self.filter(request.GET, self.cls.objects.only('pk'))]
else:
pk_list = [int(pk) for pk in request.POST.getlist('pk')]
@ -535,12 +537,14 @@ class BulkDeleteView(View):
cls: The model of the objects being deleted
parent_cls: The model of the parent object (if any)
filter: FilterSet to apply when deleting by QuerySet
form: The form class used to delete objects in bulk
template_name: The name of the template
default_redirect_url: Name of the URL to which the user is redirected after deleting the objects
"""
cls = None
parent_cls = None
filter = None
form = None
template_name = 'utilities/confirm_bulk_delete.html'
default_redirect_url = None
@ -565,8 +569,8 @@ class BulkDeleteView(View):
raise ImproperlyConfigured('No redirect URL has been provided.')
# Are we deleting *all* objects in the queryset or just a selected subset?
if request.POST.get('_all'):
pk_list = [int(pk) for pk in request.POST.get('pk_all').split(',') if pk]
if request.POST.get('_all') and self.filter is not None:
pk_list = [obj.pk for obj in self.filter(request.GET, self.cls.objects.only('pk'))]
else:
pk_list = [int(pk) for pk in request.POST.getlist('pk')]