Fixes #11267: Avoid catching ImportError exceptions when loading plugins (#11566)

* Avoid catching ImportErrors when loading plugin URLs

* Avoid catching ImportErrors when loading plugin resources
This commit is contained in:
Jeremy Stretch 2023-01-27 16:44:10 -05:00 committed by GitHub
parent ccc108a217
commit fbc9fea0a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 42 additions and 41 deletions

View File

@ -1,4 +1,5 @@
import collections import collections
from importlib.util import find_spec
from django.apps import AppConfig from django.apps import AppConfig
from django.conf import settings from django.conf import settings
@ -21,6 +22,15 @@ registry['plugins'] = {
'template_extensions': collections.defaultdict(list), 'template_extensions': collections.defaultdict(list),
} }
DEFAULT_RESOURCE_PATHS = {
'search_indexes': 'search.indexes',
'graphql_schema': 'graphql.schema',
'menu': 'navigation.menu',
'menu_items': 'navigation.menu_items',
'template_extensions': 'template_content.template_extensions',
'user_preferences': 'preferences.preferences',
}
# #
# Plugin AppConfig class # Plugin AppConfig class
@ -58,58 +68,50 @@ class PluginConfig(AppConfig):
# Django apps to append to INSTALLED_APPS when plugin requires them. # Django apps to append to INSTALLED_APPS when plugin requires them.
django_apps = [] django_apps = []
# Default integration paths. Plugin authors can override these to customize the paths to # Optional plugin resources
# integrated components. search_indexes = None
search_indexes = 'search.indexes' graphql_schema = None
graphql_schema = 'graphql.schema' menu = None
menu = 'navigation.menu' menu_items = None
menu_items = 'navigation.menu_items' template_extensions = None
template_extensions = 'template_content.template_extensions' user_preferences = None
user_preferences = 'preferences.preferences'
def _load_resource(self, name):
# Import from the configured path, if defined.
if getattr(self, name):
return import_string(f"{self.__module__}.{self.name}")
# Fall back to the resource's default path. Return None if the module has not been provided.
default_path = DEFAULT_RESOURCE_PATHS[name]
default_module = f'{self.__module__}.{default_path}'.rsplit('.', 1)[0]
if find_spec(default_module):
setattr(self, name, default_path)
return import_string(f"{self.__module__}.{default_path}")
def ready(self): def ready(self):
plugin_name = self.name.rsplit('.', 1)[-1] plugin_name = self.name.rsplit('.', 1)[-1]
# Register search extensions (if defined) # Register search extensions (if defined)
try: search_indexes = self._load_resource('search_indexes') or []
search_indexes = import_string(f"{self.__module__}.{self.search_indexes}") for idx in search_indexes:
for idx in search_indexes: register_search(idx)
register_search(idx)
except ImportError:
pass
# Register template content (if defined) # Register template content (if defined)
try: if template_extensions := self._load_resource('template_extensions'):
template_extensions = import_string(f"{self.__module__}.{self.template_extensions}")
register_template_extensions(template_extensions) register_template_extensions(template_extensions)
except ImportError:
pass
# Register navigation menu and/or menu items (if defined) # Register navigation menu and/or menu items (if defined)
try: if menu := self._load_resource('menu'):
menu = import_string(f"{self.__module__}.{self.menu}")
register_menu(menu) register_menu(menu)
except ImportError: if menu_items := self._load_resource('menu_items'):
pass
try:
menu_items = import_string(f"{self.__module__}.{self.menu_items}")
register_menu_items(self.verbose_name, menu_items) register_menu_items(self.verbose_name, menu_items)
except ImportError:
pass
# Register GraphQL schema (if defined) # Register GraphQL schema (if defined)
try: if graphql_schema := self._load_resource('graphql_schema'):
graphql_schema = import_string(f"{self.__module__}.{self.graphql_schema}")
register_graphql_schema(graphql_schema) register_graphql_schema(graphql_schema)
except ImportError:
pass
# Register user preferences (if defined) # Register user preferences (if defined)
try: if user_preferences := self._load_resource('user_preferences'):
user_preferences = import_string(f"{self.__module__}.{self.user_preferences}")
register_user_preferences(plugin_name, user_preferences) register_user_preferences(plugin_name, user_preferences)
except ImportError:
pass
@classmethod @classmethod
def validate(cls, user_config, netbox_version): def validate(cls, user_config, netbox_version):

View File

@ -1,9 +1,11 @@
from importlib import import_module
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 django.utils.module_loading import import_string, module_has_submodule
from . import views from . import views
@ -19,24 +21,21 @@ plugin_admin_patterns = [
# Register base/API URL patterns for each plugin # Register base/API URL patterns for each plugin
for plugin_path in settings.PLUGINS: for plugin_path in settings.PLUGINS:
plugin = import_module(plugin_path)
plugin_name = plugin_path.split('.')[-1] plugin_name = plugin_path.split('.')[-1]
app = apps.get_app_config(plugin_name) app = apps.get_app_config(plugin_name)
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: if module_has_submodule(plugin, 'urls'):
urlpatterns = import_string(f"{plugin_path}.urls.urlpatterns") urlpatterns = import_string(f"{plugin_path}.urls.urlpatterns")
plugin_patterns.append( plugin_patterns.append(
path(f"{base_url}/", include((urlpatterns, app.label))) path(f"{base_url}/", include((urlpatterns, app.label)))
) )
except ImportError:
pass
# Check if the plugin specifies any API URLs # Check if the plugin specifies any API URLs
try: if module_has_submodule(plugin, 'api.urls'):
urlpatterns = import_string(f"{plugin_path}.api.urls.urlpatterns") urlpatterns = import_string(f"{plugin_path}.api.urls.urlpatterns")
plugin_api_patterns.append( plugin_api_patterns.append(
path(f"{base_url}/", include((urlpatterns, f"{app.label}-api"))) path(f"{base_url}/", include((urlpatterns, f"{app.label}-api")))
) )
except ImportError:
pass