Merge pull request #9630 from netbox-community/9092-ObjectChildrenView

Closes #9092: Introduce support for use of ObjectChildrenView by plugins
This commit is contained in:
Jeremy Stretch 2022-06-29 14:49:17 -04:00 committed by GitHub
commit 2245f1bf41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 122 additions and 92 deletions

View File

@ -52,10 +52,11 @@ This makes our view accessible at the URL `/plugins/animal-sounds/random/`. (Rem
NetBox provides several generic view classes (documented below) to facilitate common operations, such as creating, viewing, modifying, and deleting objects. Plugins can subclass these views for their own use. NetBox provides several generic view classes (documented below) to facilitate common operations, such as creating, viewing, modifying, and deleting objects. Plugins can subclass these views for their own use.
| View Class | Description | | View Class | Description |
|--------------------|--------------------------------| |----------------------|--------------------------------------------------------|
| `ObjectView` | View a single object | | `ObjectView` | View a single object |
| `ObjectEditView` | Create or edit a single object | | `ObjectEditView` | Create or edit a single object |
| `ObjectDeleteView` | Delete a single object | | `ObjectDeleteView` | Delete a single object |
| `ObjectChildrenView` | A list of child objects within the context of a parent |
| `ObjectListView` | View a list of objects | | `ObjectListView` | View a list of objects |
| `BulkImportView` | Import a set of new objects | | `BulkImportView` | Import a set of new objects |
| `BulkEditView` | Edit multiple objects | | `BulkEditView` | Edit multiple objects |
@ -99,6 +100,12 @@ Below are the class definitions for NetBox's object views. These views handle CR
members: members:
- get_object - get_object
::: netbox.views.generic.ObjectChildrenView
selection:
members:
- get_children
- prep_table_data
## Multi-Object Views ## Multi-Object Views
Below are the class definitions for NetBox's multi-object views. These views handle simultaneous actions for sets objects. The list, import, edit, and delete views each inherit from `BaseMultiObjectView`, which is not intended to be used directly. Below are the class definitions for NetBox's multi-object views. These views handle simultaneous actions for sets objects. The list, import, edit, and delete views each inherit from `BaseMultiObjectView`, which is not intended to be used directly.

View File

@ -34,6 +34,7 @@
### Plugins API ### Plugins API
* [#9092](https://github.com/netbox-community/netbox/issues/9092) - Add support for `ObjectChildrenView` generic view
* [#9414](https://github.com/netbox-community/netbox/issues/9414) - Add `clone()` method to NetBoxModel for copying instance attributes * [#9414](https://github.com/netbox-community/netbox/issues/9414) - Add `clone()` method to NetBoxModel for copying instance attributes
### Other Changes ### Other Changes

View File

@ -24,6 +24,7 @@ from utilities.htmx import is_htmx
from utilities.permissions import get_permission_for_model from utilities.permissions import get_permission_for_model
from utilities.views import GetReturnURLMixin from utilities.views import GetReturnURLMixin
from .base import BaseMultiObjectView from .base import BaseMultiObjectView
from .mixins import ActionsMixin, TableMixin
__all__ = ( __all__ = (
'BulkComponentCreateView', 'BulkComponentCreateView',
@ -36,9 +37,9 @@ __all__ = (
) )
class ObjectListView(BaseMultiObjectView): class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin):
""" """
Display multiple objects, all of the same type, as a table. Display multiple objects, all the same type, as a table.
Attributes: Attributes:
filterset: A django-filter FilterSet that is applied to the queryset filterset: A django-filter FilterSet that is applied to the queryset
@ -50,31 +51,10 @@ class ObjectListView(BaseMultiObjectView):
template_name = 'generic/object_list.html' template_name = 'generic/object_list.html'
filterset = None filterset = None
filterset_form = None filterset_form = None
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete')
action_perms = defaultdict(set, **{
'add': {'add'},
'import': {'add'},
'bulk_edit': {'change'},
'bulk_delete': {'delete'},
})
def get_required_permission(self): def get_required_permission(self):
return get_permission_for_model(self.queryset.model, 'view') 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 # Export methods
# #
@ -147,19 +127,14 @@ class ObjectListView(BaseMultiObjectView):
self.queryset = self.filterset(request.GET, self.queryset).qs self.queryset = self.filterset(request.GET, self.queryset).qs
# Determine the available actions # Determine the available actions
actions = [] actions = self.get_permitted_actions(request.user)
for action in self.actions:
if request.user.has_perms([
get_permission_for_model(model, name) for name in self.action_perms[action]
]):
actions.append(action)
has_bulk_actions = any([a.startswith('bulk_') for a in actions]) has_bulk_actions = any([a.startswith('bulk_') for a in actions])
if 'export' in request.GET: if 'export' in request.GET:
# Export the current table view # Export the current table view
if request.GET['export'] == 'table': 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] columns = [name for name, _ in table.selected_columns]
return self.export_table(table, columns) return self.export_table(table, columns)
@ -177,12 +152,11 @@ class ObjectListView(BaseMultiObjectView):
# Fall back to default table/YAML export # Fall back to default table/YAML export
else: else:
table = self.get_table(request, has_bulk_actions) table = self.get_table(self.queryset, request, has_bulk_actions)
return self.export_table(table) return self.export_table(table)
# Render the objects table # Render the objects table
table = self.get_table(request, has_bulk_actions) table = self.get_table(self.queryset, request, has_bulk_actions)
table.configure(request)
# If this is an HTMX request, return only the rendered table HTML # If this is an HTMX request, return only the rendered table HTML
if is_htmx(request): if is_htmx(request):
@ -190,15 +164,13 @@ class ObjectListView(BaseMultiObjectView):
'table': table, 'table': table,
}) })
context = { return render(request, self.template_name, {
'model': model, 'model': model,
'table': table, 'table': table,
'actions': actions, 'actions': actions,
'filter_form': self.filterset_form(request.GET, label_suffix='') if self.filterset_form else None, 'filter_form': self.filterset_form(request.GET, label_suffix='') if self.filterset_form else None,
**self.get_extra_context(request), **self.get_extra_context(request),
} })
return render(request, self.template_name, context)
class BulkCreateView(GetReturnURLMixin, BaseMultiObjectView): class BulkCreateView(GetReturnURLMixin, BaseMultiObjectView):

View File

@ -0,0 +1,47 @@
from collections import defaultdict
from utilities.permissions import get_permission_for_model
__all__ = (
'TableMixin',
)
class ActionsMixin:
actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete')
action_perms = defaultdict(set, **{
'add': {'add'},
'import': {'add'},
'bulk_edit': {'change'},
'bulk_delete': {'delete'},
})
def get_permitted_actions(self, user, model=None):
"""
Return a tuple of actions for which the given user is permitted to do.
"""
model = model or self.queryset.model
return [
action for action in self.actions if user.has_perms([
get_permission_for_model(model, name) for name in self.action_perms[action]
])
]
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

View File

@ -20,6 +20,7 @@ from utilities.permissions import get_permission_for_model
from utilities.utils import get_viewname, normalize_querydict, prepare_cloned_fields from utilities.utils import get_viewname, normalize_querydict, prepare_cloned_fields
from utilities.views import GetReturnURLMixin from utilities.views import GetReturnURLMixin
from .base import BaseObjectView from .base import BaseObjectView
from .mixins import ActionsMixin, TableMixin
__all__ = ( __all__ = (
'ComponentCreateView', 'ComponentCreateView',
@ -69,12 +70,17 @@ class ObjectView(BaseObjectView):
}) })
class ObjectChildrenView(ObjectView): class ObjectChildrenView(ObjectView, ActionsMixin, TableMixin):
""" """
Display a table of child objects associated with the parent object. Display a table of child objects associated with the parent object.
Attributes: 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
actions: Supported actions for the model. When adding custom actions, bulk action names must
be prefixed with `bulk_`. Default actions: add, import, export, bulk_edit, bulk_delete
action_perms: A dictionary mapping supported actions to a set of permissions required for each
""" """
child_model = None child_model = None
table = None table = None
@ -84,6 +90,7 @@ class ObjectChildrenView(ObjectView):
""" """
Return a QuerySet of child objects. Return a QuerySet of child objects.
Args:
request: The current request request: The current request
parent: The parent object parent: The parent object
""" """
@ -114,16 +121,11 @@ class ObjectChildrenView(ObjectView):
if self.filterset: if self.filterset:
child_objects = self.filterset(request.GET, child_objects).qs child_objects = self.filterset(request.GET, child_objects).qs
permissions = {} # Determine the available actions
for action in ('change', 'delete'): actions = self.get_permitted_actions(request.user, model=self.child_model)
perm_name = get_permission_for_model(self.child_model, action)
permissions[action] = request.user.has_perm(perm_name)
table = self.table(self.prep_table_data(request, child_objects, instance), user=request.user) table_data = self.prep_table_data(request, child_objects, instance)
# Determine whether to display bulk action checkboxes table = self.get_table(table_data, request, bool(actions))
if 'pk' in table.base_columns and (permissions['change'] or permissions['delete']):
table.columns.show('pk')
table.configure(request)
# If this is an HTMX request, return only the rendered table HTML # If this is an HTMX request, return only the rendered table HTML
if is_htmx(request): if is_htmx(request):
@ -134,8 +136,9 @@ class ObjectChildrenView(ObjectView):
return render(request, self.get_template_name(), { return render(request, self.get_template_name(), {
'object': instance, 'object': instance,
'child_model': self.child_model,
'table': table, 'table': table,
'permissions': permissions, 'actions': actions,
**self.get_extra_context(request, instance), **self.get_extra_context(request, instance),
}) })

View File

@ -17,7 +17,7 @@
<div class="noprint bulk-buttons"> <div class="noprint bulk-buttons">
<div class="bulk-button-group"> <div class="bulk-button-group">
{% if perms.dcim.change_consoleport %} {% if 'bulk_edit' in actions %}
<button type="submit" name="_rename" formaction="{% url 'dcim:consoleport_bulk_rename' %}?return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-outline-warning btn-sm"> <button type="submit" name="_rename" formaction="{% url 'dcim:consoleport_bulk_rename' %}?return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button> </button>
@ -28,7 +28,7 @@
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect <span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
</button> </button>
{% endif %} {% endif %}
{% if perms.dcim.delete_consoleport %} {% if 'bulk_delete' in actions %}
<button type="submit" name="_delete" formaction="{% url 'dcim:consoleport_bulk_delete' %}?return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-danger btn-sm"> <button type="submit" name="_delete" formaction="{% url 'dcim:consoleport_bulk_delete' %}?return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button> </button>

View File

@ -17,7 +17,7 @@
<div class="noprint bulk-buttons"> <div class="noprint bulk-buttons">
<div class="bulk-button-group"> <div class="bulk-button-group">
{% if perms.dcim.change_consoleserverport %} {% if 'bulk_edit' in actions %}
<button type="submit" name="_rename" formaction="{% url 'dcim:consoleserverport_bulk_rename' %}?return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-outline-warning btn-sm"> <button type="submit" name="_rename" formaction="{% url 'dcim:consoleserverport_bulk_rename' %}?return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button> </button>
@ -28,7 +28,7 @@
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect <span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
</button> </button>
{% endif %} {% endif %}
{% if perms.dcim.delete_consoleserverport %} {% if 'bulk_delete' in actions %}
<button type="submit" formaction="{% url 'dcim:consoleserverport_bulk_delete' %}?return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-danger btn-sm"> <button type="submit" formaction="{% url 'dcim:consoleserverport_bulk_delete' %}?return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button> </button>

View File

@ -17,7 +17,7 @@
<div class="noprint bulk-buttons"> <div class="noprint bulk-buttons">
<div class="bulk-button-group"> <div class="bulk-button-group">
{% if perms.dcim.change_devicebay %} {% if 'bulk_edit' in actions %}
<button type="submit" name="_rename" formaction="{% url 'dcim:devicebay_bulk_rename' %}?return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-outline-warning btn-sm"> <button type="submit" name="_rename" formaction="{% url 'dcim:devicebay_bulk_rename' %}?return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button> </button>
@ -25,7 +25,7 @@
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
</button> </button>
{% endif %} {% endif %}
{% if perms.dcim.delete_devicebay %} {% if 'bulk_delete' in actions %}
<button type="submit" formaction="{% url 'dcim:devicebay_bulk_delete' %}?return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-outline-danger btn-sm"> <button type="submit" formaction="{% url 'dcim:devicebay_bulk_delete' %}?return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete selected <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete selected
</button> </button>

View File

@ -17,7 +17,7 @@
<div class="noprint bulk-buttons"> <div class="noprint bulk-buttons">
<div class="bulk-button-group"> <div class="bulk-button-group">
{% if perms.dcim.change_frontport %} {% if 'bulk_edit' in actions %}
<button type="submit" name="_rename" formaction="{% url 'dcim:frontport_bulk_rename' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-outline-warning btn-sm"> <button type="submit" name="_rename" formaction="{% url 'dcim:frontport_bulk_rename' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button> </button>
@ -28,7 +28,7 @@
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect <span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
</button> </button>
{% endif %} {% endif %}
{% if perms.dcim.delete_frontport %} {% if 'bulk_delete' in actions %}
<button type="submit" formaction="{% url 'dcim:frontport_bulk_delete' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-danger btn-sm"> <button type="submit" formaction="{% url 'dcim:frontport_bulk_delete' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button> </button>

View File

@ -53,7 +53,7 @@
<div class="noprint bulk-buttons"> <div class="noprint bulk-buttons">
<div class="bulk-button-group"> <div class="bulk-button-group">
{% if perms.dcim.change_interface %} {% if 'bulk_edit' in actions %}
<button type="submit" name="_rename" formaction="{% url 'dcim:interface_bulk_rename' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}" class="btn btn-outline-warning btn-sm"> <button type="submit" name="_rename" formaction="{% url 'dcim:interface_bulk_rename' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button> </button>
@ -64,7 +64,7 @@
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect <span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
</button> </button>
{% endif %} {% endif %}
{% if perms.dcim.delete_interface %} {% if 'bulk_delete' in actions %}
<button type="submit" name="_delete" formaction="{% url 'dcim:interface_bulk_delete' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}" class="btn btn-danger btn-sm"> <button type="submit" name="_delete" formaction="{% url 'dcim:interface_bulk_delete' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button> </button>

View File

@ -17,7 +17,7 @@
<div class="noprint bulk-buttons"> <div class="noprint bulk-buttons">
<div class="bulk-button-group"> <div class="bulk-button-group">
{% if perms.dcim.change_inventoryitem %} {% if 'bulk_edit' in actions %}
<button type="submit" name="_rename" formaction="{% url 'dcim:inventoryitem_bulk_rename' %}?return_url={% url 'dcim:device_inventory' pk=object.pk %}" class="btn btn-warning btn-sm"> <button type="submit" name="_rename" formaction="{% url 'dcim:inventoryitem_bulk_rename' %}?return_url={% url 'dcim:device_inventory' pk=object.pk %}" class="btn btn-warning btn-sm">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button> </button>
@ -25,7 +25,7 @@
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
</button> </button>
{% endif %} {% endif %}
{% if perms.dcim.delete_inventoryitem %} {% if 'bulk_delete' in actions %}
<button type="submit" name="_delete" formaction="{% url 'dcim:inventoryitem_bulk_delete' %}?return_url={% url 'dcim:device_inventory' pk=object.pk %}" class="btn btn-danger btn-sm"> <button type="submit" name="_delete" formaction="{% url 'dcim:inventoryitem_bulk_delete' %}?return_url={% url 'dcim:device_inventory' pk=object.pk %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button> </button>

View File

@ -17,7 +17,7 @@
<div class="noprint bulk-buttons"> <div class="noprint bulk-buttons">
<div class="bulk-button-group"> <div class="bulk-button-group">
{% if perms.dcim.change_modulebay %} {% if 'bulk_edit' in actions %}
<button type="submit" name="_rename" formaction="{% url 'dcim:modulebay_bulk_rename' %}?return_url={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-outline-warning btn-sm"> <button type="submit" name="_rename" formaction="{% url 'dcim:modulebay_bulk_rename' %}?return_url={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button> </button>
@ -25,7 +25,7 @@
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
</button> </button>
{% endif %} {% endif %}
{% if perms.dcim.delete_modulebay %} {% if 'bulk_delete' in actions %}
<button type="submit" formaction="{% url 'dcim:modulebay_bulk_delete' %}?return_url={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-outline-danger btn-sm"> <button type="submit" formaction="{% url 'dcim:modulebay_bulk_delete' %}?return_url={% url 'dcim:device_modulebays' pk=object.pk %}" class="btn btn-outline-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete selected <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete selected
</button> </button>

View File

@ -17,7 +17,7 @@
<div class="noprint bulk-buttons"> <div class="noprint bulk-buttons">
<div class="bulk-button-group"> <div class="bulk-button-group">
{% if perms.dcim.change_powerport %} {% if 'bulk_edit' in actions %}
<button type="submit" name="_rename" formaction="{% url 'dcim:poweroutlet_bulk_rename' %}?return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-outline-warning btn-sm"> <button type="submit" name="_rename" formaction="{% url 'dcim:poweroutlet_bulk_rename' %}?return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button> </button>
@ -28,7 +28,7 @@
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect <span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
</button> </button>
{% endif %} {% endif %}
{% if perms.dcim.delete_poweroutlet %} {% if 'bulk_delete' in actions %}
<button type="submit" formaction="{% url 'dcim:poweroutlet_bulk_delete' %}?return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-danger btn-sm"> <button type="submit" formaction="{% url 'dcim:poweroutlet_bulk_delete' %}?return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button> </button>

View File

@ -17,7 +17,7 @@
<div class="noprint bulk-buttons"> <div class="noprint bulk-buttons">
<div class="bulk-button-group"> <div class="bulk-button-group">
{% if perms.dcim.change_powerport %} {% if 'bulk_edit' in actions %}
<button type="submit" name="_rename" formaction="{% url 'dcim:powerport_bulk_rename' %}?return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-outline-warning btn-sm"> <button type="submit" name="_rename" formaction="{% url 'dcim:powerport_bulk_rename' %}?return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button> </button>
@ -28,7 +28,7 @@
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect <span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
</button> </button>
{% endif %} {% endif %}
{% if perms.dcim.delete_powerport %} {% if 'bulk_delete' in actions %}
<button type="submit" name="_delete" formaction="{% url 'dcim:powerport_bulk_delete' %}?return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-danger btn-sm"> <button type="submit" name="_delete" formaction="{% url 'dcim:powerport_bulk_delete' %}?return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button> </button>

View File

@ -17,7 +17,7 @@
<div class="noprint bulk-buttons"> <div class="noprint bulk-buttons">
<div class="bulk-button-group"> <div class="bulk-button-group">
{% if perms.dcim.change_rearport %} {% if 'bulk_edit' in actions %}
<button type="submit" name="_rename" formaction="{% url 'dcim:rearport_bulk_rename' %}?return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-outline-warning btn-sm"> <button type="submit" name="_rename" formaction="{% url 'dcim:rearport_bulk_rename' %}?return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-outline-warning btn-sm">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button> </button>
@ -28,7 +28,7 @@
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect <span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
</button> </button>
{% endif %} {% endif %}
{% if perms.dcim.delete_rearport %} {% if 'bulk_delete' in actions %}
<button type="submit" formaction="{% url 'dcim:rearport_bulk_delete' %}?return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-danger btn-sm"> <button type="submit" formaction="{% url 'dcim:rearport_bulk_delete' %}?return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button> </button>

View File

@ -25,12 +25,12 @@
<div class="noprint bulk-buttons"> <div class="noprint bulk-buttons">
<div class="bulk-button-group"> <div class="bulk-button-group">
{% if perms.ipam.change_prefix %} {% if 'bulk_edit' in actions %}
<button type="submit" name="_edit" formaction="{% url 'ipam:prefix_bulk_edit' %}?return_url={% url 'ipam:aggregate_prefixes' pk=object.pk %}" class="btn btn-warning btn-sm"> <button type="submit" name="_edit" formaction="{% url 'ipam:prefix_bulk_edit' %}?return_url={% url 'ipam:aggregate_prefixes' pk=object.pk %}" class="btn btn-warning btn-sm">
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
</button> </button>
{% endif %} {% endif %}
{% if perms.ipam.delete_prefix %} {% if 'bulk_delete' in actions %}
<button type="submit" name="_delete" formaction="{% url 'ipam:prefix_bulk_delete' %}?return_url={% url 'ipam:aggregate_prefixes' pk=object.pk %}" class="btn btn-danger btn-sm"> <button type="submit" name="_delete" formaction="{% url 'ipam:prefix_bulk_delete' %}?return_url={% url 'ipam:aggregate_prefixes' pk=object.pk %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button> </button>

View File

@ -23,12 +23,12 @@
<div class="noprint bulk-buttons"> <div class="noprint bulk-buttons">
<div class="bulk-button-group"> <div class="bulk-button-group">
{% if perms.ipam.change_ipaddress %} {% if 'bulk_edit' in actions %}
<button type="submit" name="_edit" formaction="{% url 'ipam:ipaddress_bulk_edit' %}?return_url={% url 'ipam:iprange_ipaddresses' pk=object.pk %}" class="btn btn-warning btn-sm"> <button type="submit" name="_edit" formaction="{% url 'ipam:ipaddress_bulk_edit' %}?return_url={% url 'ipam:iprange_ipaddresses' pk=object.pk %}" class="btn btn-warning btn-sm">
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
</button> </button>
{% endif %} {% endif %}
{% if perms.ipam.delete_ipaddress %} {% if 'bulk_delete' in actions %}
<button type="submit" name="_delete" formaction="{% url 'ipam:ipaddress_bulk_delete' %}?return_url={% url 'ipam:iprange_ipaddresses' pk=object.pk %}" class="btn btn-danger btn-sm"> <button type="submit" name="_delete" formaction="{% url 'ipam:ipaddress_bulk_delete' %}?return_url={% url 'ipam:iprange_ipaddresses' pk=object.pk %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button> </button>

View File

@ -23,12 +23,12 @@
<div class="noprint bulk-buttons"> <div class="noprint bulk-buttons">
<div class="bulk-button-group"> <div class="bulk-button-group">
{% if perms.ipam.change_ipaddress %} {% if 'bulk_edit' in actions %}
<button type="submit" name="_edit" formaction="{% url 'ipam:ipaddress_bulk_edit' %}?return_url={% url 'ipam:prefix_ipaddresses' pk=object.pk %}" class="btn btn-warning btn-sm"> <button type="submit" name="_edit" formaction="{% url 'ipam:ipaddress_bulk_edit' %}?return_url={% url 'ipam:prefix_ipaddresses' pk=object.pk %}" class="btn btn-warning btn-sm">
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
</button> </button>
{% endif %} {% endif %}
{% if perms.ipam.delete_ipaddress %} {% if 'bulk_delete' in actions %}
<button type="submit" name="_delete" formaction="{% url 'ipam:ipaddress_bulk_delete' %}?return_url={% url 'ipam:prefix_ipaddresses' pk=object.pk %}" class="btn btn-danger btn-sm"> <button type="submit" name="_delete" formaction="{% url 'ipam:ipaddress_bulk_delete' %}?return_url={% url 'ipam:prefix_ipaddresses' pk=object.pk %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button> </button>

View File

@ -23,12 +23,12 @@
<div class="noprint bulk-buttons"> <div class="noprint bulk-buttons">
<div class="bulk-button-group"> <div class="bulk-button-group">
{% if perms.ipam.change_iprange %} {% if 'bulk_edit' in actions %}
<button type="submit" name="_edit" formaction="{% url 'ipam:iprange_bulk_edit' %}?return_url={% url 'ipam:prefix_ipranges' pk=object.pk %}" class="btn btn-warning btn-sm"> <button type="submit" name="_edit" formaction="{% url 'ipam:iprange_bulk_edit' %}?return_url={% url 'ipam:prefix_ipranges' pk=object.pk %}" class="btn btn-warning btn-sm">
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
</button> </button>
{% endif %} {% endif %}
{% if perms.ipam.delete_iprange %} {% if 'bulk_delete' in actions %}
<button type="submit" name="_delete" formaction="{% url 'ipam:iprange_bulk_delete' %}?return_url={% url 'ipam:prefix_ipranges' pk=object.pk %}" class="btn btn-danger btn-sm"> <button type="submit" name="_delete" formaction="{% url 'ipam:iprange_bulk_delete' %}?return_url={% url 'ipam:prefix_ipranges' pk=object.pk %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button> </button>

View File

@ -25,12 +25,12 @@
<div class="noprint bulk-buttons"> <div class="noprint bulk-buttons">
<div class="bulk-button-group"> <div class="bulk-button-group">
{% if perms.ipam.change_prefix %} {% if 'bulk_edit' in actions %}
<button type="submit" name="_edit" formaction="{% url 'ipam:prefix_bulk_edit' %}?return_url={% url 'ipam:prefix_prefixes' pk=object.pk %}" class="btn btn-warning btn-sm"> <button type="submit" name="_edit" formaction="{% url 'ipam:prefix_bulk_edit' %}?return_url={% url 'ipam:prefix_prefixes' pk=object.pk %}" class="btn btn-warning btn-sm">
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
</button> </button>
{% endif %} {% endif %}
{% if perms.ipam.delete_prefix %} {% if 'bulk_delete' in actions %}
<button type="submit" name="_delete" formaction="{% url 'ipam:prefix_bulk_delete' %}?return_url={% url 'ipam:prefix_prefixes' pk=object.pk %}" class="btn btn-danger btn-sm"> <button type="submit" name="_delete" formaction="{% url 'ipam:prefix_bulk_delete' %}?return_url={% url 'ipam:prefix_prefixes' pk=object.pk %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button> </button>

View File

@ -14,12 +14,12 @@
</div> </div>
<div class="noprint bulk-buttons"> <div class="noprint bulk-buttons">
<div class="bulk-button-group"> <div class="bulk-button-group">
{% if perms.virtualization.change_virtualmachine %} {% if 'bulk_edit' in actions %}
<button type="submit" name="_edit" formaction="{% url 'virtualization:virtualmachine_bulk_edit' %}?return_url={% url 'virtualization:cluster_virtualmachines' pk=object.pk %}" class="btn btn-warning btn-sm"> <button type="submit" name="_edit" formaction="{% url 'virtualization:virtualmachine_bulk_edit' %}?return_url={% url 'virtualization:cluster_virtualmachines' pk=object.pk %}" class="btn btn-warning btn-sm">
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
</button> </button>
{% endif %} {% endif %}
{% if perms.virtualization.delete_virtualmachine %} {% if 'bulk_delete' in actions %}
<button type="submit" name="_delete" formaction="{% url 'virtualization:virtualmachine_bulk_delete' %}?return_url={% url 'virtualization:cluster_virtualmachines' pk=object.pk %}" class="btn btn-danger btn-sm"> <button type="submit" name="_delete" formaction="{% url 'virtualization:virtualmachine_bulk_delete' %}?return_url={% url 'virtualization:cluster_virtualmachines' pk=object.pk %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button> </button>