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

This commit is contained in:
Glenn Matthews 2020-07-01 15:23:38 -04:00
parent 43d610405f
commit f807d3a024
3 changed files with 66 additions and 32 deletions

View File

@ -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: module, attr = f"{self.__module__}.{self.template_extensions}".rsplit('.', 1)
template_extensions = import_string(f"{self.__module__}.{self.template_extensions}") spec = importlib.util.find_spec(module)
register_template_extensions(template_extensions) if spec is not None:
except ImportError: template_content = importlib.util.module_from_spec(spec)
pass 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: module, attr = f"{self.__module__}.{self.menu_items}".rsplit('.', 1)
menu_items = import_string(f"{self.__module__}.{self.menu_items}") spec = importlib.util.find_spec(module)
register_menu_items(self.verbose_name, menu_items) if spec is not None:
except ImportError: navigation = importlib.util.module_from_spec(spec)
pass 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):

View File

@ -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: spec = importlib.util.find_spec(f"{plugin_path}.urls")
urlpatterns = import_string(f"{plugin_path}.urls.urlpatterns") if spec is not None:
plugin_patterns.append( # The plugin has a .urls module - import it
path(f"{base_url}/", include((urlpatterns, app.label))) urls = importlib.util.module_from_spec(spec)
) sys.modules[f"{plugin_path}.urls"] = urls
except ImportError: spec.loader.exec_module(urls)
pass 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: spec = importlib.util.find_spec(f"{plugin_path}.api")
urlpatterns = import_string(f"{plugin_path}.api.urls.urlpatterns") if spec is not None:
plugin_api_patterns.append( spec = importlib.util.find_spec(f"{plugin_path}.api.urls")
path(f"{base_url}/", include((urlpatterns, f"{app.label}-api"))) if spec is not None:
) # The plugin has a .api.urls module - import it
except ImportError: api_urls = importlib.util.module_from_spec(spec)
pass 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")))
)

View File

@ -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: # Check if the plugin specifies any API URLs
api_app_name = import_string(f"{plugin}.api.urls.app_name") spec = importlib.util.find_spec(f"{plugin}.api")
except (ImportError, ModuleNotFoundError): if spec is None:
# Plugin does not expose an API # There is no plugin.api module
return None 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: 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