diff --git a/netbox/extras/plugins/__init__.py b/netbox/extras/plugins/__init__.py index c0ac4259d..12e79d2dc 100644 --- a/netbox/extras/plugins/__init__.py +++ b/netbox/extras/plugins/__init__.py @@ -2,13 +2,46 @@ import collections import importlib import inspect -from django.core.exceptions import ImproperlyConfigured +from django.apps import AppConfig from django.template.loader import get_template from extras.registry import registry from .signals import register_detail_page_content_classes, register_nav_menu_link_classes +# +# Plugin AppConfig class +# + +class PluginConfig(AppConfig): + """ + Subclass of Django's built-in AppConfig class, to be used for NetBox plugins. + """ + # Plugin metadata + author = '' + description = '' + version = '' + + # Root URL path under /plugins. If not set, the plugin's label will be used. + url_slug = None + + # Minimum/maximum compatible versions of NetBox + min_version = None + max_version = None + + # Default configuration parameters + default_settings = {} + + # Mandatory configuration parameters + required_settings = [] + + # Middleware classes provided by the plugin + middleware = [] + + # Caching configuration + caching_config = {} + + # # Template content injection # diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index e3fa4e924..6fed9dafe 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -640,71 +640,52 @@ if PAGINATE_COUNT not in PER_PAGE_DEFAULTS: PLUGINS = [] if PLUGINS_ENABLED: - for entry_point in iter_entry_points(group='netbox.plugin', name=None): + for entry_point in iter_entry_points(group='netbox_plugins', name=None): plugin = entry_point.module_name + app_config = entry_point.load() + PLUGINS.append(plugin) INSTALLED_APPS.append(plugin) - # Import the app config and locate the inner meta class - try: - module = importlib.import_module(plugin) - default_app_config = getattr(module, 'default_app_config') - module, app_config = default_app_config.rsplit('.', 1) - app_config = getattr(importlib.import_module(module), app_config) - except ImportError: - raise ImproperlyConfigured('Plugin config for {} could not be imported!'.format(plugin)) - - app_config_meta = getattr(app_config, 'NetBoxPluginMeta', None) - if not app_config_meta: - raise ImproperlyConfigured( - 'The app config for plugin {} does not contain an inner meta class'.format(plugin) - ) - - # Check version contraints - min_version = getattr(app_config_meta, 'min_version', None) - max_version = getattr(app_config_meta, 'max_version', None) - parsed_min_version = parse_version(min_version or VERSION) - parsed_max_version = parse_version(max_version or VERSION) - if min_version and max_version and parsed_min_version > parsed_max_version: - raise ImproperlyConfigured('Plugin {} specifies invalid version contraints!'.format(plugin)) - if min_version and parsed_min_version > parse_version(VERSION): - raise ImproperlyConfigured('Plugin {} requires NetBox minimum version {}!'.format(plugin, min_version)) - if max_version and parsed_max_version < parse_version(VERSION): - raise ImproperlyConfigured('Plugin {} requires NetBox maximum version {}!'.format(plugin, max_version)) + # Check version constraints + parsed_min_version = parse_version(app_config.min_version or VERSION) + parsed_max_version = parse_version(app_config.max_version or VERSION) + if app_config.min_version and app_config.max_version and parsed_min_version > parsed_max_version: + raise ImproperlyConfigured(f"Plugin {plugin} specifies invalid version constraints!") + if app_config.min_version and parsed_min_version > parse_version(VERSION): + raise ImproperlyConfigured(f"Plugin {plugin} requires NetBox minimum version {app_config.min_version}!") + if app_config.max_version and parsed_max_version < parse_version(VERSION): + raise ImproperlyConfigured(f"Plugin {plugin} requires NetBox maximum version {app_config.max_version}!") # Add middleware - plugin_middleware = getattr(app_config_meta, 'middleware', []) + plugin_middleware = app_config.middleware if plugin_middleware and isinstance(plugin_middleware, list): MIDDLEWARE.extend(plugin_middleware) # Verify required configuration settings if plugin not in PLUGINS_CONFIG: PLUGINS_CONFIG[plugin] = {} - for setting in getattr(app_config_meta, 'required_settings', []): + for setting in app_config.required_settings: if setting not in PLUGINS_CONFIG[plugin]: raise ImproperlyConfigured( - "Plugin {} requires '{}' to be present in the PLUGINS_CONFIG section of configuration.py.".format( - plugin, - setting - ) + f"Plugin {plugin} requires '{setting}' to be present in the PLUGINS_CONFIG section of " + f"configuration.py." ) # Set defined default setting values - for setting, value in getattr(app_config_meta, 'default_settings', {}).items(): + for setting, value in app_config.default_settings.items(): if setting not in PLUGINS_CONFIG[plugin]: PLUGINS_CONFIG[plugin][setting] = value # Apply cacheops config - plugin_cacheops = getattr(app_config_meta, 'caching_config', {}) + plugin_cacheops = app_config.caching_config if plugin_cacheops and isinstance(plugin_cacheops, dict): for key in plugin_cacheops.keys(): # Validate config is only being set for the given plugin try: app = key.split('.')[0] except IndexError: - raise ImproperlyConfigured('Plugin {} caching_config is invalid!'.format(plugin)) + raise ImproperlyConfigured(f"Plugin {plugin} caching_config is invalid!") if app != plugin: - raise ImproperlyConfigured( - 'Plugin {} may not modify caching config for another app!'.format(plugin) - ) + raise ImproperlyConfigured(f"Plugin {plugin} may not modify caching config for another app!") CACHEOPS.update(plugin_cacheops) diff --git a/netbox/netbox/urls.py b/netbox/netbox/urls.py index 651aef14a..657e3d6b4 100644 --- a/netbox/netbox/urls.py +++ b/netbox/netbox/urls.py @@ -8,6 +8,7 @@ from django.views.static import serve from drf_yasg import openapi from drf_yasg.views import get_schema_view +from extras.plugins import PluginConfig from netbox.views import APIRootView, HomeView, StaticMediaFailureView, SearchView from users.views import LoginView, LogoutView from .admin import admin_site @@ -76,11 +77,11 @@ plugin_patterns = [] plugin_api_patterns = [] for app in apps.get_app_configs(): # Loop over all apps look for installed plugins - if hasattr(app, 'NetBoxPluginMeta'): + if isinstance(app, PluginConfig): # Check if the plugin specifies any URLs if importlib.util.find_spec('{}.urls'.format(app.name)): urls = importlib.import_module('{}.urls'.format(app.name)) - url_slug = getattr(app.NetBoxPluginMeta, 'url_slug', app.label) + url_slug = getattr(app, 'url_slug') or app.label if hasattr(urls, 'urlpatterns'): # Mount URLs at `/` plugin_patterns.append( @@ -91,7 +92,7 @@ for app in apps.get_app_configs(): if importlib.util.find_spec('{}.api.urls'.format(app.name)): urls = importlib.import_module('{}.api.urls'.format(app.name)) if hasattr(urls, 'urlpatterns'): - url_slug = getattr(app.NetBoxPluginMeta, 'url_slug', app.label) + url_slug = getattr(app, 'url_slug') or app.label # Mount URLs at `/` plugin_api_patterns.append( path('{}/'.format(url_slug), include((urls.urlpatterns, app.label)))