Merge pull request #4392 from netbox-community/refactor-plugins-import

Refactor plugins import
This commit is contained in:
John Anderson 2020-03-20 17:04:33 -04:00 committed by GitHub
commit e220c38b97
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 58 additions and 43 deletions

View File

@ -2,13 +2,46 @@ import collections
import importlib import importlib
import inspect import inspect
from django.core.exceptions import ImproperlyConfigured from django.apps import AppConfig
from django.template.loader import get_template from django.template.loader import get_template
from extras.registry import registry from extras.registry import registry
from .signals import register_detail_page_content_classes, register_nav_menu_link_classes 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 # Template content injection
# #

View File

@ -640,71 +640,52 @@ if PAGINATE_COUNT not in PER_PAGE_DEFAULTS:
PLUGINS = [] PLUGINS = []
if PLUGINS_ENABLED: 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 plugin = entry_point.module_name
app_config = entry_point.load()
PLUGINS.append(plugin) PLUGINS.append(plugin)
INSTALLED_APPS.append(plugin) INSTALLED_APPS.append(plugin)
# Import the app config and locate the inner meta class # Check version constraints
try: parsed_min_version = parse_version(app_config.min_version or VERSION)
module = importlib.import_module(plugin) parsed_max_version = parse_version(app_config.max_version or VERSION)
default_app_config = getattr(module, 'default_app_config') if app_config.min_version and app_config.max_version and parsed_min_version > parsed_max_version:
module, app_config = default_app_config.rsplit('.', 1) raise ImproperlyConfigured(f"Plugin {plugin} specifies invalid version constraints!")
app_config = getattr(importlib.import_module(module), app_config) if app_config.min_version and parsed_min_version > parse_version(VERSION):
except ImportError: raise ImproperlyConfigured(f"Plugin {plugin} requires NetBox minimum version {app_config.min_version}!")
raise ImproperlyConfigured('Plugin config for {} could not be imported!'.format(plugin)) 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}!")
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))
# Add middleware # Add middleware
plugin_middleware = getattr(app_config_meta, 'middleware', []) plugin_middleware = app_config.middleware
if plugin_middleware and isinstance(plugin_middleware, list): if plugin_middleware and isinstance(plugin_middleware, list):
MIDDLEWARE.extend(plugin_middleware) MIDDLEWARE.extend(plugin_middleware)
# Verify required configuration settings # Verify required configuration settings
if plugin not in PLUGINS_CONFIG: if plugin not in PLUGINS_CONFIG:
PLUGINS_CONFIG[plugin] = {} 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]: if setting not in PLUGINS_CONFIG[plugin]:
raise ImproperlyConfigured( raise ImproperlyConfigured(
"Plugin {} requires '{}' to be present in the PLUGINS_CONFIG section of configuration.py.".format( f"Plugin {plugin} requires '{setting}' to be present in the PLUGINS_CONFIG section of "
plugin, f"configuration.py."
setting
)
) )
# Set defined default setting values # 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]: if setting not in PLUGINS_CONFIG[plugin]:
PLUGINS_CONFIG[plugin][setting] = value PLUGINS_CONFIG[plugin][setting] = value
# Apply cacheops config # 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): if plugin_cacheops and isinstance(plugin_cacheops, dict):
for key in plugin_cacheops.keys(): for key in plugin_cacheops.keys():
# Validate config is only being set for the given plugin # Validate config is only being set for the given plugin
try: try:
app = key.split('.')[0] app = key.split('.')[0]
except IndexError: except IndexError:
raise ImproperlyConfigured('Plugin {} caching_config is invalid!'.format(plugin)) raise ImproperlyConfigured(f"Plugin {plugin} caching_config is invalid!")
if app != plugin: if app != plugin:
raise ImproperlyConfigured( raise ImproperlyConfigured(f"Plugin {plugin} may not modify caching config for another app!")
'Plugin {} may not modify caching config for another app!'.format(plugin)
)
CACHEOPS.update(plugin_cacheops) CACHEOPS.update(plugin_cacheops)

View File

@ -8,6 +8,7 @@ from django.views.static import serve
from drf_yasg import openapi from drf_yasg import openapi
from drf_yasg.views import get_schema_view from drf_yasg.views import get_schema_view
from extras.plugins import PluginConfig
from netbox.views import APIRootView, HomeView, StaticMediaFailureView, SearchView from netbox.views import APIRootView, HomeView, StaticMediaFailureView, SearchView
from users.views import LoginView, LogoutView from users.views import LoginView, LogoutView
from .admin import admin_site from .admin import admin_site
@ -76,11 +77,11 @@ plugin_patterns = []
plugin_api_patterns = [] plugin_api_patterns = []
for app in apps.get_app_configs(): for app in apps.get_app_configs():
# Loop over all apps look for installed plugins # Loop over all apps look for installed plugins
if hasattr(app, 'NetBoxPluginMeta'): if isinstance(app, PluginConfig):
# Check if the plugin specifies any URLs # Check if the plugin specifies any URLs
if importlib.util.find_spec('{}.urls'.format(app.name)): if importlib.util.find_spec('{}.urls'.format(app.name)):
urls = importlib.import_module('{}.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'): if hasattr(urls, 'urlpatterns'):
# Mount URLs at `<url_slug>/<path>` # Mount URLs at `<url_slug>/<path>`
plugin_patterns.append( plugin_patterns.append(
@ -91,7 +92,7 @@ for app in apps.get_app_configs():
if importlib.util.find_spec('{}.api.urls'.format(app.name)): if importlib.util.find_spec('{}.api.urls'.format(app.name)):
urls = importlib.import_module('{}.api.urls'.format(app.name)) urls = importlib.import_module('{}.api.urls'.format(app.name))
if hasattr(urls, 'urlpatterns'): 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 `<url_slug>/<path>` # Mount URLs at `<url_slug>/<path>`
plugin_api_patterns.append( plugin_api_patterns.append(
path('{}/'.format(url_slug), include((urls.urlpatterns, app.label))) path('{}/'.format(url_slug), include((urls.urlpatterns, app.label)))