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

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 collections
+import importlib
 import inspect
 import inspect
+import sys
 from packaging import version
 from packaging import version
 
 
 from django.apps import AppConfig
 from django.apps import AppConfig
 from django.conf import settings
 from django.conf import settings
 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 django.utils.module_loading import import_string
 
 
 from extras.registry import registry
 from extras.registry import registry
 from utilities.choices import ButtonColorChoices
 from utilities.choices import ButtonColorChoices
@@ -60,18 +61,26 @@ class PluginConfig(AppConfig):
     def ready(self):
     def ready(self):
 
 
         # Register template content
         # 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)
         # 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
     @classmethod
     def validate(cls, user_config):
     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.apps import apps
 from django.conf import settings
 from django.conf import settings
 from django.conf.urls import include
 from django.conf.urls import include
 from django.contrib.admin.views.decorators import staff_member_required
 from django.contrib.admin.views.decorators import staff_member_required
 from django.urls import path
 from django.urls import path
-from django.utils.module_loading import import_string
 
 
 from . import views
 from . import views
 
 
@@ -24,19 +26,29 @@ for plugin_path in settings.PLUGINS:
     base_url = getattr(app, 'base_url') or app.label
     base_url = getattr(app, 'base_url') or app.label
 
 
     # Check if the plugin specifies any base URLs
     # 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
     # 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
 from collections import OrderedDict
+import importlib
+import sys
 
 
 from django.apps import apps
 from django.apps import apps
 from django.conf import settings
 from django.conf import settings
 from django.shortcuts import render
 from django.shortcuts import render
 from django.urls.exceptions import NoReverseMatch
 from django.urls.exceptions import NoReverseMatch
-from django.utils.module_loading import import_string
 from django.views.generic import View
 from django.views.generic import View
 from rest_framework import permissions
 from rest_framework import permissions
 from rest_framework.response import Response
 from rest_framework.response import Response
@@ -60,11 +61,23 @@ class PluginsAPIRootView(APIView):
 
 
     @staticmethod
     @staticmethod
     def _get_plugin_entry(plugin, app_config, request, format):
     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
             return None
+        api_app_name = api_urls.app_name
 
 
         try:
         try:
             entry = (getattr(app_config, 'base_url', app_config.label), reverse(
             entry = (getattr(app_config, 'base_url', app_config.label), reverse(
@@ -73,7 +86,7 @@ class PluginsAPIRootView(APIView):
                 format=format
                 format=format
             ))
             ))
         except NoReverseMatch:
         except NoReverseMatch:
-            # The plugin does not include an api-root
+            # The plugin does not include an api-root url
             entry = None
             entry = None
 
 
         return entry
         return entry