diff --git a/docs/plugins/development/generic-views.md b/docs/plugins/development/generic-views.md index ced7e3807..d8ad0e7a4 100644 --- a/docs/plugins/development/generic-views.md +++ b/docs/plugins/development/generic-views.md @@ -12,6 +12,9 @@ NetBox provides several generic view classes to facilitate common operations, su | `BulkEditView` | Edit multiple objects | | `BulkDeleteView` | Delete multiple objects | +!!! note + Please note that only the classes which appear in this documentation are currently supported. Although other classes may be present within the `views.generic` module, they are not yet supported for use by plugins. + ### Example Usage ```python @@ -25,23 +28,19 @@ class ThingEditView(ObjectEditView): ... ``` -## Generic Views Reference +## Object Views -Below is the class definition for NetBox's base GenericView. The attributes and methods defined here are available on all generic views. +Below is the class definition for NetBox's BaseObjectView. The attributes and methods defined here are available on all generic views which handle a single object. -::: netbox.views.generic.base.GenericView +::: netbox.views.generic.base.BaseObjectView rendering: show_source: false -!!! note - Please note that only the classes which appear in this documentation are currently supported. Although other classes may be present within the `views.generic` module, they are not yet supported for use by plugins. - ::: netbox.views.generic.ObjectView selection: members: - get_object - get_template_name - - get_extra_context rendering: show_source: false @@ -60,11 +59,18 @@ Below is the class definition for NetBox's base GenericView. The attributes and rendering: show_source: false +## Multi-Object Views + +Below is the class definition for NetBox's BaseMultiObjectView. The attributes and methods defined here are available on all generic views which deal with multiple objects. + +::: netbox.views.generic.base.BaseMultiObjectView + rendering: + show_source: false + ::: netbox.views.generic.ObjectListView selection: members: - get_table - - get_extra_context - export_yaml - export_table - export_template diff --git a/netbox/netbox/views/generic/base.py b/netbox/netbox/views/generic/base.py index 3861a93aa..7d7c305dd 100644 --- a/netbox/netbox/views/generic/base.py +++ b/netbox/netbox/views/generic/base.py @@ -3,7 +3,7 @@ from django.views.generic import View from utilities.views import ObjectPermissionRequiredMixin -class GenericView(ObjectPermissionRequiredMixin, View): +class BaseObjectView(ObjectPermissionRequiredMixin, View): """ Base view class for reusable generic views. @@ -13,3 +13,34 @@ class GenericView(ObjectPermissionRequiredMixin, View): """ queryset = None template_name = None + + def get_extra_context(self, request, instance): + """ + Return any additional context data to include when rendering the template. + + Args: + request: The current request + instance: The object being viewed + """ + return {} + + +class BaseMultiObjectView(ObjectPermissionRequiredMixin, View): + """ + Base view class for reusable generic views. + + Attributes: + queryset: Django QuerySet from which the object(s) will be fetched + template_name: The name of the HTML template file to render + """ + queryset = None + template_name = None + + def get_extra_context(self, request): + """ + Return any additional context data to include when rendering the template. + + Args: + request: The current request + """ + return {} diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index c1ae2038e..3025818fa 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -23,7 +23,7 @@ from utilities.htmx import is_htmx from utilities.permissions import get_permission_for_model from utilities.tables import configure_table from utilities.views import GetReturnURLMixin -from .base import GenericView +from .base import BaseMultiObjectView __all__ = ( 'BulkComponentCreateView', @@ -36,7 +36,7 @@ __all__ = ( ) -class ObjectListView(GenericView): +class ObjectListView(BaseMultiObjectView): """ Display multiple objects, all of the same type, as a table. @@ -70,15 +70,6 @@ class ObjectListView(GenericView): return table - def get_extra_context(self, request): - """ - Return any additional context data for the template. - - Agrs: - request: The current request - """ - return {} - def get(self, request): """ GET request handler. @@ -139,8 +130,8 @@ class ObjectListView(GenericView): 'permissions': permissions, 'action_buttons': self.action_buttons, 'filter_form': self.filterset_form(request.GET, label_suffix='') if self.filterset_form else None, + **self.get_extra_context(request), } - context.update(self.get_extra_context(request)) return render(request, self.template_name, context) @@ -196,7 +187,7 @@ class ObjectListView(GenericView): return redirect(request.path) -class BulkCreateView(GetReturnURLMixin, GenericView): +class BulkCreateView(GetReturnURLMixin, BaseMultiObjectView): """ Create new objects in bulk. @@ -251,6 +242,7 @@ class BulkCreateView(GetReturnURLMixin, GenericView): 'form': form, 'model_form': model_form, 'return_url': self.get_return_url(request), + **self.get_extra_context(request), }) def post(self, request): @@ -295,10 +287,11 @@ class BulkCreateView(GetReturnURLMixin, GenericView): 'model_form': model_form, 'obj_type': model._meta.verbose_name, 'return_url': self.get_return_url(request), + **self.get_extra_context(request), }) -class BulkImportView(GetReturnURLMixin, GenericView): +class BulkImportView(GetReturnURLMixin, BaseMultiObjectView): """ Import objects in bulk (CSV format). @@ -375,6 +368,7 @@ class BulkImportView(GetReturnURLMixin, GenericView): 'fields': self.model_form().fields, 'obj_type': self.model_form._meta.model._meta.verbose_name, 'return_url': self.get_return_url(request), + **self.get_extra_context(request), }) def post(self, request): @@ -423,10 +417,11 @@ class BulkImportView(GetReturnURLMixin, GenericView): 'fields': self.model_form().fields, 'obj_type': self.model_form._meta.model._meta.verbose_name, 'return_url': self.get_return_url(request), + **self.get_extra_context(request), }) -class BulkEditView(GetReturnURLMixin, GenericView): +class BulkEditView(GetReturnURLMixin, BaseMultiObjectView): """ Edit objects in bulk. @@ -578,10 +573,11 @@ class BulkEditView(GetReturnURLMixin, GenericView): 'table': table, 'obj_type_plural': model._meta.verbose_name_plural, 'return_url': self.get_return_url(request), + **self.get_extra_context(request), }) -class BulkRenameView(GetReturnURLMixin, GenericView): +class BulkRenameView(GetReturnURLMixin, BaseMultiObjectView): """ An extendable view for renaming objects in bulk. """ @@ -668,7 +664,7 @@ class BulkRenameView(GetReturnURLMixin, GenericView): }) -class BulkDeleteView(GetReturnURLMixin, GenericView): +class BulkDeleteView(GetReturnURLMixin, BaseMultiObjectView): """ Delete objects in bulk. @@ -684,6 +680,18 @@ class BulkDeleteView(GetReturnURLMixin, GenericView): def get_required_permission(self): return get_permission_for_model(self.queryset.model, 'delete') + def get_form(self): + """ + Provide a standard bulk delete form if none has been specified for the view + """ + class BulkDeleteForm(ConfirmationForm): + pk = ModelMultipleChoiceField(queryset=self.queryset, widget=MultipleHiddenInput) + + if self.form: + return self.form + + return BulkDeleteForm + def get(self, request): return redirect(self.get_return_url(request)) @@ -746,26 +754,15 @@ class BulkDeleteView(GetReturnURLMixin, GenericView): 'obj_type_plural': model._meta.verbose_name_plural, 'table': table, 'return_url': self.get_return_url(request), + **self.get_extra_context(request), }) - def get_form(self): - """ - Provide a standard bulk delete form if none has been specified for the view - """ - class BulkDeleteForm(ConfirmationForm): - pk = ModelMultipleChoiceField(queryset=self.queryset, widget=MultipleHiddenInput) - - if self.form: - return self.form - - return BulkDeleteForm - # # Device/VirtualMachine components # -class BulkComponentCreateView(GetReturnURLMixin, GenericView): +class BulkComponentCreateView(GetReturnURLMixin, BaseMultiObjectView): """ Add one or more components (e.g. interfaces, console ports, etc.) to a set of Devices or VirtualMachines. """ diff --git a/netbox/netbox/views/generic/object_views.py b/netbox/netbox/views/generic/object_views.py index 79732572d..c681767c2 100644 --- a/netbox/netbox/views/generic/object_views.py +++ b/netbox/netbox/views/generic/object_views.py @@ -21,7 +21,7 @@ from utilities.permissions import get_permission_for_model from utilities.tables import configure_table from utilities.utils import normalize_querydict, prepare_cloned_fields from utilities.views import GetReturnURLMixin -from .base import GenericView +from .base import BaseObjectView __all__ = ( 'ComponentCreateView', @@ -33,13 +33,12 @@ __all__ = ( ) -class ObjectView(GenericView): +class ObjectView(BaseObjectView): """ Retrieve a single object for display. Note: If `template_name` is not specified, it will be determined automatically based on the queryset model. """ - def get_required_permission(self): return get_permission_for_model(self.queryset.model, 'view') @@ -60,16 +59,6 @@ class ObjectView(GenericView): model_opts = self.queryset.model._meta return f'{model_opts.app_label}/{model_opts.model_name}.html' - def get_extra_context(self, request, instance): - """ - Return any additional context data for the template. - - Args: - request: The current request - instance: The object being viewed - """ - return {} - def get(self, request, **kwargs): """ GET request handler. `*args` and `**kwargs` are passed to identify the object being queried. @@ -152,7 +141,7 @@ class ObjectChildrenView(ObjectView): }) -class ObjectImportView(GetReturnURLMixin, GenericView): +class ObjectImportView(GetReturnURLMixin, BaseObjectView): """ Import a single object (YAML or JSON format). @@ -291,7 +280,7 @@ class ObjectImportView(GetReturnURLMixin, GenericView): }) -class ObjectEditView(GetReturnURLMixin, GenericView): +class ObjectEditView(GetReturnURLMixin, BaseObjectView): """ Create or edit a single object. @@ -362,6 +351,7 @@ class ObjectEditView(GetReturnURLMixin, GenericView): 'obj_type': self.queryset.model._meta.verbose_name, 'form': form, 'return_url': self.get_return_url(request, obj), + **self.get_extra_context(request, obj), }) def post(self, request, *args, **kwargs): @@ -435,10 +425,11 @@ class ObjectEditView(GetReturnURLMixin, GenericView): 'obj_type': self.queryset.model._meta.verbose_name, 'form': form, 'return_url': self.get_return_url(request, obj), + **self.get_extra_context(request, obj), }) -class ObjectDeleteView(GetReturnURLMixin, GenericView): +class ObjectDeleteView(GetReturnURLMixin, BaseObjectView): """ Delete a single object. """ @@ -481,6 +472,7 @@ class ObjectDeleteView(GetReturnURLMixin, GenericView): 'object_type': self.queryset.model._meta.verbose_name, 'form': form, 'form_url': form_url, + **self.get_extra_context(request, obj), }) return render(request, self.template_name, { @@ -488,6 +480,7 @@ class ObjectDeleteView(GetReturnURLMixin, GenericView): 'object_type': self.queryset.model._meta.verbose_name, 'form': form, 'return_url': self.get_return_url(request, obj), + **self.get_extra_context(request, obj), }) def post(self, request, *args, **kwargs): @@ -529,6 +522,7 @@ class ObjectDeleteView(GetReturnURLMixin, GenericView): 'object_type': self.queryset.model._meta.verbose_name, 'form': form, 'return_url': self.get_return_url(request, obj), + **self.get_extra_context(request, obj), }) @@ -536,7 +530,7 @@ class ObjectDeleteView(GetReturnURLMixin, GenericView): # Device/VirtualMachine components # -class ComponentCreateView(GetReturnURLMixin, GenericView): +class ComponentCreateView(GetReturnURLMixin, BaseObjectView): """ Add one or more components (e.g. interfaces, console ports, etc.) to a Device or VirtualMachine. """