From 28b5e88c50d6aba9ba08bb1be961e7f315965dfd Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 20 Mar 2020 14:35:54 -0400 Subject: [PATCH 1/3] Rename entry point group; simplify import --- netbox/netbox/settings.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index e3fa4e924..8527b737c 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -640,19 +640,12 @@ 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 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 = entry_point.load() app_config_meta = getattr(app_config, 'NetBoxPluginMeta', None) if not app_config_meta: From bc50c2aa551091fa48428fbc00c112d42de4dd46 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 20 Mar 2020 15:13:25 -0400 Subject: [PATCH 2/3] Introduce PluginConfig --- netbox/extras/plugins/__init__.py | 35 +++++++++++++++++++++- netbox/netbox/settings.py | 50 ++++++++++++------------------- 2 files changed, 53 insertions(+), 32 deletions(-) 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 8527b737c..6fed9dafe 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -642,62 +642,50 @@ PLUGINS = [] if PLUGINS_ENABLED: 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) - app_config = entry_point.load() - - 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) From ad1522f42802dc3e08ade11022c4685fc32ce8d9 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 20 Mar 2020 15:51:14 -0400 Subject: [PATCH 3/3] Update plugin URL loading logic --- netbox/netbox/urls.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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)))