Closes #13550: Refactor view action mappings (#14062)

* Merge actions and action_perms into a single mapping

* Update obsolete permission maps

* Update obsolete action lists

* Normalize empty permission mappings

* Cleanup

* Add deprecation warnings

* Introduce DEFAULT_ACTION_PERMISSIONS constant
This commit is contained in:
Jeremy Stretch 2023-10-20 15:08:09 -04:00 committed by GitHub
parent 3f40ee5501
commit 450790ab4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 165 additions and 117 deletions

View File

@ -100,7 +100,9 @@ class DataFileListView(generic.ObjectListView):
filterset = filtersets.DataFileFilterSet filterset = filtersets.DataFileFilterSet
filterset_form = forms.DataFileFilterForm filterset_form = forms.DataFileFilterForm
table = tables.DataFileTable table = tables.DataFileTable
actions = ('bulk_delete',) actions = {
'bulk_delete': {'delete'},
}
@register_model_view(DataFile) @register_model_view(DataFile)
@ -128,7 +130,10 @@ class JobListView(generic.ObjectListView):
filterset = filtersets.JobFilterSet filterset = filtersets.JobFilterSet
filterset_form = forms.JobFilterForm filterset_form = forms.JobFilterForm
table = tables.JobTable table = tables.JobTable
actions = ('export', 'delete', 'bulk_delete') actions = {
'export': {'view'},
'bulk_delete': {'delete'},
}
class JobView(generic.ObjectView): class JobView(generic.ObjectView):

View File

@ -20,6 +20,7 @@ from circuits.models import Circuit, CircuitTermination
from extras.views import ObjectConfigContextView from extras.views import ObjectConfigContextView
from ipam.models import ASN, IPAddress, Prefix, VLAN, VLANGroup from ipam.models import ASN, IPAddress, Prefix, VLAN, VLANGroup
from ipam.tables import InterfaceVLANTable from ipam.tables import InterfaceVLANTable
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
from netbox.views import generic from netbox.views import generic
from tenancy.views import ObjectContactsView from tenancy.views import ObjectContactsView
from utilities.forms import ConfirmationForm from utilities.forms import ConfirmationForm
@ -46,15 +47,11 @@ CABLE_TERMINATION_TYPES = {
class DeviceComponentsView(generic.ObjectChildrenView): class DeviceComponentsView(generic.ObjectChildrenView):
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename', 'bulk_disconnect') actions = {
action_perms = defaultdict(set, **{ **DEFAULT_ACTION_PERMISSIONS,
'add': {'add'},
'import': {'add'},
'bulk_edit': {'change'},
'bulk_delete': {'delete'},
'bulk_rename': {'change'}, 'bulk_rename': {'change'},
'bulk_disconnect': {'change'}, 'bulk_disconnect': {'change'},
}) }
queryset = Device.objects.all() queryset = Device.objects.all()
def get_children(self, request, parent): def get_children(self, request, parent):
@ -1977,7 +1974,10 @@ class DeviceModuleBaysView(DeviceComponentsView):
table = tables.DeviceModuleBayTable table = tables.DeviceModuleBayTable
filterset = filtersets.ModuleBayFilterSet filterset = filtersets.ModuleBayFilterSet
template_name = 'dcim/device/modulebays.html' template_name = 'dcim/device/modulebays.html'
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') actions = {
**DEFAULT_ACTION_PERMISSIONS,
'bulk_rename': {'change'},
}
tab = ViewTab( tab = ViewTab(
label=_('Module Bays'), label=_('Module Bays'),
badge=lambda obj: obj.module_bay_count, badge=lambda obj: obj.module_bay_count,
@ -1993,7 +1993,10 @@ class DeviceDeviceBaysView(DeviceComponentsView):
table = tables.DeviceDeviceBayTable table = tables.DeviceDeviceBayTable
filterset = filtersets.DeviceBayFilterSet filterset = filtersets.DeviceBayFilterSet
template_name = 'dcim/device/devicebays.html' template_name = 'dcim/device/devicebays.html'
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') actions = {
**DEFAULT_ACTION_PERMISSIONS,
'bulk_rename': {'change'},
}
tab = ViewTab( tab = ViewTab(
label=_('Device Bays'), label=_('Device Bays'),
badge=lambda obj: obj.device_bay_count, badge=lambda obj: obj.device_bay_count,
@ -2005,11 +2008,14 @@ class DeviceDeviceBaysView(DeviceComponentsView):
@register_model_view(Device, 'inventory') @register_model_view(Device, 'inventory')
class DeviceInventoryView(DeviceComponentsView): class DeviceInventoryView(DeviceComponentsView):
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename')
child_model = InventoryItem child_model = InventoryItem
table = tables.DeviceInventoryItemTable table = tables.DeviceInventoryItemTable
filterset = filtersets.InventoryItemFilterSet filterset = filtersets.InventoryItemFilterSet
template_name = 'dcim/device/inventory.html' template_name = 'dcim/device/inventory.html'
actions = {
**DEFAULT_ACTION_PERMISSIONS,
'bulk_rename': {'change'},
}
tab = ViewTab( tab = ViewTab(
label=_('Inventory Items'), label=_('Inventory Items'),
badge=lambda obj: obj.inventory_item_count, badge=lambda obj: obj.inventory_item_count,
@ -2187,14 +2193,10 @@ class ConsolePortListView(generic.ObjectListView):
filterset_form = forms.ConsolePortFilterForm filterset_form = forms.ConsolePortFilterForm
table = tables.ConsolePortTable table = tables.ConsolePortTable
template_name = 'dcim/component_list.html' template_name = 'dcim/component_list.html'
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') actions = {
action_perms = defaultdict(set, **{ **DEFAULT_ACTION_PERMISSIONS,
'add': {'add'},
'import': {'add'},
'bulk_edit': {'change'},
'bulk_delete': {'delete'},
'bulk_rename': {'change'}, 'bulk_rename': {'change'},
}) }
@register_model_view(ConsolePort) @register_model_view(ConsolePort)
@ -2259,14 +2261,10 @@ class ConsoleServerPortListView(generic.ObjectListView):
filterset_form = forms.ConsoleServerPortFilterForm filterset_form = forms.ConsoleServerPortFilterForm
table = tables.ConsoleServerPortTable table = tables.ConsoleServerPortTable
template_name = 'dcim/component_list.html' template_name = 'dcim/component_list.html'
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') actions = {
action_perms = defaultdict(set, **{ **DEFAULT_ACTION_PERMISSIONS,
'add': {'add'},
'import': {'add'},
'bulk_edit': {'change'},
'bulk_delete': {'delete'},
'bulk_rename': {'change'}, 'bulk_rename': {'change'},
}) }
@register_model_view(ConsoleServerPort) @register_model_view(ConsoleServerPort)
@ -2331,14 +2329,10 @@ class PowerPortListView(generic.ObjectListView):
filterset_form = forms.PowerPortFilterForm filterset_form = forms.PowerPortFilterForm
table = tables.PowerPortTable table = tables.PowerPortTable
template_name = 'dcim/component_list.html' template_name = 'dcim/component_list.html'
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') actions = {
action_perms = defaultdict(set, **{ **DEFAULT_ACTION_PERMISSIONS,
'add': {'add'},
'import': {'add'},
'bulk_edit': {'change'},
'bulk_delete': {'delete'},
'bulk_rename': {'change'}, 'bulk_rename': {'change'},
}) }
@register_model_view(PowerPort) @register_model_view(PowerPort)
@ -2403,14 +2397,10 @@ class PowerOutletListView(generic.ObjectListView):
filterset_form = forms.PowerOutletFilterForm filterset_form = forms.PowerOutletFilterForm
table = tables.PowerOutletTable table = tables.PowerOutletTable
template_name = 'dcim/component_list.html' template_name = 'dcim/component_list.html'
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') actions = {
action_perms = defaultdict(set, **{ **DEFAULT_ACTION_PERMISSIONS,
'add': {'add'},
'import': {'add'},
'bulk_edit': {'change'},
'bulk_delete': {'delete'},
'bulk_rename': {'change'}, 'bulk_rename': {'change'},
}) }
@register_model_view(PowerOutlet) @register_model_view(PowerOutlet)
@ -2475,14 +2465,10 @@ class InterfaceListView(generic.ObjectListView):
filterset_form = forms.InterfaceFilterForm filterset_form = forms.InterfaceFilterForm
table = tables.InterfaceTable table = tables.InterfaceTable
template_name = 'dcim/component_list.html' template_name = 'dcim/component_list.html'
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') actions = {
action_perms = defaultdict(set, **{ **DEFAULT_ACTION_PERMISSIONS,
'add': {'add'},
'import': {'add'},
'bulk_edit': {'change'},
'bulk_delete': {'delete'},
'bulk_rename': {'change'}, 'bulk_rename': {'change'},
}) }
@register_model_view(Interface) @register_model_view(Interface)
@ -2595,14 +2581,10 @@ class FrontPortListView(generic.ObjectListView):
filterset_form = forms.FrontPortFilterForm filterset_form = forms.FrontPortFilterForm
table = tables.FrontPortTable table = tables.FrontPortTable
template_name = 'dcim/component_list.html' template_name = 'dcim/component_list.html'
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') actions = {
action_perms = defaultdict(set, **{ **DEFAULT_ACTION_PERMISSIONS,
'add': {'add'},
'import': {'add'},
'bulk_edit': {'change'},
'bulk_delete': {'delete'},
'bulk_rename': {'change'}, 'bulk_rename': {'change'},
}) }
@register_model_view(FrontPort) @register_model_view(FrontPort)
@ -2667,14 +2649,10 @@ class RearPortListView(generic.ObjectListView):
filterset_form = forms.RearPortFilterForm filterset_form = forms.RearPortFilterForm
table = tables.RearPortTable table = tables.RearPortTable
template_name = 'dcim/component_list.html' template_name = 'dcim/component_list.html'
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') actions = {
action_perms = defaultdict(set, **{ **DEFAULT_ACTION_PERMISSIONS,
'add': {'add'},
'import': {'add'},
'bulk_edit': {'change'},
'bulk_delete': {'delete'},
'bulk_rename': {'change'}, 'bulk_rename': {'change'},
}) }
@register_model_view(RearPort) @register_model_view(RearPort)
@ -2739,14 +2717,10 @@ class ModuleBayListView(generic.ObjectListView):
filterset_form = forms.ModuleBayFilterForm filterset_form = forms.ModuleBayFilterForm
table = tables.ModuleBayTable table = tables.ModuleBayTable
template_name = 'dcim/component_list.html' template_name = 'dcim/component_list.html'
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') actions = {
action_perms = defaultdict(set, **{ **DEFAULT_ACTION_PERMISSIONS,
'add': {'add'},
'import': {'add'},
'bulk_edit': {'change'},
'bulk_delete': {'delete'},
'bulk_rename': {'change'}, 'bulk_rename': {'change'},
}) }
@register_model_view(ModuleBay) @register_model_view(ModuleBay)
@ -2803,14 +2777,10 @@ class DeviceBayListView(generic.ObjectListView):
filterset_form = forms.DeviceBayFilterForm filterset_form = forms.DeviceBayFilterForm
table = tables.DeviceBayTable table = tables.DeviceBayTable
template_name = 'dcim/component_list.html' template_name = 'dcim/component_list.html'
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') actions = {
action_perms = defaultdict(set, **{ **DEFAULT_ACTION_PERMISSIONS,
'add': {'add'},
'import': {'add'},
'bulk_edit': {'change'},
'bulk_delete': {'delete'},
'bulk_rename': {'change'}, 'bulk_rename': {'change'},
}) }
@register_model_view(DeviceBay) @register_model_view(DeviceBay)
@ -2936,14 +2906,10 @@ class InventoryItemListView(generic.ObjectListView):
filterset_form = forms.InventoryItemFilterForm filterset_form = forms.InventoryItemFilterForm
table = tables.InventoryItemTable table = tables.InventoryItemTable
template_name = 'dcim/component_list.html' template_name = 'dcim/component_list.html'
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') actions = {
action_perms = defaultdict(set, **{ **DEFAULT_ACTION_PERMISSIONS,
'add': {'add'},
'import': {'add'},
'bulk_edit': {'change'},
'bulk_delete': {'delete'},
'bulk_rename': {'change'}, 'bulk_rename': {'change'},
}) }
@register_model_view(InventoryItem) @register_model_view(InventoryItem)
@ -3175,7 +3141,12 @@ class CableListView(generic.ObjectListView):
filterset = filtersets.CableFilterSet filterset = filtersets.CableFilterSet
filterset_form = forms.CableFilterForm filterset_form = forms.CableFilterForm
table = tables.CableTable table = tables.CableTable
actions = ('import', 'export', 'bulk_edit', 'bulk_delete') actions = {
'import': {'add'},
'export': {'view'},
'bulk_edit': {'change'},
'bulk_delete': {'delete'},
}
@register_model_view(Cable) @register_model_view(Cable)
@ -3269,7 +3240,9 @@ class ConsoleConnectionsListView(generic.ObjectListView):
filterset_form = forms.ConsoleConnectionFilterForm filterset_form = forms.ConsoleConnectionFilterForm
table = tables.ConsoleConnectionTable table = tables.ConsoleConnectionTable
template_name = 'dcim/connections_list.html' template_name = 'dcim/connections_list.html'
actions = ('export',) actions = {
'export': {'view'},
}
def get_extra_context(self, request): def get_extra_context(self, request):
return { return {
@ -3283,7 +3256,9 @@ class PowerConnectionsListView(generic.ObjectListView):
filterset_form = forms.PowerConnectionFilterForm filterset_form = forms.PowerConnectionFilterForm
table = tables.PowerConnectionTable table = tables.PowerConnectionTable
template_name = 'dcim/connections_list.html' template_name = 'dcim/connections_list.html'
actions = ('export',) actions = {
'export': {'view'},
}
def get_extra_context(self, request): def get_extra_context(self, request):
return { return {
@ -3297,7 +3272,9 @@ class InterfaceConnectionsListView(generic.ObjectListView):
filterset_form = forms.InterfaceConnectionFilterForm filterset_form = forms.InterfaceConnectionFilterForm
table = tables.InterfaceConnectionTable table = tables.InterfaceConnectionTable
template_name = 'dcim/connections_list.html' template_name = 'dcim/connections_list.html'
actions = ('export',) actions = {
'export': {'view'},
}
def get_extra_context(self, request): def get_extra_context(self, request):
return { return {

View File

@ -16,6 +16,7 @@ from core.tables import JobTable
from extras.dashboard.forms import DashboardWidgetAddForm, DashboardWidgetForm from extras.dashboard.forms import DashboardWidgetAddForm, DashboardWidgetForm
from extras.dashboard.utils import get_widget_class from extras.dashboard.utils import get_widget_class
from netbox.config import get_config, PARAMS from netbox.config import get_config, PARAMS
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
from netbox.views import generic from netbox.views import generic
from utilities.forms import ConfirmationForm, get_field_value from utilities.forms import ConfirmationForm, get_field_value
from utilities.htmx import is_htmx from utilities.htmx import is_htmx
@ -210,7 +211,10 @@ class ExportTemplateListView(generic.ObjectListView):
filterset_form = forms.ExportTemplateFilterForm filterset_form = forms.ExportTemplateFilterForm
table = tables.ExportTemplateTable table = tables.ExportTemplateTable
template_name = 'extras/exporttemplate_list.html' template_name = 'extras/exporttemplate_list.html'
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_sync') actions = {
**DEFAULT_ACTION_PERMISSIONS,
'bulk_sync': {'sync'},
}
@register_model_view(ExportTemplate) @register_model_view(ExportTemplate)
@ -472,7 +476,12 @@ class ConfigContextListView(generic.ObjectListView):
filterset_form = forms.ConfigContextFilterForm filterset_form = forms.ConfigContextFilterForm
table = tables.ConfigContextTable table = tables.ConfigContextTable
template_name = 'extras/configcontext_list.html' template_name = 'extras/configcontext_list.html'
actions = ('add', 'bulk_edit', 'bulk_delete', 'bulk_sync') actions = {
'add': {'add'},
'bulk_edit': {'change'},
'bulk_delete': {'delete'},
'bulk_sync': {'sync'},
}
@register_model_view(ConfigContext) @register_model_view(ConfigContext)
@ -576,7 +585,10 @@ class ConfigTemplateListView(generic.ObjectListView):
filterset_form = forms.ConfigTemplateFilterForm filterset_form = forms.ConfigTemplateFilterForm
table = tables.ConfigTemplateTable table = tables.ConfigTemplateTable
template_name = 'extras/configtemplate_list.html' template_name = 'extras/configtemplate_list.html'
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_sync') actions = {
**DEFAULT_ACTION_PERMISSIONS,
'bulk_sync': {'sync'},
}
@register_model_view(ConfigTemplate) @register_model_view(ConfigTemplate)
@ -627,7 +639,9 @@ class ObjectChangeListView(generic.ObjectListView):
filterset_form = forms.ObjectChangeFilterForm filterset_form = forms.ObjectChangeFilterForm
table = tables.ObjectChangeTable table = tables.ObjectChangeTable
template_name = 'extras/objectchange_list.html' template_name = 'extras/objectchange_list.html'
actions = ('export',) actions = {
'export': {'view'},
}
@register_model_view(ObjectChange) @register_model_view(ObjectChange)
@ -693,7 +707,9 @@ class ImageAttachmentListView(generic.ObjectListView):
filterset = filtersets.ImageAttachmentFilterSet filterset = filtersets.ImageAttachmentFilterSet
filterset_form = forms.ImageAttachmentFilterForm filterset_form = forms.ImageAttachmentFilterForm
table = tables.ImageAttachmentTable table = tables.ImageAttachmentTable
actions = ('export',) actions = {
'export': {'view'},
}
@register_model_view(ImageAttachment, 'edit') @register_model_view(ImageAttachment, 'edit')
@ -736,7 +752,12 @@ class JournalEntryListView(generic.ObjectListView):
filterset = filtersets.JournalEntryFilterSet filterset = filtersets.JournalEntryFilterSet
filterset_form = forms.JournalEntryFilterForm filterset_form = forms.JournalEntryFilterForm
table = tables.JournalEntryTable table = tables.JournalEntryTable
actions = ('import', 'export', 'bulk_edit', 'bulk_delete') actions = {
'import': {'add'},
'export': {'view'},
'bulk_edit': {'change'},
'bulk_delete': {'delete'},
}
@register_model_view(JournalEntry) @register_model_view(JournalEntry)

View File

@ -27,3 +27,12 @@ ADVISORY_LOCK_KEYS = {
'inventoryitem': 105700, 'inventoryitem': 105700,
'inventoryitemtemplate': 105800, 'inventoryitemtemplate': 105800,
} }
# Default view action permission mapping
DEFAULT_ACTION_PERMISSIONS = {
'add': {'add'},
'import': {'add'},
'export': {'view'},
'bulk_edit': {'change'},
'bulk_delete': {'delete'},
}

View File

@ -1,5 +1,6 @@
from collections import defaultdict import warnings
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
from utilities.permissions import get_permission_for_model from utilities.permissions import get_permission_for_model
__all__ = ( __all__ = (
@ -9,13 +10,15 @@ __all__ = (
class ActionsMixin: class ActionsMixin:
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete') """
action_perms = defaultdict(set, **{ Maps action names to the set of required permissions for each. Object list views reference this mapping to
'add': {'add'}, determine whether to render the applicable button for each action: The button will be rendered only if the user
'import': {'add'}, possesses the specified permission(s).
'bulk_edit': {'change'},
'bulk_delete': {'delete'}, Standard actions include: add, import, export, bulk_edit, and bulk_delete. Some views extend this default map
}) with custom actions, such as bulk_sync.
"""
actions = DEFAULT_ACTION_PERMISSIONS
def get_permitted_actions(self, user, model=None): def get_permitted_actions(self, user, model=None):
""" """
@ -23,11 +26,43 @@ class ActionsMixin:
""" """
model = model or self.queryset.model model = model or self.queryset.model
return [ # TODO: Remove backward compatibility in Netbox v4.0
action for action in self.actions if user.has_perms([ # Determine how permissions are being mapped to actions for the view
get_permission_for_model(model, name) for name in self.action_perms[action] if hasattr(self, 'action_perms'):
]) # Backward compatibility for <3.7
] permissions_map = self.action_perms
warnings.warn(
"Setting action_perms on views is deprecated and will be removed in NetBox v4.0. Use actions instead.",
DeprecationWarning
)
elif type(self.actions) is dict:
# New actions format (3.7+)
permissions_map = self.actions
else:
# actions is still defined as a list or tuple (<3.7) but no custom mapping is defined; use the old
# default mapping
permissions_map = {
'add': {'add'},
'import': {'add'},
'bulk_edit': {'change'},
'bulk_delete': {'delete'},
}
warnings.warn(
"View actions should be defined as a dictionary mapping. Support for the legacy list format will be "
"removed in NetBox v4.0.",
DeprecationWarning
)
# Resolve required permissions for each action
permitted_actions = []
for action in self.actions:
required_permissions = [
get_permission_for_model(model, name) for name in permissions_map.get(action, set())
]
if not required_permissions or user.has_perms(required_permissions):
permitted_actions.append(action)
return permitted_actions
class TableMixin: class TableMixin:

View File

@ -386,7 +386,11 @@ class ContactAssignmentListView(generic.ObjectListView):
filterset = filtersets.ContactAssignmentFilterSet filterset = filtersets.ContactAssignmentFilterSet
filterset_form = forms.ContactAssignmentFilterForm filterset_form = forms.ContactAssignmentFilterForm
table = tables.ContactAssignmentTable table = tables.ContactAssignmentTable
actions = ('export', 'bulk_edit', 'bulk_delete') actions = {
'export': {'view'},
'bulk_edit': {'change'},
'bulk_delete': {'delete'},
}
@register_model_view(ContactAssignment, 'edit') @register_model_view(ContactAssignment, 'edit')

View File

@ -16,6 +16,7 @@ from dcim.tables import DeviceTable
from extras.views import ObjectConfigContextView from extras.views import ObjectConfigContextView
from ipam.models import IPAddress from ipam.models import IPAddress
from ipam.tables import InterfaceVLANTable from ipam.tables import InterfaceVLANTable
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
from netbox.views import generic from netbox.views import generic
from tenancy.views import ObjectContactsView from tenancy.views import ObjectContactsView
from utilities.utils import count_related from utilities.utils import count_related
@ -199,13 +200,13 @@ class ClusterDevicesView(generic.ObjectChildrenView):
table = DeviceTable table = DeviceTable
filterset = DeviceFilterSet filterset = DeviceFilterSet
template_name = 'virtualization/cluster/devices.html' template_name = 'virtualization/cluster/devices.html'
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_remove_devices') actions = {
action_perms = defaultdict(set, **{
'add': {'add'}, 'add': {'add'},
'import': {'add'}, 'import': {'add'},
'export': {'view'},
'bulk_edit': {'change'}, 'bulk_edit': {'change'},
'bulk_remove_devices': {'change'}, 'bulk_remove_devices': {'change'},
}) }
tab = ViewTab( tab = ViewTab(
label=_('Devices'), label=_('Devices'),
badge=lambda obj: obj.devices.count(), badge=lambda obj: obj.devices.count(),
@ -359,20 +360,16 @@ class VirtualMachineInterfacesView(generic.ObjectChildrenView):
table = tables.VirtualMachineVMInterfaceTable table = tables.VirtualMachineVMInterfaceTable
filterset = filtersets.VMInterfaceFilterSet filterset = filtersets.VMInterfaceFilterSet
template_name = 'virtualization/virtualmachine/interfaces.html' template_name = 'virtualization/virtualmachine/interfaces.html'
actions = {
**DEFAULT_ACTION_PERMISSIONS,
'bulk_rename': {'change'},
}
tab = ViewTab( tab = ViewTab(
label=_('Interfaces'), label=_('Interfaces'),
badge=lambda obj: obj.interface_count, badge=lambda obj: obj.interface_count,
permission='virtualization.view_vminterface', permission='virtualization.view_vminterface',
weight=500 weight=500
) )
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename')
action_perms = defaultdict(set, **{
'add': {'add'},
'import': {'add'},
'bulk_edit': {'change'},
'bulk_delete': {'delete'},
'bulk_rename': {'change'},
})
def get_children(self, request, parent): def get_children(self, request, parent):
return parent.interfaces.restrict(request.user, 'view').prefetch_related( return parent.interfaces.restrict(request.user, 'view').prefetch_related(