mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-22 20:12:00 -06:00
Initial work on #19735
This commit is contained in:
parent
71e6ea5785
commit
c438c13045
123
netbox/netbox/object_actions.py
Normal file
123
netbox/netbox/object_actions.py
Normal file
@ -0,0 +1,123 @@
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from core.models import ObjectType
|
||||
from extras.models import ExportTemplate
|
||||
|
||||
__all__ = (
|
||||
'Add',
|
||||
'BulkDelete',
|
||||
'BulkEdit',
|
||||
'BulkExport',
|
||||
'BulkImport',
|
||||
'Delete',
|
||||
'Edit',
|
||||
'ObjectAction',
|
||||
)
|
||||
|
||||
|
||||
class ObjectAction:
|
||||
name = ''
|
||||
label = None
|
||||
bulk = False
|
||||
permissions_required = set()
|
||||
url_kwargs = []
|
||||
|
||||
def get_context(self, context, obj):
|
||||
viewname = f'{obj._meta.app_label}:{obj._meta.model_name}_{self.name}'
|
||||
url = reverse(viewname, kwargs={kwarg: getattr(obj, kwarg) for kwarg in self.url_kwargs})
|
||||
return {
|
||||
'url': url,
|
||||
}
|
||||
|
||||
|
||||
class Add(ObjectAction):
|
||||
"""
|
||||
Create a new object.
|
||||
"""
|
||||
name = 'add'
|
||||
label = _('Add')
|
||||
permissions_required = {'add'}
|
||||
template_name = 'buttons/add.html'
|
||||
|
||||
|
||||
class Edit(ObjectAction):
|
||||
"""
|
||||
Edit a single object.
|
||||
"""
|
||||
name = 'edit'
|
||||
label = _('Edit')
|
||||
permissions_required = {'change'}
|
||||
url_kwargs = ['pk']
|
||||
template_name = 'buttons/edit.html'
|
||||
|
||||
|
||||
class Delete(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'
|
||||
|
||||
def get_context(self, 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 {
|
||||
'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')
|
||||
bulk = True
|
||||
permissions_required = {'change'}
|
||||
template_name = 'buttons/bulk_edit.html'
|
||||
|
||||
|
||||
class BulkDelete(ObjectAction):
|
||||
"""
|
||||
Delete each of a set of objects.
|
||||
"""
|
||||
name = 'bulk_delete'
|
||||
label = _('Delete')
|
||||
bulk = True
|
||||
permissions_required = {'delete'}
|
||||
template_name = 'buttons/bulk_delete.html'
|
@ -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 Add, 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 = (Add, BulkImport, BulkEdit, BulkExport, BulkDelete)
|
||||
|
||||
def get_required_permission(self):
|
||||
return get_permission_for_model(self.queryset.model, 'view')
|
||||
@ -150,7 +152,8 @@ 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_bulk_actions = any([a.startswith('bulk_') for a in actions])
|
||||
has_bulk_actions = True
|
||||
|
||||
if 'export' in request.GET:
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from extras.models import TableConfig
|
||||
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
|
||||
from utilities.permissions import get_permission_for_model
|
||||
|
||||
__all__ = (
|
||||
@ -19,7 +18,7 @@ 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 = DEFAULT_ACTION_PERMISSIONS
|
||||
|
||||
def get_permitted_actions(self, user, model=None):
|
||||
"""
|
||||
@ -30,13 +29,16 @@ class ActionsMixin:
|
||||
# Resolve required permissions for each action
|
||||
permitted_actions = []
|
||||
for action in self.actions:
|
||||
perms = action if type(action) is str else action.permissions_required # Backward compatibility
|
||||
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 perms
|
||||
]
|
||||
if not required_permissions or user.has_perms(required_permissions):
|
||||
permitted_actions.append(action)
|
||||
|
||||
return permitted_actions
|
||||
return {
|
||||
action.name: action for action in permitted_actions
|
||||
}
|
||||
|
||||
|
||||
class TableMixin:
|
||||
|
@ -14,6 +14,7 @@ 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 Add, BulkDelete, BulkEdit, BulkExport, BulkImport, Delete, Edit
|
||||
from utilities.error_handlers import handle_protectederror
|
||||
from utilities.exceptions import AbortRequest, PermissionsViolation
|
||||
from utilities.forms import ConfirmationForm, restrict_form_fields
|
||||
@ -36,7 +37,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class ObjectView(BaseObjectView):
|
||||
class ObjectView(ActionsMixin, BaseObjectView):
|
||||
"""
|
||||
Retrieve a single object for display.
|
||||
|
||||
@ -46,6 +47,7 @@ class ObjectView(BaseObjectView):
|
||||
tab: A ViewTab instance for the view
|
||||
"""
|
||||
tab = None
|
||||
actions = (Edit, Delete)
|
||||
|
||||
def get_required_permission(self):
|
||||
return get_permission_for_model(self.queryset.model, 'view')
|
||||
@ -72,9 +74,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 +101,7 @@ class ObjectChildrenView(ObjectView, ActionsMixin, TableMixin):
|
||||
table = None
|
||||
filterset = None
|
||||
filterset_form = None
|
||||
actions = (Add, BulkImport, BulkEdit, BulkExport, BulkDelete)
|
||||
template_name = 'generic/object_children.html'
|
||||
|
||||
def get_children(self, request, parent):
|
||||
|
@ -80,15 +80,9 @@ 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 %}
|
||||
{% for name, action in actions.items %}
|
||||
{% action_button action object %}
|
||||
{% endfor %}
|
||||
{% endblock control-buttons %}
|
||||
</div>
|
||||
|
||||
|
@ -31,15 +31,11 @@ Context:
|
||||
<div class="btn-list">
|
||||
{% 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 %}
|
||||
{% for name, action in actions.items %}
|
||||
{% if not action.bulk %}
|
||||
{% action_button action model %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock controls %}
|
||||
|
||||
@ -91,12 +87,11 @@ Context:
|
||||
</label>
|
||||
</div>
|
||||
<div class="bulk-action-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 %}
|
||||
{% for name, action in actions.items %}
|
||||
{% if action.bulk %}
|
||||
{% bulk_action_button action model %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -124,12 +119,11 @@ Context:
|
||||
<div class="btn-list d-print-none">
|
||||
{% block bulk_buttons %}
|
||||
<div class="bulk-action-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 %}
|
||||
{% for name, action in actions.items %}
|
||||
{% if action.bulk %}
|
||||
{% bulk_action_button action model %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
@ -1,5 +1,6 @@
|
||||
from django import template
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.template import loader
|
||||
from django.urls import NoReverseMatch, reverse
|
||||
|
||||
from core.models import ObjectType
|
||||
@ -9,8 +10,10 @@ from utilities.querydict import prepare_cloned_fields
|
||||
from utilities.views import get_viewname
|
||||
|
||||
__all__ = (
|
||||
'action_button',
|
||||
'add_button',
|
||||
'bookmark_button',
|
||||
'bulk_action_button',
|
||||
'bulk_delete_button',
|
||||
'bulk_edit_button',
|
||||
'clone_button',
|
||||
@ -217,3 +220,13 @@ def bulk_delete_button(context, model, action='bulk_delete', query_params=None):
|
||||
'htmx_navigation': context.get('htmx_navigation'),
|
||||
'url': url,
|
||||
}
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def action_button(context, action, obj):
|
||||
return loader.render_to_string(action.template_name, action.get_context(context, obj))
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def bulk_action_button(context, action, model):
|
||||
return loader.render_to_string(action.template_name, action.get_context(context, model))
|
||||
|
Loading…
Reference in New Issue
Block a user