Browse Source

add version contraints and cacheops config

John Anderson 5 years ago
parent
commit
c7fb2ff894
3 changed files with 45 additions and 18 deletions
  1. 9 9
      netbox/extras/plugins/__init__.py
  2. 29 9
      netbox/netbox/settings.py
  3. 7 0
      netbox/netbox/urls.py

+ 9 - 9
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']

+ 29 - 9
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)

+ 7 - 0
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 `<url_slug>/<path>`
                 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 `<url_slug>/<path>`
                     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')))
 )