diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index 96efc0de7..7267e73ed 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -24,6 +24,7 @@ from utilities.htmx import is_htmx from utilities.permissions import get_permission_for_model from utilities.views import GetReturnURLMixin from .base import BaseMultiObjectView +from .mixins import TableMixin __all__ = ( 'BulkComponentCreateView', @@ -36,9 +37,9 @@ __all__ = ( ) -class ObjectListView(BaseMultiObjectView): +class ObjectListView(BaseMultiObjectView, TableMixin): """ - Display multiple objects, all of the same type, as a table. + Display multiple objects, all the same type, as a table. Attributes: filterset: A django-filter FilterSet that is applied to the queryset @@ -61,20 +62,6 @@ class ObjectListView(BaseMultiObjectView): def get_required_permission(self): return get_permission_for_model(self.queryset.model, 'view') - 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 - bulk_actions: Show checkboxes for object selection - """ - table = self.table(self.queryset, user=request.user) - if 'pk' in table.base_columns and bulk_actions: - table.columns.show('pk') - - return table - # # Export methods # @@ -159,7 +146,7 @@ class ObjectListView(BaseMultiObjectView): # Export the current table view if request.GET['export'] == 'table': - table = self.get_table(request, has_bulk_actions) + table = self.get_table(self.queryset, request, has_bulk_actions) columns = [name for name, _ in table.selected_columns] return self.export_table(table, columns) @@ -177,12 +164,11 @@ class ObjectListView(BaseMultiObjectView): # Fall back to default table/YAML export else: - table = self.get_table(request, has_bulk_actions) + table = self.get_table(self.queryset, request, has_bulk_actions) return self.export_table(table) # Render the objects table - table = self.get_table(request, has_bulk_actions) - table.configure(request) + table = self.get_table(self.queryset, request, has_bulk_actions) # If this is an HTMX request, return only the rendered table HTML if is_htmx(request): diff --git a/netbox/netbox/views/generic/mixins.py b/netbox/netbox/views/generic/mixins.py new file mode 100644 index 000000000..0adf3a4c4 --- /dev/null +++ b/netbox/netbox/views/generic/mixins.py @@ -0,0 +1,22 @@ +__all__ = ( + 'TableMixin', +) + + +class TableMixin: + + def get_table(self, data, request, bulk_actions=True): + """ + Return the django-tables2 Table instance to be used for rendering the objects list. + + Args: + data: Queryset or iterable containing table data + request: The current request + bulk_actions: Render checkboxes for object selection + """ + table = self.table(data, user=request.user) + if 'pk' in table.base_columns and bulk_actions: + table.columns.show('pk') + table.configure(request) + + return table diff --git a/netbox/netbox/views/generic/object_views.py b/netbox/netbox/views/generic/object_views.py index 88abfa48f..f9d8b6ac9 100644 --- a/netbox/netbox/views/generic/object_views.py +++ b/netbox/netbox/views/generic/object_views.py @@ -1,4 +1,5 @@ import logging +from collections import defaultdict from copy import deepcopy from django.contrib import messages @@ -20,6 +21,7 @@ from utilities.permissions import get_permission_for_model from utilities.utils import get_viewname, normalize_querydict, prepare_cloned_fields from utilities.views import GetReturnURLMixin from .base import BaseObjectView +from .mixins import TableMixin __all__ = ( 'ComponentCreateView', @@ -69,23 +71,31 @@ class ObjectView(BaseObjectView): }) -class ObjectChildrenView(ObjectView): +class ObjectChildrenView(ObjectView, TableMixin): """ Display a table of child objects associated with the parent object. Attributes: - table: Table class used to render child objects list + child_model: The model class which represents the child objects + table: The django-tables2 Table class used to render the child objects list + filterset: A django-filter FilterSet that is applied to the queryset """ child_model = None table = None filterset = None + actions = ('bulk_edit', 'bulk_delete') + action_perms = defaultdict(set, **{ + 'bulk_edit': {'change'}, + 'bulk_delete': {'delete'}, + }) def get_children(self, request, parent): """ Return a QuerySet of child objects. - request: The current request - parent: The parent object + Args: + request: The current request + parent: The parent object """ raise NotImplementedError(f'{self.__class__.__name__} must implement get_children()') @@ -114,16 +124,16 @@ class ObjectChildrenView(ObjectView): if self.filterset: child_objects = self.filterset(request.GET, child_objects).qs - permissions = {} - for action in ('change', 'delete'): - perm_name = get_permission_for_model(self.child_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(self.child_model, name) for name in self.action_perms[action] + ]): + actions.append(action) - table = self.table(self.prep_table_data(request, child_objects, instance), user=request.user) - # Determine whether to display bulk action checkboxes - if 'pk' in table.base_columns and (permissions['change'] or permissions['delete']): - table.columns.show('pk') - table.configure(request) + table_data = self.prep_table_data(request, child_objects, instance) + table = self.get_table(table_data, request, bool(actions)) # If this is an HTMX request, return only the rendered table HTML if is_htmx(request): @@ -134,8 +144,9 @@ class ObjectChildrenView(ObjectView): return render(request, self.get_template_name(), { 'object': instance, + 'child_model': self.child_model, 'table': table, - 'permissions': permissions, + 'actions': actions, **self.get_extra_context(request, instance), })