1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Merge pull request #4813 from glennmatthews/gfm-issue-4805

Don't ignore ImportErrors raised when loading a plugin. Fixes #4805
This commit is contained in:
Jeremy Stretch
2020-07-15 09:05:42 -04:00
committed by GitHub
4 changed files with 51 additions and 23 deletions

View File

@ -6,11 +6,12 @@ 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
from extras.plugins.utils import import_object
# Initialize plugin registry stores
registry['plugin_template_extensions'] = collections.defaultdict(list)
@ -60,18 +61,14 @@ class PluginConfig(AppConfig):
def ready(self):
# Register template content
try:
template_extensions = import_string(f"{self.__module__}.{self.template_extensions}")
template_extensions = import_object(f"{self.__module__}.{self.template_extensions}")
if template_extensions is not None:
register_template_extensions(template_extensions)
except ImportError:
pass
# Register navigation menu items (if defined)
try:
menu_items = import_string(f"{self.__module__}.{self.menu_items}")
menu_items = import_object(f"{self.__module__}.{self.menu_items}")
if menu_items is not None:
register_menu_items(self.verbose_name, menu_items)
except ImportError:
pass
@classmethod
def validate(cls, user_config):

View File

@ -3,7 +3,8 @@ 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 extras.plugins.utils import import_object
from . import views
@ -24,19 +25,15 @@ 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")
urlpatterns = import_object(f"{plugin_path}.urls.urlpatterns")
if urlpatterns is not None:
plugin_patterns.append(
path(f"{base_url}/", include((urlpatterns, app.label)))
)
except ImportError:
pass
# Check if the plugin specifies any API URLs
try:
urlpatterns = import_string(f"{plugin_path}.api.urls.urlpatterns")
urlpatterns = import_object(f"{plugin_path}.api.urls.urlpatterns")
if urlpatterns is not None:
plugin_api_patterns.append(
path(f"{base_url}/", include((urlpatterns, f"{app.label}-api")))
)
except ImportError:
pass

View File

@ -0,0 +1,33 @@
import importlib.util
import sys
def import_object(module_and_object):
"""
Import a specific object from a specific module by name, such as "extras.plugins.utils.import_object".
Returns the imported object, or None if it doesn't exist.
"""
target_module_name, object_name = module_and_object.rsplit('.', 1)
module_hierarchy = target_module_name.split('.')
# Iterate through the module hierarchy, checking for the existence of each successive submodule.
# We have to do this rather than jumping directly to calling find_spec(target_module_name)
# because find_spec will raise a ModuleNotFoundError if any parent module of target_module_name does not exist.
module_name = ""
for module_component in module_hierarchy:
module_name = f"{module_name}.{module_component}" if module_name else module_component
spec = importlib.util.find_spec(module_name)
if spec is None:
# No such module
return None
# Okay, target_module_name exists. Load it if not already loaded
if target_module_name in sys.modules:
module = sys.modules[target_module_name]
else:
module = importlib.util.module_from_spec(spec)
sys.modules[target_module_name] = module
spec.loader.exec_module(module)
return getattr(module, object_name, None)

View File

@ -4,13 +4,14 @@ 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
from rest_framework.reverse import reverse
from rest_framework.views import APIView
from extras.plugins.utils import import_object
class InstalledPluginsAdminView(View):
"""
@ -60,9 +61,9 @@ 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):
# Check if the plugin specifies any API URLs
api_app_name = import_object(f"{plugin}.api.urls.app_name")
if api_app_name is None:
# Plugin does not expose an API
return None
@ -73,7 +74,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