diff --git a/docs/plugins/development/views.md b/docs/plugins/development/views.md index f6624f42c..9781cfa55 100644 --- a/docs/plugins/development/views.md +++ b/docs/plugins/development/views.md @@ -191,7 +191,7 @@ class MyView(generic.ObjectView): ### Extra Template Content -Plugins can inject custom content into certain areas of core NetBox views. This is accomplished by subclassing `PluginTemplateExtension`, designating a particular NetBox model, and defining the desired method(s) to render custom content. Five methods are available: +Plugins can inject custom content into certain areas of core NetBox views. This is accomplished by subclassing `PluginTemplateExtension`, optionally designating one or more particular NetBox models, and defining the desired method(s) to render custom content. Five methods are available: | Method | View | Description | |---------------------|-------------|-----------------------------------------------------| @@ -206,7 +206,9 @@ Plugins can inject custom content into certain areas of core NetBox views. This Additionally, a `render()` method is available for convenience. This method accepts the name of a template to render, and any additional context data you want to pass. Its use is optional, however. -When a PluginTemplateExtension is instantiated, context data is assigned to `self.context`. Available data include: +To control where the custom content is injected, plugin authors can specify an iterable of models by overriding the `models` attribute on the subclass. Extensions which do not specify a set of models will be invoked on every view, where supported. + +When a PluginTemplateExtension is instantiated, context data is assigned to `self.context`. Available data includes: * `object` - The object being viewed (object views only) * `model` - The model of the list view (list views only) @@ -223,7 +225,7 @@ from netbox.plugins import PluginTemplateExtension from .models import Animal class SiteAnimalCount(PluginTemplateExtension): - model = 'dcim.site' + models = ['dcim.site'] def right_page(self): return self.render('netbox_animal_sounds/inc/animal_count.html', extra_context={ diff --git a/docs/release-notes/version-4.1.md b/docs/release-notes/version-4.1.md index c6c260ee0..54bbf7ed9 100644 --- a/docs/release-notes/version-4.1.md +++ b/docs/release-notes/version-4.1.md @@ -12,7 +12,14 @@ ### Enhancements * [#7537](https://github.com/netbox-community/netbox/issues/7537) - Add a serial number field for virtual machines +* [#8984](https://github.com/netbox-community/netbox/issues/8984) - Enable filtering of custom script output by log level +* [#15156](https://github.com/netbox-community/netbox/issues/15156) - Add `display_url` field to all REST API serializers * [#16359](https://github.com/netbox-community/netbox/issues/16359) - Enable plugins to embed content in the top navigation bar +* [#16580](https://github.com/netbox-community/netbox/issues/16580) - Enable individual views to enforce `LOGIN_REQUIRED` selectively (remove `AUTH_EXEMPT_PATHS`) + +### Plugins + +* [#16726](https://github.com/netbox-community/netbox/issues/16726) - Extend `PluginTemplateExtension` to enable registering multiple models ### Other Changes diff --git a/netbox/extras/constants.py b/netbox/extras/constants.py index 005f6863d..7162299e7 100644 --- a/netbox/extras/constants.py +++ b/netbox/extras/constants.py @@ -1,3 +1,5 @@ +from extras.choices import LogLevelChoices + # Events EVENT_CREATE = 'create' EVENT_UPDATE = 'update' @@ -135,3 +137,12 @@ DEFAULT_DASHBOARD = [ } }, ] + +LOG_LEVEL_RANK = { + LogLevelChoices.LOG_DEFAULT: 0, + LogLevelChoices.LOG_DEBUG: 1, + LogLevelChoices.LOG_SUCCESS: 2, + LogLevelChoices.LOG_INFO: 3, + LogLevelChoices.LOG_WARNING: 4, + LogLevelChoices.LOG_FAILURE: 5, +} diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 6544abdad..4d3332405 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -14,6 +14,7 @@ from core.forms import ManagedFileForm from core.models import Job from core.tables import JobTable 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 netbox.constants import DEFAULT_ACTION_PERMISSIONS @@ -30,6 +31,7 @@ from utilities.templatetags.builtins.filters import render_markdown from utilities.views import ContentTypePermissionRequiredMixin, get_viewname, register_model_view from virtualization.models import VirtualMachine from . import filtersets, forms, tables +from .constants import LOG_LEVEL_RANK from .models import * from .scripts import run_script from .tables import ReportResultsTable, ScriptResultsTable @@ -1119,22 +1121,27 @@ class ScriptResultView(TableMixin, generic.ObjectView): tests = None table = None index = 0 + + log_threshold = LOG_LEVEL_RANK.get(request.GET.get('log_threshold', LogLevelChoices.LOG_DEFAULT)) if job.data: + if 'log' in job.data: if 'tests' in job.data: tests = job.data['tests'] for log in job.data['log']: - index += 1 - result = { - 'index': index, - 'time': log.get('time'), - 'status': log.get('status'), - 'message': log.get('message'), - 'object': log.get('obj'), - 'url': log.get('url'), - } - data.append(result) + log_level = LOG_LEVEL_RANK.get(log.get('status'), LogLevelChoices.LOG_DEFAULT) + if log_level >= log_threshold: + index += 1 + result = { + 'index': index, + 'time': log.get('time'), + 'status': log.get('status'), + 'message': log.get('message'), + 'object': log.get('obj'), + 'url': log.get('url'), + } + data.append(result) table = ScriptResultsTable(data, user=request.user) table.configure(request) @@ -1146,17 +1153,19 @@ class ScriptResultView(TableMixin, generic.ObjectView): for method, test_data in tests.items(): if 'log' in test_data: for time, status, obj, url, message in test_data['log']: - index += 1 - result = { - 'index': index, - 'method': method, - 'time': time, - 'status': status, - 'object': obj, - 'url': url, - 'message': message, - } - data.append(result) + log_level = LOG_LEVEL_RANK.get(status, LogLevelChoices.LOG_DEFAULT) + if log_level >= log_threshold: + index += 1 + result = { + 'index': index, + 'method': method, + 'time': time, + 'status': status, + 'object': obj, + 'url': url, + 'message': message, + } + data.append(result) table = ReportResultsTable(data, user=request.user) table.configure(request) @@ -1174,6 +1183,8 @@ class ScriptResultView(TableMixin, generic.ObjectView): 'script': job.object, 'job': job, 'table': table, + 'log_levels': dict(LogLevelChoices), + 'log_threshold': request.GET.get('log_threshold', LogLevelChoices.LOG_DEFAULT) } if job.data and 'log' in job.data: diff --git a/netbox/netbox/plugins/registration.py b/netbox/netbox/plugins/registration.py index fbece12e5..c84572794 100644 --- a/netbox/netbox/plugins/registration.py +++ b/netbox/netbox/plugins/registration.py @@ -18,8 +18,8 @@ def register_template_extensions(class_list): """ Register a list of PluginTemplateExtension classes """ - # Validation for template_extension in class_list: + # Validation if not inspect.isclass(template_extension): raise TypeError( _("PluginTemplateExtension class {template_extension} was passed as an instance!").format( @@ -33,7 +33,17 @@ def register_template_extensions(class_list): ) ) - registry['plugins']['template_extensions'][template_extension.model].append(template_extension) + if template_extension.models: + # Registration for multiple models + models = template_extension.models + elif template_extension.model: + # Registration for a single model + models = [template_extension.model] + else: + # Global registration (no specific models) + models = [None] + for model in models: + registry['plugins']['template_extensions'][model].append(template_extension) def register_menu(menu): diff --git a/netbox/netbox/plugins/templates.py b/netbox/netbox/plugins/templates.py index ccd549160..5fa1959b8 100644 --- a/netbox/netbox/plugins/templates.py +++ b/netbox/netbox/plugins/templates.py @@ -20,6 +20,7 @@ class PluginTemplateExtension: * settings - Global NetBox settings * config - Plugin-specific configuration parameters """ + models = None model = None def __init__(self, context): diff --git a/netbox/netbox/tests/dummy_plugin/template_content.py b/netbox/netbox/tests/dummy_plugin/template_content.py index 764faa60e..b7157e370 100644 --- a/netbox/netbox/tests/dummy_plugin/template_content.py +++ b/netbox/netbox/tests/dummy_plugin/template_content.py @@ -8,7 +8,7 @@ class GlobalContent(PluginTemplateExtension): class SiteContent(PluginTemplateExtension): - model = 'dcim.site' + models = ['dcim.site'] def left_page(self): return "SITE CONTENT - LEFT PAGE" diff --git a/netbox/templates/extras/script_result.html b/netbox/templates/extras/script_result.html index 1b297673b..40be0456e 100644 --- a/netbox/templates/extras/script_result.html +++ b/netbox/templates/extras/script_result.html @@ -42,8 +42,26 @@