From c7fb2ff894b23b82ab47b248a603da60750ec709 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Wed, 18 Mar 2020 18:28:27 -0400 Subject: [PATCH] add version contraints and cacheops config --- netbox/extras/plugins/__init__.py | 18 +++++++-------- netbox/netbox/settings.py | 38 +++++++++++++++++++++++-------- netbox/netbox/urls.py | 7 ++++++ 3 files changed, 45 insertions(+), 18 deletions(-) diff --git a/netbox/extras/plugins/__init__.py b/netbox/extras/plugins/__init__.py index f3ce7dcad..c0ac4259d 100644 --- a/netbox/extras/plugins/__init__.py +++ b/netbox/extras/plugins/__init__.py @@ -5,7 +5,7 @@ import inspect from django.core.exceptions import ImproperlyConfigured from django.template.loader import get_template -from extras.utils import registry +from extras.registry import registry from .signals import register_detail_page_content_classes, register_nav_menu_link_classes @@ -76,7 +76,7 @@ def register_content_classes(): """ Helper method that populates the registry with all template content classes that have been registered by plugins """ - registry.plugin_template_content_classes = collections.defaultdict(list) + registry['plugin_template_content_classes'] = collections.defaultdict(list) responses = register_detail_page_content_classes.send('registration_event') for receiver, response in responses: @@ -90,7 +90,7 @@ def register_content_classes(): if template_class.model is None: raise TypeError('Plugin content class {} does not define a valid model!'.format(template_class)) - registry.plugin_template_content_classes[template_class.model].append(template_class) + registry['plugin_template_content_classes'][template_class.model].append(template_class) def get_content_classes(model): @@ -98,10 +98,10 @@ def get_content_classes(model): Given a model string, return the list of all registered template content classes. Populate the registry if it is empty. """ - if not hasattr(registry, 'plugin_template_content_classes'): + if 'plugin_template_content_classes' not in registry: register_content_classes() - return registry.plugin_template_content_classes.get(model, []) + return registry['plugin_template_content_classes'].get(model, []) # @@ -139,7 +139,7 @@ def register_nav_menu_links(): """ Helper method that populates the registry with all nav menu link classes that have been registered by plugins """ - registry.plugin_nav_menu_link_classes = {} + registry['plugin_nav_menu_link_classes'] = {} responses = register_nav_menu_link_classes.send('registration_event') for receiver, response in responses: @@ -165,7 +165,7 @@ def register_nav_menu_links(): if not isinstance(button, PluginNavMenuButton): raise TypeError('{} must be an instance of PluginNavMenuButton!'.format(button)) - registry.plugin_nav_menu_link_classes[section_name] = response + registry['plugin_nav_menu_link_classes'][section_name] = response def get_nav_menu_link_classes(): @@ -173,7 +173,7 @@ def get_nav_menu_link_classes(): Return the list of all registered nav menu link classes. Populate the registry if it is empty. """ - if not hasattr(registry, 'plugin_nav_menu_link_classes'): + if 'plugin_nav_menu_link_classes' not in registry: register_nav_menu_links() - return registry.plugin_nav_menu_link_classes + return registry['plugin_nav_menu_link_classes'] diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 926501498..e3fa4e924 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -10,7 +10,7 @@ from urllib.parse import urlsplit from django.contrib.messages import constants as messages from django.core.exceptions import ImproperlyConfigured, ValidationError from django.core.validators import URLValidator -from pkg_resources import iter_entry_points +from pkg_resources import iter_entry_points, parse_version # @@ -645,6 +645,7 @@ if PLUGINS_ENABLED: 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') @@ -659,18 +660,22 @@ if PLUGINS_ENABLED: '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 plugin_middleware = getattr(app_config_meta, 'middleware', []) if plugin_middleware and isinstance(plugin_middleware, list): MIDDLEWARE.extend(plugin_middleware) - plugin_middleware_prepend = getattr(app_config_meta, 'middleware_prepend', []) - if plugin_middleware_prepend and isinstance(plugin_middleware_prepend, list): - MIDDLEWARE[:0] = plugin_middleware_prepend - - # Add installed apps - plugin_installed_apps = getattr(app_config_meta, 'installed_apps', []) - if plugin_installed_apps and isinstance(plugin_installed_apps, list): - INSTALLED_APPS.extend(plugin_installed_apps) # Verify required configuration settings if plugin not in PLUGINS_CONFIG: @@ -688,3 +693,18 @@ if PLUGINS_ENABLED: for setting, value in getattr(app_config_meta, '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', {}) + 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)) + if app != plugin: + raise ImproperlyConfigured( + 'Plugin {} may not modify caching config for another app!'.format(plugin) + ) + CACHEOPS.update(plugin_cacheops) diff --git a/netbox/netbox/urls.py b/netbox/netbox/urls.py index 0c8845f5e..651aef14a 100644 --- a/netbox/netbox/urls.py +++ b/netbox/netbox/urls.py @@ -75,26 +75,33 @@ _patterns = [ plugin_patterns = [] plugin_api_patterns = [] for app in apps.get_app_configs(): + # Loop over all apps look for installed plugins if hasattr(app, 'NetBoxPluginMeta'): + # 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) if hasattr(urls, 'urlpatterns'): + # Mount URLs at `/` plugin_patterns.append( path('{}/'.format(url_slug), include((urls.urlpatterns, app.label))) ) + # Check if the plugin specifies any API URLs if importlib.util.find_spec('{}.api'.format(app.name)): 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) + # Mount URLs at `/` plugin_api_patterns.append( path('{}/'.format(url_slug), include((urls.urlpatterns, app.label))) ) +# Mount all plugin URLs within the `plugins` namespace _patterns.append( path('plugins/', include((plugin_patterns, 'plugins'))) ) +# Mount all plugin API URLs within the `plugins-api` namespace _patterns.append( path('api/plugins/', include((plugin_api_patterns, 'plugins-api'))) )