Browse Source

Hide table checkboxes when no bulk actions are enabled

jeremystretch 4 years ago
parent
commit
21e3159711

+ 25 - 15
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),
         }

+ 1 - 0
netbox/templates/dcim/device_list.html

@@ -73,4 +73,5 @@
       </ul>
     </div>
   {% endif %}
+  {{ block.super }}
 {% endblock %}

+ 38 - 42
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:
   <div class="controls">
     <div class="control-group">
       {% 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" %}
-          <div id="select-all-box" class="d-none card noprint">
-            <form method="post" class="form col-md-12">
-              {% csrf_token %}
-              <div class="card-body">
-                <div class="float-end">
-                  {% if bulk_edit_url and permissions.change %}
-                    <button type="submit" name="_edit" formaction="{% url bulk_edit_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-warning btn-sm" disabled>
-                      <span class="mdi mdi-pencil" aria-hidden="true"></span> Edit All
-                    </button>
-                  {% endif %}
-                  {% if bulk_delete_url and permissions.delete %}
-                    <button type="submit" name="_delete" formaction="{% url bulk_delete_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-danger btn-sm" disabled>
-                      <span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete All
-                    </button>
-                  {% endif %}
-                </div>
-                <div class="form-check">
-                  <input type="checkbox" id="select-all" name="_all" class="form-check-input" />
-                  <label for="select-all" class="form-check-label">
-                    Select <strong>all {{ table.rows|length }} {{ table.data.verbose_name_plural }}</strong> matching query
-                  </label>
-                </div>
+        <div id="select-all-box" class="d-none card noprint">
+          <form method="post" class="form col-md-12">
+            {% csrf_token %}
+            <div class="card-body">
+              <div class="float-end">
+                {% if 'bulk_edit' in actions %}
+                  <button type="submit" name="_edit" formaction="{% url model|viewname:"bulk_edit" %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-warning btn-sm" disabled>
+                    <span class="mdi mdi-pencil" aria-hidden="true"></span> Edit All
+                  </button>
+                {% endif %}
+                {% if 'bulk_delete' in actions %}
+                  <button type="submit" name="_delete" formaction="{% url model|viewname:"bulk_delete" %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-danger btn-sm" disabled>
+                    <span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete All
+                  </button>
+                {% endif %}
               </div>
-            </form>
-          </div>
-        {% endwith %}
+              <div class="form-check">
+                <input type="checkbox" id="select-all" name="_all" class="form-check-input" />
+                <label for="select-all" class="form-check-label">
+                  Select <strong>all {{ table.rows|length }} {{ table.data.verbose_name_plural }}</strong> matching query
+                </label>
+              </div>
+            </div>
+          </form>
+        </div>
       {% endif %}
 
       {# Object table controls #}
@@ -118,17 +113,18 @@ Context:
         {# Form buttons #}
         <div class="noprint bulk-buttons">
           <div class="bulk-button-group">
-            {% block bulk_buttons %}{% endblock %}
-            {% if 'bulk_edit' in actions and permissions.change %}
-              <button type="submit" name="_edit" formaction="{% url model|viewname:"bulk_edit" %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-warning btn-sm">
-                <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit Selected
-              </button>
-            {% endif %}
-            {% if 'bulk_delete' in actions and permissions.delete %}
-              <button type="submit" name="_delete" formaction="{% url model|viewname:"bulk_delete" %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-danger btn-sm">
-                <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete Selected
-              </button>
-            {% endif %}
+            {% block bulk_buttons %}
+              {% if 'bulk_edit' in actions %}
+                <button type="submit" name="_edit" formaction="{% url model|viewname:"bulk_edit" %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-warning btn-sm">
+                  <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit Selected
+                </button>
+              {% endif %}
+              {% if 'bulk_delete' in actions %}
+                <button type="submit" name="_delete" formaction="{% url model|viewname:"bulk_delete" %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-danger btn-sm">
+                  <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete Selected
+                </button>
+              {% endif %}
+            {% endblock %}
           </div>
         </div>
 

+ 1 - 0
netbox/templates/virtualization/virtualmachine_list.html

@@ -17,4 +17,5 @@
       </ul>
     </div>
   {% endif %}
+  {{ block.super }}
 {% endblock %}

+ 0 - 3
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,