add version contraints and cacheops config

This commit is contained in:
John Anderson 2020-03-18 18:28:27 -04:00
parent fd879c7cf5
commit c7fb2ff894
3 changed files with 45 additions and 18 deletions

View File

@ -5,7 +5,7 @@ import inspect
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.template.loader import get_template 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 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 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') responses = register_detail_page_content_classes.send('registration_event')
for receiver, response in responses: for receiver, response in responses:
@ -90,7 +90,7 @@ def register_content_classes():
if template_class.model is None: if template_class.model is None:
raise TypeError('Plugin content class {} does not define a valid model!'.format(template_class)) 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): 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. Given a model string, return the list of all registered template content classes.
Populate the registry if it is empty. 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() 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 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') responses = register_nav_menu_link_classes.send('registration_event')
for receiver, response in responses: for receiver, response in responses:
@ -165,7 +165,7 @@ def register_nav_menu_links():
if not isinstance(button, PluginNavMenuButton): if not isinstance(button, PluginNavMenuButton):
raise TypeError('{} must be an instance of PluginNavMenuButton!'.format(button)) 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(): 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. Return the list of all registered nav menu link classes.
Populate the registry if it is empty. 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() register_nav_menu_links()
return registry.plugin_nav_menu_link_classes return registry['plugin_nav_menu_link_classes']

View File

@ -10,7 +10,7 @@ from urllib.parse import urlsplit
from django.contrib.messages import constants as messages from django.contrib.messages import constants as messages
from django.core.exceptions import ImproperlyConfigured, ValidationError from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.core.validators import URLValidator 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) PLUGINS.append(plugin)
INSTALLED_APPS.append(plugin) INSTALLED_APPS.append(plugin)
# Import the app config and locate the inner meta class
try: try:
module = importlib.import_module(plugin) module = importlib.import_module(plugin)
default_app_config = getattr(module, 'default_app_config') 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) '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 = getattr(app_config_meta, '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)
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 # Verify required configuration settings
if plugin not in PLUGINS_CONFIG: if plugin not in PLUGINS_CONFIG:
@ -688,3 +693,18 @@ if PLUGINS_ENABLED:
for setting, value in getattr(app_config_meta, 'default_settings', {}).items(): for setting, value in getattr(app_config_meta, '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
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)

View File

@ -75,26 +75,33 @@ _patterns = [
plugin_patterns = [] 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
if hasattr(app, 'NetBoxPluginMeta'): if hasattr(app, 'NetBoxPluginMeta'):
# 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.NetBoxPluginMeta, 'url_slug', app.label)
if hasattr(urls, 'urlpatterns'): if hasattr(urls, 'urlpatterns'):
# Mount URLs at `<url_slug>/<path>`
plugin_patterns.append( plugin_patterns.append(
path('{}/'.format(url_slug), include((urls.urlpatterns, app.label))) 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'.format(app.name)):
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.NetBoxPluginMeta, 'url_slug', app.label)
# 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)))
) )
# Mount all plugin URLs within the `plugins` namespace
_patterns.append( _patterns.append(
path('plugins/', include((plugin_patterns, 'plugins'))) path('plugins/', include((plugin_patterns, 'plugins')))
) )
# Mount all plugin API URLs within the `plugins-api` namespace
_patterns.append( _patterns.append(
path('api/plugins/', include((plugin_api_patterns, 'plugins-api'))) path('api/plugins/', include((plugin_api_patterns, 'plugins-api')))
) )