Просмотр исходного кода

Don't ignore ImportErrors raised when loading a plugin. Fixes #4805

Glenn Matthews 5 лет назад
Родитель
Сommit
f807d3a024
3 измененных файлов с 66 добавлено и 32 удалено
  1. 20 11
      netbox/extras/plugins/__init__.py
  2. 27 15
      netbox/extras/plugins/urls.py
  3. 19 6
      netbox/extras/plugins/views.py

+ 20 - 11
netbox/extras/plugins/__init__.py

@@ -1,12 +1,13 @@
 import collections
+import importlib
 import inspect
+import sys
 from packaging import version
 
 from django.apps import AppConfig
 from django.conf import settings
 from django.core.exceptions import ImproperlyConfigured
 from django.template.loader import get_template
-from django.utils.module_loading import import_string
 
 from extras.registry import registry
 from utilities.choices import ButtonColorChoices
@@ -60,18 +61,26 @@ class PluginConfig(AppConfig):
     def ready(self):
 
         # Register template content
-        try:
-            template_extensions = import_string(f"{self.__module__}.{self.template_extensions}")
-            register_template_extensions(template_extensions)
-        except ImportError:
-            pass
+        module, attr = f"{self.__module__}.{self.template_extensions}".rsplit('.', 1)
+        spec = importlib.util.find_spec(module)
+        if spec is not None:
+            template_content = importlib.util.module_from_spec(spec)
+            sys.modules[module] = template_content
+            spec.loader.exec_module(template_content)
+            if hasattr(template_content, attr):
+                template_extensions = getattr(template_content, attr)
+                register_template_extensions(template_extensions)
 
         # Register navigation menu items (if defined)
-        try:
-            menu_items = import_string(f"{self.__module__}.{self.menu_items}")
-            register_menu_items(self.verbose_name, menu_items)
-        except ImportError:
-            pass
+        module, attr = f"{self.__module__}.{self.menu_items}".rsplit('.', 1)
+        spec = importlib.util.find_spec(module)
+        if spec is not None:
+            navigation = importlib.util.module_from_spec(spec)
+            sys.modules[module] = navigation
+            spec.loader.exec_module(navigation)
+            if hasattr(navigation, attr):
+                menu_items = getattr(navigation, attr)
+                register_menu_items(self.verbose_name, menu_items)
 
     @classmethod
     def validate(cls, user_config):

+ 27 - 15
netbox/extras/plugins/urls.py

@@ -1,9 +1,11 @@
+import importlib
+import sys
+
 from django.apps import apps
 from django.conf import settings
 from django.conf.urls import include
 from django.contrib.admin.views.decorators import staff_member_required
 from django.urls import path
-from django.utils.module_loading import import_string
 
 from . import views
 
@@ -24,19 +26,29 @@ for plugin_path in settings.PLUGINS:
     base_url = getattr(app, 'base_url') or app.label
 
     # Check if the plugin specifies any base URLs
-    try:
-        urlpatterns = import_string(f"{plugin_path}.urls.urlpatterns")
-        plugin_patterns.append(
-            path(f"{base_url}/", include((urlpatterns, app.label)))
-        )
-    except ImportError:
-        pass
+    spec = importlib.util.find_spec(f"{plugin_path}.urls")
+    if spec is not None:
+        # The plugin has a .urls module - import it
+        urls = importlib.util.module_from_spec(spec)
+        sys.modules[f"{plugin_path}.urls"] = urls
+        spec.loader.exec_module(urls)
+        if hasattr(urls, "urlpatterns"):
+            urlpatterns = urls.urlpatterns
+            plugin_patterns.append(
+                path(f"{base_url}/", include((urlpatterns, app.label)))
+            )
 
     # Check if the plugin specifies any API URLs
-    try:
-        urlpatterns = import_string(f"{plugin_path}.api.urls.urlpatterns")
-        plugin_api_patterns.append(
-            path(f"{base_url}/", include((urlpatterns, f"{app.label}-api")))
-        )
-    except ImportError:
-        pass
+    spec = importlib.util.find_spec(f"{plugin_path}.api")
+    if spec is not None:
+        spec = importlib.util.find_spec(f"{plugin_path}.api.urls")
+        if spec is not None:
+            # The plugin has a .api.urls module - import it
+            api_urls = importlib.util.module_from_spec(spec)
+            sys.modules[f"{plugin_path}.api.urls"] = api_urls
+            spec.loader.exec_module(api_urls)
+            if hasattr(api_urls, "urlpatterns"):
+                urlpatterns = api_urls.urlpatterns
+                plugin_api_patterns.append(
+                    path(f"{base_url}/", include((urlpatterns, f"{app.label}-api")))
+                )

+ 19 - 6
netbox/extras/plugins/views.py

@@ -1,10 +1,11 @@
 from collections import OrderedDict
+import importlib
+import sys
 
 from django.apps import apps
 from django.conf import settings
 from django.shortcuts import render
 from django.urls.exceptions import NoReverseMatch
-from django.utils.module_loading import import_string
 from django.views.generic import View
 from rest_framework import permissions
 from rest_framework.response import Response
@@ -60,11 +61,23 @@ class PluginsAPIRootView(APIView):
 
     @staticmethod
     def _get_plugin_entry(plugin, app_config, request, format):
-        try:
-            api_app_name = import_string(f"{plugin}.api.urls.app_name")
-        except (ImportError, ModuleNotFoundError):
-            # Plugin does not expose an API
+        # Check if the plugin specifies any API URLs
+        spec = importlib.util.find_spec(f"{plugin}.api")
+        if spec is None:
+            # There is no plugin.api module
+            return None
+        spec = importlib.util.find_spec(f"{plugin}.api.urls")
+        if spec is None:
+            # There is no plugin.api.urls module
+            return None
+        # The plugin has a .api.urls module - import it
+        api_urls = importlib.util.module_from_spec(spec)
+        sys.modules[f"{plugin}.api.urls"] = api_urls
+        spec.loader.exec_module(api_urls)
+        if not hasattr(api_urls, "app_name"):
+            # The plugin api.urls does not declare an app_name string
             return None
+        api_app_name = api_urls.app_name
 
         try:
             entry = (getattr(app_config, 'base_url', app_config.label), reverse(
@@ -73,7 +86,7 @@ class PluginsAPIRootView(APIView):
                 format=format
             ))
         except NoReverseMatch:
-            # The plugin does not include an api-root
+            # The plugin does not include an api-root url
             entry = None
 
         return entry