diff --git a/netbox/core/object_actions.py b/netbox/core/object_actions.py new file mode 100644 index 000000000..81b5fb2c8 --- /dev/null +++ b/netbox/core/object_actions.py @@ -0,0 +1,18 @@ +from django.utils.translation import gettext as _ + +from netbox.object_actions import ObjectAction + +__all__ = ( + 'BulkSync', +) + + +class BulkSync(ObjectAction): + """ + Synchronize multiple objects at once. + """ + name = 'bulk_sync' + label = _('Sync Data') + multi = True + permissions_required = {'sync'} + template_name = 'core/buttons/bulk_sync.html' diff --git a/netbox/core/views.py b/netbox/core/views.py index ef52147f1..1bd952b62 100644 --- a/netbox/core/views.py +++ b/netbox/core/views.py @@ -22,6 +22,7 @@ from rq.worker_registration import clean_worker_registry from core.utils import delete_rq_job, enqueue_rq_job, get_rq_jobs_from_status, requeue_rq_job, stop_rq_job from netbox.config import get_config, PARAMS +from netbox.object_actions import AddObject, BulkDelete, BulkExport, DeleteObject from netbox.registry import registry from netbox.views import generic from netbox.views.generic.base import BaseObjectView @@ -138,14 +139,13 @@ class DataFileListView(generic.ObjectListView): filterset = filtersets.DataFileFilterSet filterset_form = forms.DataFileFilterForm table = tables.DataFileTable - actions = { - 'bulk_delete': {'delete'}, - } + actions = (BulkDelete,) @register_model_view(DataFile) class DataFileView(generic.ObjectView): queryset = DataFile.objects.all() + actions = (DeleteObject,) @register_model_view(DataFile, 'delete') @@ -170,15 +170,13 @@ class JobListView(generic.ObjectListView): filterset = filtersets.JobFilterSet filterset_form = forms.JobFilterForm table = tables.JobTable - actions = { - 'export': {'view'}, - 'bulk_delete': {'delete'}, - } + actions = (BulkExport, BulkDelete) @register_model_view(Job) class JobView(generic.ObjectView): queryset = Job.objects.all() + actions = (DeleteObject,) @register_model_view(Job, 'delete') @@ -204,9 +202,7 @@ class ObjectChangeListView(generic.ObjectListView): filterset_form = forms.ObjectChangeFilterForm table = tables.ObjectChangeTable template_name = 'core/objectchange_list.html' - actions = { - 'export': {'view'}, - } + actions = (BulkExport,) @register_model_view(ObjectChange) @@ -274,6 +270,7 @@ class ConfigRevisionListView(generic.ObjectListView): filterset = filtersets.ConfigRevisionFilterSet filterset_form = forms.ConfigRevisionFilterForm table = tables.ConfigRevisionTable + actions = (AddObject, BulkExport) @register_model_view(ConfigRevision) diff --git a/netbox/dcim/object_actions.py b/netbox/dcim/object_actions.py new file mode 100644 index 000000000..00a409274 --- /dev/null +++ b/netbox/dcim/object_actions.py @@ -0,0 +1,38 @@ +from django.utils.translation import gettext as _ + +from netbox.object_actions import ObjectAction + +__all__ = ( + 'BulkAddComponents', + 'BulkDisconnect', +) + + +class BulkAddComponents(ObjectAction): + """ + Add components to the selected devices. + """ + label = _('Add Components') + multi = True + permissions_required = {'change'} + template_name = 'dcim/buttons/bulk_add_components.html' + + @classmethod + def get_context(cls, context, obj): + return { + 'perms': context.get('perms'), + 'request': context.get('request'), + 'formaction': context.get('formaction'), + 'label': cls.label, + } + + +class BulkDisconnect(ObjectAction): + """ + Disconnect each of a set of objects to which a cable is connected. + """ + name = 'bulk_disconnect' + label = _('Disconnect Selected') + multi = True + permissions_required = {'change'} + template_name = 'dcim/buttons/bulk_disconnect.html' diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 304438698..6d72f5c43 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -15,7 +15,7 @@ from circuits.models import Circuit, CircuitTermination from extras.views import ObjectConfigContextView, ObjectRenderConfigView from ipam.models import ASN, IPAddress, Prefix, VLANGroup, VLAN from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable -from netbox.constants import DEFAULT_ACTION_PERMISSIONS +from netbox.object_actions import * from netbox.views import generic from utilities.forms import ConfirmationForm from utilities.paginator import EnhancedPaginator, get_paginate_count @@ -34,6 +34,7 @@ from wireless.models import WirelessLAN from . import filtersets, forms, tables from .choices import DeviceFaceChoices, InterfaceModeChoices from .models import * +from .object_actions import BulkAddComponents, BulkDisconnect CABLE_TERMINATION_TYPES = { 'dcim.consoleport': ConsolePort, @@ -49,11 +50,6 @@ CABLE_TERMINATION_TYPES = { class DeviceComponentsView(generic.ObjectChildrenView): - actions = { - **DEFAULT_ACTION_PERMISSIONS, - 'bulk_rename': {'change'}, - 'bulk_disconnect': {'change'}, - } queryset = Device.objects.all() def get_children(self, request, parent): @@ -61,12 +57,8 @@ class DeviceComponentsView(generic.ObjectChildrenView): class DeviceTypeComponentsView(generic.ObjectChildrenView): - actions = { - **DEFAULT_ACTION_PERMISSIONS, - 'bulk_rename': {'change'}, - } + actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDelete) queryset = DeviceType.objects.all() - template_name = 'dcim/devicetype/component_templates.html' viewname = None # Used for return_url resolution def get_children(self, request, parent): @@ -78,9 +70,9 @@ class DeviceTypeComponentsView(generic.ObjectChildrenView): } -class ModuleTypeComponentsView(DeviceComponentsView): +class ModuleTypeComponentsView(generic.ObjectChildrenView): queryset = ModuleType.objects.all() - template_name = 'dcim/moduletype/component_templates.html' + actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDelete) viewname = None # Used for return_url resolution def get_children(self, request, parent): @@ -2116,7 +2108,7 @@ class DeviceListView(generic.ObjectListView): filterset = filtersets.DeviceFilterSet filterset_form = forms.DeviceFilterForm table = tables.DeviceTable - template_name = 'dcim/device_list.html' + actions = (AddObject, BulkImport, BulkExport, BulkAddComponents, BulkEdit, BulkRename, BulkDelete) @register_model_view(Device) @@ -2157,7 +2149,7 @@ class DeviceConsolePortsView(DeviceComponentsView): table = tables.DeviceConsolePortTable filterset = filtersets.ConsolePortFilterSet filterset_form = forms.ConsolePortFilterForm - template_name = 'dcim/device/consoleports.html', + actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDisconnect, BulkDelete) tab = ViewTab( label=_('Console Ports'), badge=lambda obj: obj.console_port_count, @@ -2173,7 +2165,7 @@ class DeviceConsoleServerPortsView(DeviceComponentsView): table = tables.DeviceConsoleServerPortTable filterset = filtersets.ConsoleServerPortFilterSet filterset_form = forms.ConsoleServerPortFilterForm - template_name = 'dcim/device/consoleserverports.html' + actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDisconnect, BulkDelete) tab = ViewTab( label=_('Console Server Ports'), badge=lambda obj: obj.console_server_port_count, @@ -2189,7 +2181,7 @@ class DevicePowerPortsView(DeviceComponentsView): table = tables.DevicePowerPortTable filterset = filtersets.PowerPortFilterSet filterset_form = forms.PowerPortFilterForm - template_name = 'dcim/device/powerports.html' + actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDisconnect, BulkDelete) tab = ViewTab( label=_('Power Ports'), badge=lambda obj: obj.power_port_count, @@ -2205,7 +2197,7 @@ class DevicePowerOutletsView(DeviceComponentsView): table = tables.DevicePowerOutletTable filterset = filtersets.PowerOutletFilterSet filterset_form = forms.PowerOutletFilterForm - template_name = 'dcim/device/poweroutlets.html' + actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDisconnect, BulkDelete) tab = ViewTab( label=_('Power Outlets'), badge=lambda obj: obj.power_outlet_count, @@ -2221,6 +2213,7 @@ class DeviceInterfacesView(DeviceComponentsView): table = tables.DeviceInterfaceTable filterset = filtersets.InterfaceFilterSet filterset_form = forms.InterfaceFilterForm + actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDisconnect, BulkDelete) template_name = 'dcim/device/interfaces.html' tab = ViewTab( label=_('Interfaces'), @@ -2243,7 +2236,7 @@ class DeviceFrontPortsView(DeviceComponentsView): table = tables.DeviceFrontPortTable filterset = filtersets.FrontPortFilterSet filterset_form = forms.FrontPortFilterForm - template_name = 'dcim/device/frontports.html' + actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDisconnect, BulkDelete) tab = ViewTab( label=_('Front Ports'), badge=lambda obj: obj.front_port_count, @@ -2259,7 +2252,7 @@ class DeviceRearPortsView(DeviceComponentsView): table = tables.DeviceRearPortTable filterset = filtersets.RearPortFilterSet filterset_form = forms.RearPortFilterForm - template_name = 'dcim/device/rearports.html' + actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDisconnect, BulkDelete) tab = ViewTab( label=_('Rear Ports'), badge=lambda obj: obj.rear_port_count, @@ -2275,11 +2268,7 @@ class DeviceModuleBaysView(DeviceComponentsView): table = tables.DeviceModuleBayTable filterset = filtersets.ModuleBayFilterSet filterset_form = forms.ModuleBayFilterForm - template_name = 'dcim/device/modulebays.html' - actions = { - **DEFAULT_ACTION_PERMISSIONS, - 'bulk_rename': {'change'}, - } + actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDelete) tab = ViewTab( label=_('Module Bays'), badge=lambda obj: obj.module_bay_count, @@ -2295,11 +2284,7 @@ class DeviceDeviceBaysView(DeviceComponentsView): table = tables.DeviceDeviceBayTable filterset = filtersets.DeviceBayFilterSet filterset_form = forms.DeviceBayFilterForm - template_name = 'dcim/device/devicebays.html' - actions = { - **DEFAULT_ACTION_PERMISSIONS, - 'bulk_rename': {'change'}, - } + actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDelete) tab = ViewTab( label=_('Device Bays'), badge=lambda obj: obj.device_bay_count, @@ -2315,11 +2300,7 @@ class DeviceInventoryView(DeviceComponentsView): table = tables.DeviceInventoryItemTable filterset = filtersets.InventoryItemFilterSet filterset_form = forms.InventoryItemFilterForm - template_name = 'dcim/device/inventory.html' - actions = { - **DEFAULT_ACTION_PERMISSIONS, - 'bulk_rename': {'change'}, - } + actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDelete) tab = ViewTab( label=_('Inventory Items'), badge=lambda obj: obj.inventory_item_count, @@ -2472,11 +2453,7 @@ class ConsolePortListView(generic.ObjectListView): filterset = filtersets.ConsolePortFilterSet filterset_form = forms.ConsolePortFilterForm table = tables.ConsolePortTable - template_name = 'dcim/component_list.html' - actions = { - **DEFAULT_ACTION_PERMISSIONS, - 'bulk_rename': {'change'}, - } + actions = (AddObject, BulkImport, BulkEdit, BulkRename, BulkExport, BulkDelete) @register_model_view(ConsolePort) @@ -2547,11 +2524,7 @@ class ConsoleServerPortListView(generic.ObjectListView): filterset = filtersets.ConsoleServerPortFilterSet filterset_form = forms.ConsoleServerPortFilterForm table = tables.ConsoleServerPortTable - template_name = 'dcim/component_list.html' - actions = { - **DEFAULT_ACTION_PERMISSIONS, - 'bulk_rename': {'change'}, - } + actions = (AddObject, BulkImport, BulkEdit, BulkRename, BulkExport, BulkDelete) @register_model_view(ConsoleServerPort) @@ -2622,11 +2595,7 @@ class PowerPortListView(generic.ObjectListView): filterset = filtersets.PowerPortFilterSet filterset_form = forms.PowerPortFilterForm table = tables.PowerPortTable - template_name = 'dcim/component_list.html' - actions = { - **DEFAULT_ACTION_PERMISSIONS, - 'bulk_rename': {'change'}, - } + actions = (AddObject, BulkImport, BulkEdit, BulkRename, BulkExport, BulkDelete) @register_model_view(PowerPort) @@ -2697,11 +2666,7 @@ class PowerOutletListView(generic.ObjectListView): filterset = filtersets.PowerOutletFilterSet filterset_form = forms.PowerOutletFilterForm table = tables.PowerOutletTable - template_name = 'dcim/component_list.html' - actions = { - **DEFAULT_ACTION_PERMISSIONS, - 'bulk_rename': {'change'}, - } + actions = (AddObject, BulkImport, BulkEdit, BulkRename, BulkExport, BulkDelete) @register_model_view(PowerOutlet) @@ -2772,11 +2737,7 @@ class InterfaceListView(generic.ObjectListView): filterset = filtersets.InterfaceFilterSet filterset_form = forms.InterfaceFilterForm table = tables.InterfaceTable - template_name = 'dcim/component_list.html' - actions = { - **DEFAULT_ACTION_PERMISSIONS, - 'bulk_rename': {'change'}, - } + actions = (AddObject, BulkImport, BulkEdit, BulkRename, BulkExport, BulkDelete) @register_model_view(Interface) @@ -2920,11 +2881,7 @@ class FrontPortListView(generic.ObjectListView): filterset = filtersets.FrontPortFilterSet filterset_form = forms.FrontPortFilterForm table = tables.FrontPortTable - template_name = 'dcim/component_list.html' - actions = { - **DEFAULT_ACTION_PERMISSIONS, - 'bulk_rename': {'change'}, - } + actions = (AddObject, BulkImport, BulkEdit, BulkRename, BulkExport, BulkDelete) @register_model_view(FrontPort) @@ -2995,11 +2952,7 @@ class RearPortListView(generic.ObjectListView): filterset = filtersets.RearPortFilterSet filterset_form = forms.RearPortFilterForm table = tables.RearPortTable - template_name = 'dcim/component_list.html' - actions = { - **DEFAULT_ACTION_PERMISSIONS, - 'bulk_rename': {'change'}, - } + actions = (AddObject, BulkImport, BulkEdit, BulkRename, BulkExport, BulkDelete) @register_model_view(RearPort) @@ -3070,11 +3023,7 @@ class ModuleBayListView(generic.ObjectListView): filterset = filtersets.ModuleBayFilterSet filterset_form = forms.ModuleBayFilterForm table = tables.ModuleBayTable - template_name = 'dcim/component_list.html' - actions = { - **DEFAULT_ACTION_PERMISSIONS, - 'bulk_rename': {'change'}, - } + actions = (AddObject, BulkImport, BulkEdit, BulkRename, BulkExport, BulkDelete) @register_model_view(ModuleBay) @@ -3136,11 +3085,7 @@ class DeviceBayListView(generic.ObjectListView): filterset = filtersets.DeviceBayFilterSet filterset_form = forms.DeviceBayFilterForm table = tables.DeviceBayTable - template_name = 'dcim/component_list.html' - actions = { - **DEFAULT_ACTION_PERMISSIONS, - 'bulk_rename': {'change'}, - } + actions = (AddObject, BulkImport, BulkEdit, BulkRename, BulkExport, BulkDelete) @register_model_view(DeviceBay) @@ -3283,11 +3228,7 @@ class InventoryItemListView(generic.ObjectListView): filterset = filtersets.InventoryItemFilterSet filterset_form = forms.InventoryItemFilterForm table = tables.InventoryItemTable - template_name = 'dcim/component_list.html' - actions = { - **DEFAULT_ACTION_PERMISSIONS, - 'bulk_rename': {'change'}, - } + actions = (AddObject, BulkImport, BulkEdit, BulkRename, BulkExport, BulkDelete) @register_model_view(InventoryItem) @@ -3627,9 +3568,7 @@ class ConsoleConnectionsListView(generic.ObjectListView): filterset_form = forms.ConsoleConnectionFilterForm table = tables.ConsoleConnectionTable template_name = 'dcim/connections_list.html' - actions = { - 'export': {'view'}, - } + actions = (BulkExport,) def get_extra_context(self, request): return { @@ -3643,9 +3582,7 @@ class PowerConnectionsListView(generic.ObjectListView): filterset_form = forms.PowerConnectionFilterForm table = tables.PowerConnectionTable template_name = 'dcim/connections_list.html' - actions = { - 'export': {'view'}, - } + actions = (BulkExport,) def get_extra_context(self, request): return { @@ -3659,9 +3596,7 @@ class InterfaceConnectionsListView(generic.ObjectListView): filterset_form = forms.InterfaceConnectionFilterForm table = tables.InterfaceConnectionTable template_name = 'dcim/connections_list.html' - actions = { - 'export': {'view'}, - } + actions = (BulkExport,) def get_extra_context(self, request): return { diff --git a/netbox/extras/views.py b/netbox/extras/views.py index ea465a4a4..7216c4eec 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -14,12 +14,13 @@ from jinja2.exceptions import TemplateError from core.choices import ManagedFileRootPathChoices from core.models import Job +from core.object_actions import BulkSync from dcim.models import Device, DeviceRole, Platform from extras.choices import LogLevelChoices from extras.dashboard.forms import DashboardWidgetAddForm, DashboardWidgetForm from extras.dashboard.utils import get_widget_class from extras.utils import SharedObjectViewMixin -from netbox.constants import DEFAULT_ACTION_PERMISSIONS +from netbox.object_actions import * from netbox.views import generic from netbox.views.generic.mixins import TableMixin from utilities.forms import ConfirmationForm, get_field_value @@ -232,11 +233,7 @@ class ExportTemplateListView(generic.ObjectListView): filterset = filtersets.ExportTemplateFilterSet filterset_form = forms.ExportTemplateFilterForm table = tables.ExportTemplateTable - template_name = 'extras/exporttemplate_list.html' - actions = { - **DEFAULT_ACTION_PERMISSIONS, - 'bulk_sync': {'sync'}, - } + actions = (AddObject, BulkImport, BulkSync, BulkEdit, BulkExport, BulkDelete) @register_model_view(ExportTemplate) @@ -347,9 +344,7 @@ class TableConfigListView(SharedObjectViewMixin, generic.ObjectListView): filterset = filtersets.TableConfigFilterSet filterset_form = forms.TableConfigFilterForm table = tables.TableConfigTable - actions = { - 'export': {'view'}, - } + actions = (BulkExport,) @register_model_view(TableConfig) @@ -758,13 +753,7 @@ class ConfigContextListView(generic.ObjectListView): filterset = filtersets.ConfigContextFilterSet filterset_form = forms.ConfigContextFilterForm table = tables.ConfigContextTable - template_name = 'extras/configcontext_list.html' - actions = { - 'add': {'add'}, - 'bulk_edit': {'change'}, - 'bulk_delete': {'delete'}, - 'bulk_sync': {'sync'}, - } + actions = (AddObject, BulkSync, BulkEdit, BulkDelete) @register_model_view(ConfigContext) @@ -877,11 +866,7 @@ class ConfigTemplateListView(generic.ObjectListView): filterset = filtersets.ConfigTemplateFilterSet filterset_form = forms.ConfigTemplateFilterForm table = tables.ConfigTemplateTable - template_name = 'extras/configtemplate_list.html' - actions = { - **DEFAULT_ACTION_PERMISSIONS, - 'bulk_sync': {'sync'}, - } + actions = (AddObject, BulkImport, BulkSync, BulkEdit, BulkExport, BulkDelete) @register_model_view(ConfigTemplate) @@ -992,9 +977,7 @@ class ImageAttachmentListView(generic.ObjectListView): filterset = filtersets.ImageAttachmentFilterSet filterset_form = forms.ImageAttachmentFilterForm table = tables.ImageAttachmentTable - actions = { - 'export': {'view'}, - } + actions = (BulkExport,) @register_model_view(ImageAttachment, 'add', detail=False) @@ -1038,12 +1021,7 @@ class JournalEntryListView(generic.ObjectListView): filterset = filtersets.JournalEntryFilterSet filterset_form = forms.JournalEntryFilterForm table = tables.JournalEntryTable - actions = { - 'export': {'view'}, - 'bulk_import': {'add'}, - 'bulk_edit': {'change'}, - 'bulk_delete': {'delete'}, - } + actions = (BulkImport, BulkEdit, BulkDelete) @register_model_view(JournalEntry) diff --git a/netbox/netbox/constants.py b/netbox/netbox/constants.py index 8d20fed45..f088c8e4a 100644 --- a/netbox/netbox/constants.py +++ b/netbox/netbox/constants.py @@ -28,7 +28,8 @@ ADVISORY_LOCK_KEYS = { 'job-schedules': 110100, } -# Default view action permission mapping +# TODO: Remove in NetBox v4.6 +# Legacy default view action permission mapping DEFAULT_ACTION_PERMISSIONS = { 'add': {'add'}, 'export': {'view'}, diff --git a/netbox/netbox/object_actions.py b/netbox/netbox/object_actions.py new file mode 100644 index 000000000..b427d58cd --- /dev/null +++ b/netbox/netbox/object_actions.py @@ -0,0 +1,176 @@ +from django.urls import reverse +from django.utils.translation import gettext as _ + +from core.models import ObjectType +from extras.models import ExportTemplate +from utilities.querydict import prepare_cloned_fields + +__all__ = ( + 'AddObject', + 'BulkDelete', + 'BulkEdit', + 'BulkExport', + 'BulkImport', + 'BulkRename', + 'CloneObject', + 'DeleteObject', + 'EditObject', + 'ObjectAction', +) + + +class ObjectAction: + """ + Base class for single- and multi-object operations. + + Params: + name: The action name appended to the module for view resolution + label: Human-friendly label for the rendered button + multi: Set to True if this action is performed by selecting multiple objects (i.e. using a table) + permissions_required: The set of permissions a user must have to perform the action + url_kwargs: The set of URL keyword arguments to pass when resolving the view's URL + """ + name = '' + label = None + multi = False + permissions_required = set() + url_kwargs = [] + + @classmethod + def get_url(cls, obj): + viewname = f'{obj._meta.app_label}:{obj._meta.model_name}_{cls.name}' + kwargs = { + kwarg: getattr(obj, kwarg) for kwarg in cls.url_kwargs + } + return reverse(viewname, kwargs=kwargs) + + @classmethod + def get_context(cls, context, obj): + return { + 'url': cls.get_url(obj), + 'label': cls.label, + } + + +class AddObject(ObjectAction): + """ + Create a new object. + """ + name = 'add' + label = _('Add') + permissions_required = {'add'} + template_name = 'buttons/add.html' + + +class CloneObject(ObjectAction): + """ + Populate the new object form with select details from an existing object. + """ + name = 'add' + label = _('Clone') + permissions_required = {'add'} + template_name = 'buttons/clone.html' + + @classmethod + def get_context(cls, context, obj): + param_string = prepare_cloned_fields(obj).urlencode() + url = f'{cls.get_url(obj)}?{param_string}' if param_string else None + return { + 'url': url, + 'label': cls.label, + } + + +class EditObject(ObjectAction): + """ + Edit a single object. + """ + name = 'edit' + label = _('Edit') + permissions_required = {'change'} + url_kwargs = ['pk'] + template_name = 'buttons/edit.html' + + +class DeleteObject(ObjectAction): + """ + Delete a single object. + """ + name = 'delete' + label = _('Delete') + permissions_required = {'delete'} + url_kwargs = ['pk'] + template_name = 'buttons/delete.html' + + +class BulkImport(ObjectAction): + """ + Import multiple objects at once. + """ + name = 'bulk_import' + label = _('Import') + permissions_required = {'add'} + template_name = 'buttons/import.html' + + +class BulkExport(ObjectAction): + """ + Export multiple objects at once. + """ + name = 'export' + label = _('Export') + permissions_required = {'view'} + template_name = 'buttons/export.html' + + @classmethod + def get_context(cls, context, model): + object_type = ObjectType.objects.get_for_model(model) + user = context['request'].user + + # Determine if the "all data" export returns CSV or YAML + data_format = 'YAML' if hasattr(object_type.model_class(), 'to_yaml') else 'CSV' + + # Retrieve all export templates for this model + export_templates = ExportTemplate.objects.restrict(user, 'view').filter(object_types=object_type) + + return { + 'label': cls.label, + 'perms': context['perms'], + 'object_type': object_type, + 'url_params': context['request'].GET.urlencode() if context['request'].GET else '', + 'export_templates': export_templates, + 'data_format': data_format, + } + + +class BulkEdit(ObjectAction): + """ + Change the value of one or more fields on a set of objects. + """ + name = 'bulk_edit' + label = _('Edit Selected') + multi = True + permissions_required = {'change'} + template_name = 'buttons/bulk_edit.html' + + +class BulkRename(ObjectAction): + """ + Rename multiple objects at once. + """ + name = 'bulk_rename' + label = _('Rename Selected') + multi = True + permissions_required = {'change'} + template_name = 'buttons/bulk_rename.html' + + +class BulkDelete(ObjectAction): + """ + Delete each of a set of objects. + """ + name = 'bulk_delete' + label = _('Delete Selected') + multi = True + permissions_required = {'delete'} + template_name = 'buttons/bulk_delete.html' diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index b52d12d98..08b060634 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -22,6 +22,7 @@ from core.models import ObjectType from core.signals import clear_events from extras.choices import CustomFieldUIEditableChoices from extras.models import CustomField, ExportTemplate +from netbox.object_actions import AddObject, BulkDelete, BulkEdit, BulkExport, BulkImport from utilities.error_handlers import handle_protectederror from utilities.exceptions import AbortRequest, AbortTransaction, PermissionsViolation from utilities.forms import BulkRenameForm, ConfirmationForm, restrict_form_fields @@ -60,6 +61,7 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin): template_name = 'generic/object_list.html' filterset = None filterset_form = None + actions = (AddObject, BulkImport, BulkEdit, BulkExport, BulkDelete) def get_required_permission(self): return get_permission_for_model(self.queryset.model, 'view') @@ -150,13 +152,13 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin): # Determine the available actions actions = self.get_permitted_actions(request.user) - has_bulk_actions = any([a.startswith('bulk_') for a in actions]) + has_table_actions = any(action.multi for action in actions) if 'export' in request.GET: # Export the current table view if request.GET['export'] == 'table': - table = self.get_table(self.queryset, request, has_bulk_actions) + table = self.get_table(self.queryset, request, has_table_actions) columns = [name for name, _ in table.selected_columns] return self.export_table(table, columns) @@ -174,11 +176,11 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin): # Fall back to default table/YAML export else: - table = self.get_table(self.queryset, request, has_bulk_actions) + table = self.get_table(self.queryset, request, has_table_actions) return self.export_table(table) # Render the objects table - table = self.get_table(self.queryset, request, has_bulk_actions) + table = self.get_table(self.queryset, request, has_table_actions) # If this is an HTMX request, return only the rendered table HTML if htmx_partial(request): diff --git a/netbox/netbox/views/generic/mixins.py b/netbox/netbox/views/generic/mixins.py index 5f9f62120..079164ed9 100644 --- a/netbox/netbox/views/generic/mixins.py +++ b/netbox/netbox/views/generic/mixins.py @@ -1,7 +1,7 @@ from django.shortcuts import get_object_or_404 from extras.models import TableConfig -from netbox.constants import DEFAULT_ACTION_PERMISSIONS +from netbox import object_actions from utilities.permissions import get_permission_for_model __all__ = ( @@ -9,6 +9,18 @@ __all__ = ( 'TableMixin', ) +# TODO: Remove in NetBox v4.5 +LEGACY_ACTIONS = { + 'add': object_actions.AddObject, + 'edit': object_actions.EditObject, + 'delete': object_actions.DeleteObject, + 'export': object_actions.BulkExport, + 'bulk_import': object_actions.BulkImport, + 'bulk_edit': object_actions.BulkEdit, + 'bulk_rename': object_actions.BulkRename, + 'bulk_delete': object_actions.BulkDelete, +} + class ActionsMixin: """ @@ -19,7 +31,24 @@ class ActionsMixin: 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 + actions = tuple() + + # TODO: Remove in NetBox v4.5 + def _convert_legacy_actions(self): + """ + Convert a legacy dictionary mapping action name to required permissions to a list of ObjectAction subclasses. + """ + if type(self.actions) is not dict: + return + + actions = [] + for name in self.actions.keys(): + try: + actions.append(LEGACY_ACTIONS[name]) + except KeyError: + raise ValueError(f"Unsupported legacy action: {name}") + + self.actions = actions def get_permitted_actions(self, user, model=None): """ @@ -27,11 +56,15 @@ class ActionsMixin: """ model = model or self.queryset.model + # TODO: Remove in NetBox v4.5 + # Handle legacy action sets + self._convert_legacy_actions() + # Resolve required permissions for each action permitted_actions = [] for action in self.actions: required_permissions = [ - get_permission_for_model(model, name) for name in self.actions.get(action, set()) + get_permission_for_model(model, perm) for perm in action.permissions_required ] if not required_permissions or user.has_perms(required_permissions): permitted_actions.append(action) diff --git a/netbox/netbox/views/generic/object_views.py b/netbox/netbox/views/generic/object_views.py index a7acbffc0..2e9b73d20 100644 --- a/netbox/netbox/views/generic/object_views.py +++ b/netbox/netbox/views/generic/object_views.py @@ -14,6 +14,9 @@ from django.utils.safestring import mark_safe from django.utils.translation import gettext as _ from core.signals import clear_events +from netbox.object_actions import ( + AddObject, BulkDelete, BulkEdit, BulkExport, BulkImport, CloneObject, DeleteObject, EditObject, +) from utilities.error_handlers import handle_protectederror from utilities.exceptions import AbortRequest, PermissionsViolation from utilities.forms import ConfirmationForm, restrict_form_fields @@ -36,7 +39,7 @@ __all__ = ( ) -class ObjectView(BaseObjectView): +class ObjectView(ActionsMixin, BaseObjectView): """ Retrieve a single object for display. @@ -46,6 +49,7 @@ class ObjectView(BaseObjectView): tab: A ViewTab instance for the view """ tab = None + actions = (CloneObject, EditObject, DeleteObject) def get_required_permission(self): return get_permission_for_model(self.queryset.model, 'view') @@ -72,9 +76,11 @@ class ObjectView(BaseObjectView): request: The current request """ instance = self.get_object(**kwargs) + actions = self.get_permitted_actions(request.user, model=instance) return render(request, self.get_template_name(), { 'object': instance, + 'actions': actions, 'tab': self.tab, **self.get_extra_context(request, instance), }) @@ -97,6 +103,7 @@ class ObjectChildrenView(ObjectView, ActionsMixin, TableMixin): table = None filterset = None filterset_form = None + actions = (AddObject, BulkImport, BulkEdit, BulkExport, BulkDelete) template_name = 'generic/object_children.html' def get_children(self, request, parent): @@ -138,10 +145,10 @@ class ObjectChildrenView(ObjectView, ActionsMixin, TableMixin): # Determine the available actions actions = self.get_permitted_actions(request.user, model=self.child_model) - has_bulk_actions = any([a.startswith('bulk_') for a in actions]) + has_table_actions = any(action.multi for action in actions) table_data = self.prep_table_data(request, child_objects, instance) - table = self.get_table(table_data, request, has_bulk_actions) + table = self.get_table(table_data, request, has_table_actions) # If this is an HTMX request, return only the rendered table HTML if htmx_partial(request): diff --git a/netbox/templates/core/buttons/bulk_sync.html b/netbox/templates/core/buttons/bulk_sync.html new file mode 100644 index 000000000..e92ad15df --- /dev/null +++ b/netbox/templates/core/buttons/bulk_sync.html @@ -0,0 +1,3 @@ + diff --git a/netbox/templates/core/datafile.html b/netbox/templates/core/datafile.html index 175a0e2bc..0747547b1 100644 --- a/netbox/templates/core/datafile.html +++ b/netbox/templates/core/datafile.html @@ -11,12 +11,6 @@ {% endblock %} -{% block control-buttons %} - {% if request.user|can_delete:object %} - {% delete_button object %} - {% endif %} -{% endblock control-buttons %} - {% block content %}
diff --git a/netbox/templates/core/job.html b/netbox/templates/core/job.html index a38c3650a..49fa0231a 100644 --- a/netbox/templates/core/job.html +++ b/netbox/templates/core/job.html @@ -22,12 +22,6 @@ {% endif %} {% endblock breadcrumbs %} -{% block control-buttons %} - {% if request.user|can_delete:object %} - {% delete_button object %} - {% endif %} -{% endblock control-buttons %} - {% block content %}
diff --git a/netbox/templates/dcim/buttons/bulk_add_components.html b/netbox/templates/dcim/buttons/bulk_add_components.html new file mode 100644 index 000000000..b5eadeeac --- /dev/null +++ b/netbox/templates/dcim/buttons/bulk_add_components.html @@ -0,0 +1,71 @@ +{% load i18n %} +
+ + +
diff --git a/netbox/templates/dcim/buttons/bulk_disconnect.html b/netbox/templates/dcim/buttons/bulk_disconnect.html new file mode 100644 index 000000000..9ab53472b --- /dev/null +++ b/netbox/templates/dcim/buttons/bulk_disconnect.html @@ -0,0 +1,3 @@ + diff --git a/netbox/templates/dcim/component_list.html b/netbox/templates/dcim/component_list.html deleted file mode 100644 index 6f91aff3e..000000000 --- a/netbox/templates/dcim/component_list.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends 'generic/object_list.html' %} -{% load buttons %} -{% load helpers %} -{% load i18n %} - -{% block bulk_buttons %} -
- {% if 'bulk_edit' in actions %} - {% bulk_edit_button model query_params=request.GET %} - {% endif %} - {% if 'bulk_rename' in actions %} - {% with bulk_rename_view=model|validated_viewname:"bulk_rename" %} - - {% endwith %} - {% endif %} -
- {% if 'bulk_delete' in actions %} - {% bulk_delete_button model query_params=request.GET %} - {% endif %} -{% endblock %} diff --git a/netbox/templates/dcim/device/components_base.html b/netbox/templates/dcim/device/components_base.html deleted file mode 100644 index 47b0d7aab..000000000 --- a/netbox/templates/dcim/device/components_base.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends 'generic/object_children.html' %} -{% load helpers %} - -{% block bulk_edit_controls %} - {% with bulk_edit_view=child_model|validated_viewname:"bulk_edit" %} - {% if 'bulk_edit' in actions and bulk_edit_view %} - - {% endif %} - {% endwith %} - {% with bulk_rename_view=child_model|validated_viewname:"bulk_rename" %} - {% if 'bulk_rename' in actions and bulk_rename_view %} - - {% endif %} - {% endwith %} -{% endblock bulk_edit_controls %} diff --git a/netbox/templates/dcim/device/consoleports.html b/netbox/templates/dcim/device/consoleports.html deleted file mode 100644 index d8de85868..000000000 --- a/netbox/templates/dcim/device/consoleports.html +++ /dev/null @@ -1,28 +0,0 @@ -{% extends 'dcim/device/components_base.html' %} -{% load helpers %} -{% load i18n %} - -{% block bulk_delete_controls %} - {{ block.super }} - {% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %} - {% if 'bulk_disconnect' in actions and bulk_disconnect_view %} - - {% endif %} - {% endwith %} -{% endblock bulk_delete_controls %} - -{% block bulk_extra_controls %} - {{ block.super }} - {% if perms.dcim.add_consoleport %} - - {% endif %} -{% endblock bulk_extra_controls %} diff --git a/netbox/templates/dcim/device/consoleserverports.html b/netbox/templates/dcim/device/consoleserverports.html deleted file mode 100644 index 29efdb65b..000000000 --- a/netbox/templates/dcim/device/consoleserverports.html +++ /dev/null @@ -1,28 +0,0 @@ -{% extends 'dcim/device/components_base.html' %} -{% load helpers %} -{% load i18n %} - -{% block bulk_delete_controls %} - {{ block.super }} - {% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %} - {% if 'bulk_disconnect' in actions and bulk_disconnect_view %} - - {% endif %} - {% endwith %} -{% endblock bulk_delete_controls %} - -{% block bulk_extra_controls %} - {{ block.super }} - {% if perms.dcim.add_consoleserverport %} - - {% endif %} -{% endblock bulk_extra_controls %} \ No newline at end of file diff --git a/netbox/templates/dcim/device/devicebays.html b/netbox/templates/dcim/device/devicebays.html deleted file mode 100644 index b558cdfb4..000000000 --- a/netbox/templates/dcim/device/devicebays.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends 'dcim/device/components_base.html' %} -{% load i18n %} - -{% block bulk_extra_controls %} - {{ block.super }} - {% if perms.dcim.add_devicebay %} - - {% endif %} -{% endblock bulk_extra_controls %} diff --git a/netbox/templates/dcim/device/frontports.html b/netbox/templates/dcim/device/frontports.html deleted file mode 100644 index 4d7f14769..000000000 --- a/netbox/templates/dcim/device/frontports.html +++ /dev/null @@ -1,28 +0,0 @@ -{% extends 'dcim/device/components_base.html' %} -{% load helpers %} -{% load i18n %} - -{% block bulk_delete_controls %} - {{ block.super }} - {% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %} - {% if 'bulk_disconnect' in actions and bulk_disconnect_view %} - - {% endif %} - {% endwith %} -{% endblock bulk_delete_controls %} - -{% block bulk_extra_controls %} - {{ block.super }} - {% if perms.dcim.add_frontport %} - - {% endif %} -{% endblock bulk_extra_controls %} diff --git a/netbox/templates/dcim/device/interfaces.html b/netbox/templates/dcim/device/interfaces.html index 8d46d77ea..be81f97cb 100644 --- a/netbox/templates/dcim/device/interfaces.html +++ b/netbox/templates/dcim/device/interfaces.html @@ -1,30 +1,5 @@ -{% extends 'dcim/device/components_base.html' %} -{% load helpers %} -{% load i18n %} +{% extends 'generic/object_children.html' %} {% block table_controls %} - {% include 'dcim/device/inc/interface_table_controls.html' with table_modal="DeviceInterfaceTable_config" %} + {% include 'dcim/device/inc/interface_table_controls.html' with table_modal="DeviceInterfaceTable_config" %} {% endblock table_controls %} - -{% block bulk_delete_controls %} - {{ block.super }} - {% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %} - {% if 'bulk_disconnect' in actions and bulk_disconnect_view %} - - {% endif %} - {% endwith %} -{% endblock bulk_delete_controls %} - -{% block bulk_extra_controls %} - {{ block.super }} - {% if perms.dcim.add_interface %} - - {% trans "Add Interfaces" %} - - {% endif %} -{% endblock bulk_extra_controls %} diff --git a/netbox/templates/dcim/device/inventory.html b/netbox/templates/dcim/device/inventory.html deleted file mode 100644 index cbc113d86..000000000 --- a/netbox/templates/dcim/device/inventory.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends 'dcim/device/components_base.html' %} -{% load i18n %} - -{% block bulk_extra_controls %} - {{ block.super }} - {% if perms.dcim.add_inventoryitem %} - - {% endif %} -{% endblock bulk_extra_controls %} diff --git a/netbox/templates/dcim/device/modulebays.html b/netbox/templates/dcim/device/modulebays.html deleted file mode 100644 index c23532efe..000000000 --- a/netbox/templates/dcim/device/modulebays.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends 'dcim/device/components_base.html' %} -{% load i18n %} - -{% block bulk_extra_controls %} - {{ block.super }} - {% if perms.dcim.add_modulebay %} - - {% endif %} -{% endblock bulk_extra_controls %} diff --git a/netbox/templates/dcim/device/poweroutlets.html b/netbox/templates/dcim/device/poweroutlets.html deleted file mode 100644 index 66c765181..000000000 --- a/netbox/templates/dcim/device/poweroutlets.html +++ /dev/null @@ -1,28 +0,0 @@ -{% extends 'dcim/device/components_base.html' %} -{% load helpers %} -{% load i18n %} - -{% block bulk_delete_controls %} - {{ block.super }} - {% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %} - {% if 'bulk_disconnect' in actions and bulk_disconnect_view %} - - {% endif %} - {% endwith %} -{% endblock bulk_delete_controls %} - -{% block bulk_extra_controls %} - {{ block.super }} - {% if perms.dcim.add_poweroutlet %} - - {% endif %} -{% endblock bulk_extra_controls %} diff --git a/netbox/templates/dcim/device/powerports.html b/netbox/templates/dcim/device/powerports.html deleted file mode 100644 index a299130a4..000000000 --- a/netbox/templates/dcim/device/powerports.html +++ /dev/null @@ -1,28 +0,0 @@ -{% extends 'dcim/device/components_base.html' %} -{% load helpers %} -{% load i18n %} - -{% block bulk_delete_controls %} - {{ block.super }} - {% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %} - {% if 'bulk_disconnect' in actions and bulk_disconnect_view %} - - {% endif %} - {% endwith %} -{% endblock bulk_delete_controls %} - -{% block bulk_extra_controls %} - {{ block.super }} - {% if perms.dcim.add_powerport %} - - {% endif %} -{% endblock bulk_extra_controls %} diff --git a/netbox/templates/dcim/device/rearports.html b/netbox/templates/dcim/device/rearports.html deleted file mode 100644 index 184e80d99..000000000 --- a/netbox/templates/dcim/device/rearports.html +++ /dev/null @@ -1,28 +0,0 @@ -{% extends 'dcim/device/components_base.html' %} -{% load helpers %} -{% load i18n %} - -{% block bulk_delete_controls %} - {{ block.super }} - {% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %} - {% if 'bulk_disconnect' in actions and bulk_disconnect_view %} - - {% endif %} - {% endwith %} -{% endblock bulk_delete_controls %} - -{% block bulk_extra_controls %} - {{ block.super }} - {% if perms.dcim.add_rearport %} - - {% endif %} -{% endblock bulk_extra_controls %} \ No newline at end of file diff --git a/netbox/templates/dcim/device_list.html b/netbox/templates/dcim/device_list.html deleted file mode 100644 index 493b652f5..000000000 --- a/netbox/templates/dcim/device_list.html +++ /dev/null @@ -1,89 +0,0 @@ -{% extends 'generic/object_list.html' %} -{% load buttons %} -{% load i18n %} - -{% block bulk_buttons %} - {% if perms.dcim.change_device %} - - {% endif %} - {% if 'bulk_edit' in actions %} -
- {% bulk_edit_button model query_params=request.GET %} - -
- {% endif %} - {% if 'bulk_delete' in actions %} - {% bulk_delete_button model query_params=request.GET %} - {% endif %} -{% endblock %} diff --git a/netbox/templates/dcim/devicetype/component_templates.html b/netbox/templates/dcim/devicetype/component_templates.html deleted file mode 100644 index 291c7c988..000000000 --- a/netbox/templates/dcim/devicetype/component_templates.html +++ /dev/null @@ -1,25 +0,0 @@ -{% extends 'generic/object_children.html' %} -{% load helpers %} -{% load i18n %} -{% load perms %} - -{% block bulk_edit_controls %} - {% with bulk_edit_view=child_model|validated_viewname:"bulk_edit" %} - {% if 'bulk_edit' in actions and bulk_edit_view %} - - {% endif %} - {% endwith %} - {% with bulk_rename_view=child_model|validated_viewname:"bulk_rename" %} - {% if 'bulk_rename' in actions and bulk_rename_view %} - - {% endif %} - {% endwith %} -{% endblock bulk_edit_controls %} diff --git a/netbox/templates/dcim/moduletype/component_templates.html b/netbox/templates/dcim/moduletype/component_templates.html deleted file mode 100644 index 3cee0bbd9..000000000 --- a/netbox/templates/dcim/moduletype/component_templates.html +++ /dev/null @@ -1,30 +0,0 @@ -{% extends 'generic/object_children.html' %} -{% load render_table from django_tables2 %} -{% load helpers %} -{% load i18n %} - -{% block extra_controls %} - {% include 'dcim/inc/moduletype_buttons.html' %} -{% endblock %} - -{% block bulk_edit_controls %} - {% with bulk_edit_view=child_model|validated_viewname:"bulk_edit" %} - {% if 'bulk_edit' in actions and bulk_edit_view %} - - {% endif %} - {% endwith %} - {% with bulk_rename_view=child_model|validated_viewname:"bulk_rename" %} - {% if 'bulk_rename' in actions and bulk_rename_view %} - - {% endif %} - {% endwith %} -{% endblock bulk_edit_controls %} - diff --git a/netbox/templates/dcim/virtualchassis.html b/netbox/templates/dcim/virtualchassis.html index cce005ed1..da5a812a2 100644 --- a/netbox/templates/dcim/virtualchassis.html +++ b/netbox/templates/dcim/virtualchassis.html @@ -1,18 +1,8 @@ {% extends 'generic/object.html' %} -{% load buttons %} {% load helpers %} {% load plugins %} {% load i18n %} -{% block buttons %} - {% if perms.dcim.change_virtualchassis %} - {% edit_button object %} - {% endif %} - {% if perms.dcim.delete_virtualchassis %} - {% delete_button object %} - {% endif %} -{% endblock %} - {% block content %}
diff --git a/netbox/templates/extras/configcontext_list.html b/netbox/templates/extras/configcontext_list.html deleted file mode 100644 index 648323532..000000000 --- a/netbox/templates/extras/configcontext_list.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends 'generic/object_list.html' %} -{% load i18n %} - -{% block bulk_buttons %} - {% if perms.extras.sync_configcontext %} - - {% endif %} - {{ block.super }} -{% endblock %} diff --git a/netbox/templates/extras/configtemplate_list.html b/netbox/templates/extras/configtemplate_list.html deleted file mode 100644 index c9db67e36..000000000 --- a/netbox/templates/extras/configtemplate_list.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends 'generic/object_list.html' %} -{% load i18n %} - -{% block bulk_buttons %} - {% if perms.extras.sync_configtemplate %} - - {% endif %} - {{ block.super }} -{% endblock %} diff --git a/netbox/templates/extras/exporttemplate_list.html b/netbox/templates/extras/exporttemplate_list.html deleted file mode 100644 index 4284336a5..000000000 --- a/netbox/templates/extras/exporttemplate_list.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends 'generic/object_list.html' %} -{% load i18n %} - -{% block bulk_buttons %} - {% if perms.extras.sync_configcontext %} - - {% endif %} - {{ block.super }} -{% endblock %} diff --git a/netbox/templates/generic/bulk_remove.html b/netbox/templates/generic/bulk_remove.html deleted file mode 100644 index d0ba23097..000000000 --- a/netbox/templates/generic/bulk_remove.html +++ /dev/null @@ -1,72 +0,0 @@ -{% extends 'generic/_base.html' %} -{% load helpers %} -{% load render_table from django_tables2 %} -{% load i18n %} - -{% comment %} -Blocks: - - title: Page title - - tabs: Page tabs - - content: Primary page content - -Context: - - form: The bulk edit form class - - parent_obj: The parent object - - table: A table of objects being removed - - obj_type_plural: The plural form of the object type - - return_url: The URL to which the user is redirected after submitting the form -{% endcomment %} - -{% block title %} - {% trans "Remove" %} {{ table.rows|length }} {{ obj_type_plural|bettertitle }}? -{% endblock %} - -{% block tabs %} - -{% endblock tabs %} - -{% block content %} -
- -
-
-
- {% render_table table 'inc/table.html' %} -
-
-
- {% csrf_token %} - {% for field in form.hidden_fields %} - {{ field }} - {% endfor %} -
- {% trans "Cancel" %} - -
-
-
-
-{% endblock content %} diff --git a/netbox/templates/generic/object.html b/netbox/templates/generic/object.html index 42083280a..4bccf108c 100644 --- a/netbox/templates/generic/object.html +++ b/netbox/templates/generic/object.html @@ -80,15 +80,7 @@ Context: {% if perms.extras.add_subscription and object.subscriptions %} {% subscribe_button object %} {% endif %} - {% if request.user|can_add:object %} - {% clone_button object %} - {% endif %} - {% if request.user|can_change:object %} - {% edit_button object %} - {% endif %} - {% if request.user|can_delete:object %} - {% delete_button object %} - {% endif %} + {% action_buttons actions object %} {% endblock control-buttons %}
diff --git a/netbox/templates/generic/object_children.html b/netbox/templates/generic/object_children.html index c4d6dc52a..b9eabdc9d 100644 --- a/netbox/templates/generic/object_children.html +++ b/netbox/templates/generic/object_children.html @@ -1,4 +1,5 @@ {% extends base_template %} +{% load buttons %} {% load helpers %} {% load i18n %} @@ -7,8 +8,6 @@ Blocks: - content: Primary page content - table_controls: Control elements for the child objects table - bulk_controls: Bulk action buttons which appear beneath the child objects table - - bulk_edit_controls: Bulk edit buttons - - bulk_delete_controls: Bulk delete buttons - bulk_extra_controls: Other bulk action buttons - modals: Any pre-loaded modals @@ -36,36 +35,8 @@ Context:
{% block bulk_controls %} -
- {# Bulk edit buttons #} - {% block bulk_edit_controls %} - {% with bulk_edit_view=child_model|validated_viewname:"bulk_edit" %} - {% if 'bulk_edit' in actions and bulk_edit_view %} - - {% endif %} - {% endwith %} - {% endblock bulk_edit_controls %} -
-
- {# Bulk delete buttons #} - {% block bulk_delete_controls %} - {% with bulk_delete_view=child_model|validated_viewname:"bulk_delete" %} - {% if 'bulk_delete' in actions and bulk_delete_view %} - - {% endif %} - {% endwith %} - {% endblock bulk_delete_controls %} -
- {# Other bulk action buttons #} - {% block bulk_extra_controls %}{% endblock %} + {% action_buttons actions model multi=True %} + {% block bulk_extra_controls %}{% endblock %} {% endblock bulk_controls %}
diff --git a/netbox/templates/generic/object_list.html b/netbox/templates/generic/object_list.html index e6d5505a4..6a2c3d4c9 100644 --- a/netbox/templates/generic/object_list.html +++ b/netbox/templates/generic/object_list.html @@ -31,15 +31,7 @@ Context:
{% plugin_list_buttons model %} {% block extra_controls %}{% endblock %} - {% if 'add' in actions %} - {% add_button model %} - {% endif %} - {% if 'bulk_import' in actions %} - {% import_button model %} - {% endif %} - {% if 'export' in actions %} - {% export_button model %} - {% endif %} + {% action_buttons actions model %}
{% endblock controls %} @@ -91,12 +83,7 @@ Context:
- {% if 'bulk_edit' in actions %} - {% bulk_edit_button model query_params=request.GET %} - {% endif %} - {% if 'bulk_delete' in actions %} - {% bulk_delete_button model query_params=request.GET %} - {% endif %} + {% action_buttons actions model multi=True %}
@@ -124,12 +111,7 @@ Context:
{% block bulk_buttons %}
- {% if 'bulk_edit' in actions %} - {% bulk_edit_button model query_params=request.GET %} - {% endif %} - {% if 'bulk_delete' in actions %} - {% bulk_delete_button model query_params=request.GET %} - {% endif %} + {% action_buttons actions model multi=True %}
{% endblock %}
diff --git a/netbox/templates/virtualization/buttons/bulk_add_components.html b/netbox/templates/virtualization/buttons/bulk_add_components.html new file mode 100644 index 000000000..8b0500862 --- /dev/null +++ b/netbox/templates/virtualization/buttons/bulk_add_components.html @@ -0,0 +1,22 @@ +{% load i18n %} +
+ + +
diff --git a/netbox/templates/virtualization/cluster/devices.html b/netbox/templates/virtualization/cluster/devices.html deleted file mode 100644 index a71fab8b5..000000000 --- a/netbox/templates/virtualization/cluster/devices.html +++ /dev/null @@ -1,13 +0,0 @@ -{% extends 'generic/object_children.html' %} -{% load i18n %} - -{% block bulk_delete_controls %} - {{ block.super }} - {% if 'bulk_remove_devices' in actions %} - - {% endif %} -{% endblock bulk_delete_controls %} diff --git a/netbox/templates/virtualization/virtualmachine/interfaces.html b/netbox/templates/virtualization/virtualmachine/interfaces.html deleted file mode 100644 index f74bd9770..000000000 --- a/netbox/templates/virtualization/virtualmachine/interfaces.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends 'generic/object_children.html' %} -{% load helpers %} -{% load i18n %} - -{% block bulk_edit_controls %} - {{ block.super }} - {% if 'bulk_rename' in actions %} - - {% endif %} -{% endblock bulk_edit_controls %} diff --git a/netbox/templates/virtualization/virtualmachine/virtual_disks.html b/netbox/templates/virtualization/virtualmachine/virtual_disks.html deleted file mode 100644 index 2637d851a..000000000 --- a/netbox/templates/virtualization/virtualmachine/virtual_disks.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends 'generic/object_children.html' %} -{% load helpers %} -{% load i18n %} - -{% block bulk_edit_controls %} - {{ block.super }} - {% if 'bulk_rename' in actions %} - - {% endif %} -{% endblock bulk_edit_controls %} diff --git a/netbox/templates/virtualization/virtualmachine_list.html b/netbox/templates/virtualization/virtualmachine_list.html deleted file mode 100644 index 63fbdc2b7..000000000 --- a/netbox/templates/virtualization/virtualmachine_list.html +++ /dev/null @@ -1,29 +0,0 @@ -{% extends 'generic/object_list.html' %} -{% load i18n %} - -{% block bulk_buttons %} - {% if perms.virtualization.change_virtualmachine %} - - {% endif %} - {{ block.super }} -{% endblock %} diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index aca0a14eb..83794f21f 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -1,6 +1,7 @@ from django.contrib.contenttypes.models import ContentType from django.shortcuts import get_object_or_404 +from netbox.object_actions import BulkDelete, BulkEdit, BulkExport, BulkImport from netbox.views import generic from utilities.query import count_related from utilities.views import GetRelatedModelsMixin, register_model_view @@ -349,12 +350,7 @@ class ContactAssignmentListView(generic.ObjectListView): filterset = filtersets.ContactAssignmentFilterSet filterset_form = forms.ContactAssignmentFilterForm table = tables.ContactAssignmentTable - actions = { - 'export': {'view'}, - 'bulk_import': {'add'}, - 'bulk_edit': {'change'}, - 'bulk_delete': {'delete'}, - } + actions = (BulkExport, BulkImport, BulkEdit, BulkDelete) @register_model_view(ContactAssignment, 'add', detail=False) diff --git a/netbox/utilities/templates/buttons/add.html b/netbox/utilities/templates/buttons/add.html index df257b5d2..cf14769d3 100644 --- a/netbox/utilities/templates/buttons/add.html +++ b/netbox/utilities/templates/buttons/add.html @@ -1,6 +1,3 @@ -{% if url %} -{% load i18n %} - - {% trans "Add" %} - -{% endif %} + + {{ label }} + diff --git a/netbox/utilities/templates/buttons/bulk_delete.html b/netbox/utilities/templates/buttons/bulk_delete.html index a4860ce2d..42dd7ce30 100644 --- a/netbox/utilities/templates/buttons/bulk_delete.html +++ b/netbox/utilities/templates/buttons/bulk_delete.html @@ -1,6 +1,3 @@ -{% load i18n %} -{% if url %} - -{% endif %} + diff --git a/netbox/utilities/templates/buttons/bulk_edit.html b/netbox/utilities/templates/buttons/bulk_edit.html index a5aa03112..bc50d9b6e 100644 --- a/netbox/utilities/templates/buttons/bulk_edit.html +++ b/netbox/utilities/templates/buttons/bulk_edit.html @@ -1,6 +1,3 @@ -{% load i18n %} -{% if url %} - -{% endif %} + diff --git a/netbox/utilities/templates/buttons/bulk_rename.html b/netbox/utilities/templates/buttons/bulk_rename.html new file mode 100644 index 000000000..376faa88b --- /dev/null +++ b/netbox/utilities/templates/buttons/bulk_rename.html @@ -0,0 +1,3 @@ + diff --git a/netbox/utilities/templates/buttons/delete.html b/netbox/utilities/templates/buttons/delete.html index e367cfed1..768f605d0 100644 --- a/netbox/utilities/templates/buttons/delete.html +++ b/netbox/utilities/templates/buttons/delete.html @@ -1,12 +1,12 @@ -{% load i18n %} - {% trans "Delete" %} + {{ label }} diff --git a/netbox/utilities/templates/buttons/edit.html b/netbox/utilities/templates/buttons/edit.html index 9dc9a0b46..9890e5952 100644 --- a/netbox/utilities/templates/buttons/edit.html +++ b/netbox/utilities/templates/buttons/edit.html @@ -1,4 +1,3 @@ -{% load i18n %} - {% trans "Edit" %} + {{ label }} diff --git a/netbox/utilities/templates/buttons/export.html b/netbox/utilities/templates/buttons/export.html index 279757236..af388ca53 100644 --- a/netbox/utilities/templates/buttons/export.html +++ b/netbox/utilities/templates/buttons/export.html @@ -1,7 +1,7 @@ {% load i18n %}