From 21e31597118ee806f34f40fff2310fa028a782d5 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 2 Mar 2022 16:13:59 -0500 Subject: [PATCH] Hide table checkboxes when no bulk actions are enabled --- netbox/netbox/views/generic/bulk_views.py | 40 ++++++---- netbox/templates/dcim/device_list.html | 1 + netbox/templates/generic/object_list.html | 80 +++++++++---------- .../virtualization/virtualmachine_list.html | 1 + netbox/utilities/permissions.py | 3 - 5 files changed, 65 insertions(+), 60 deletions(-) diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index 4de8bfef0..ecdc33432 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -1,5 +1,6 @@ import logging import re +from collections import defaultdict from copy import deepcopy from django.contrib import messages @@ -42,27 +43,34 @@ class ObjectListView(BaseMultiObjectView): Attributes: filterset: A django-filter FilterSet that is applied to the queryset filterset_form: The form class used to render filter options - actions: Supported actions for the model. Default options are add, import, export, bulk_edit, and bulk_delete + actions: Supported actions for the model. When adding custom actions, bulk action names must + be prefixed with `bulk_`. Default actions: add, import, export, bulk_edit, bulk_delete + action_perms: A dictionary mapping supported actions to a set of permissions required for each """ template_name = 'generic/object_list.html' filterset = None filterset_form = None actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete') + action_perms = defaultdict(set, **{ + 'add': {'add'}, + 'import': {'add'}, + 'bulk_edit': {'change'}, + 'bulk_delete': {'delete'}, + }) def get_required_permission(self): return get_permission_for_model(self.queryset.model, 'view') - def get_table(self, request, permissions): + def get_table(self, request, bulk_actions=True): """ Return the django-tables2 Table instance to be used for rendering the objects list. Args: request: The current request - permissions: A dictionary mapping of the view, add, change, and delete permissions to booleans indicating - whether the user has each + bulk_actions: Show checkboxes for object selection """ table = self.table(self.queryset, user=request.user) - if 'pk' in table.base_columns and (permissions['change'] or permissions['delete']): + if 'pk' in table.base_columns and bulk_actions: table.columns.show('pk') return table @@ -135,17 +143,20 @@ class ObjectListView(BaseMultiObjectView): if self.filterset: self.queryset = self.filterset(request.GET, self.queryset).qs - # Compile a dictionary indicating which permissions are available to the current user for this model - permissions = {} - for action in ('add', 'change', 'delete', 'view'): - perm_name = get_permission_for_model(model, action) - permissions[action] = request.user.has_perm(perm_name) + # Determine the available actions + actions = [] + for action in self.actions: + if request.user.has_perms([ + get_permission_for_model(model, name) for name in self.action_perms[action] + ]): + actions.append(action) + has_bulk_actions = any([a.startswith('bulk_') for a in actions]) if 'export' in request.GET: # Export the current table view if request.GET['export'] == 'table': - table = self.get_table(request, permissions) + table = self.get_table(request, has_bulk_actions) columns = [name for name, _ in table.selected_columns] return self.export_table(table, columns) @@ -163,11 +174,11 @@ class ObjectListView(BaseMultiObjectView): # Fall back to default table/YAML export else: - table = self.get_table(request, permissions) + table = self.get_table(request, has_bulk_actions) return self.export_table(table) # Render the objects table - table = self.get_table(request, permissions) + table = self.get_table(request, has_bulk_actions) table.configure(request) # If this is an HTMX request, return only the rendered table HTML @@ -179,8 +190,7 @@ class ObjectListView(BaseMultiObjectView): context = { 'model': model, 'table': table, - 'permissions': permissions, - 'actions': self.actions, + 'actions': actions, 'filter_form': self.filterset_form(request.GET, label_suffix='') if self.filterset_form else None, **self.get_extra_context(request), } diff --git a/netbox/templates/dcim/device_list.html b/netbox/templates/dcim/device_list.html index 85025e413..60efc842e 100644 --- a/netbox/templates/dcim/device_list.html +++ b/netbox/templates/dcim/device_list.html @@ -73,4 +73,5 @@ {% endif %} + {{ block.super }} {% endblock %} diff --git a/netbox/templates/generic/object_list.html b/netbox/templates/generic/object_list.html index 2d0494502..bf1e5b970 100644 --- a/netbox/templates/generic/object_list.html +++ b/netbox/templates/generic/object_list.html @@ -13,9 +13,6 @@ Blocks: Context: model: The model class being listed table: The table class used for rendering the list of objects - permissions: A mapping of add/change/delete permissions to boolean indicating - whether the current user possesses each of them. Controls the display of - add/edit/delete buttons. actions: A list of buttons to display. This template checks for add, import, export, bulk_edit, and bulk_delete. filter_form: The bound filterset form for filtering the objects list (optional) @@ -28,10 +25,10 @@ Context:
{% block extra_controls %}{% endblock %} - {% if permissions.add and 'add' in actions %} + {% if 'add' in actions %} {% add_button model|validated_viewname:"add" %} {% endif %} - {% if permissions.add and 'import' in actions %} + {% if 'import' in actions %} {% import_button model|validated_viewname:"import" %} {% endif %} {% if 'export' in actions %} @@ -72,33 +69,31 @@ Context: {# "Select all" form #} {% if table.paginator.num_pages > 1 %} - {% with bulk_edit_url=model|validated_viewname:"bulk_edit" bulk_delete_url=model|validated_viewname:"bulk_delete" %} -
-
- {% csrf_token %} -
-
- {% if bulk_edit_url and permissions.change %} - - {% endif %} - {% if bulk_delete_url and permissions.delete %} - - {% endif %} -
-
- - -
+
+ + {% csrf_token %} +
+
+ {% if 'bulk_edit' in actions %} + + {% endif %} + {% if 'bulk_delete' in actions %} + + {% endif %}
- -
- {% endwith %} +
+ + +
+
+ +
{% endif %} {# Object table controls #} @@ -118,17 +113,18 @@ Context: {# Form buttons #}
- {% block bulk_buttons %}{% endblock %} - {% if 'bulk_edit' in actions and permissions.change %} - - {% endif %} - {% if 'bulk_delete' in actions and permissions.delete %} - - {% endif %} + {% block bulk_buttons %} + {% if 'bulk_edit' in actions %} + + {% endif %} + {% if 'bulk_delete' in actions %} + + {% endif %} + {% endblock %}
diff --git a/netbox/templates/virtualization/virtualmachine_list.html b/netbox/templates/virtualization/virtualmachine_list.html index 90c784f31..d1bb69284 100644 --- a/netbox/templates/virtualization/virtualmachine_list.html +++ b/netbox/templates/virtualization/virtualmachine_list.html @@ -17,4 +17,5 @@
{% endif %} + {{ block.super }} {% endblock %} diff --git a/netbox/utilities/permissions.py b/netbox/utilities/permissions.py index b6bddcf61..b11bf504a 100644 --- a/netbox/utilities/permissions.py +++ b/netbox/utilities/permissions.py @@ -9,9 +9,6 @@ def get_permission_for_model(model, action): :param model: A model or instance :param action: View, add, change, or delete (string) """ - if action not in ('view', 'add', 'change', 'delete'): - raise ValueError(f"Unsupported action: {action}") - return '{}.{}_{}'.format( model._meta.app_label, action,