Skip to content

Commit fbc9fea

Browse files
Fixes #11267: Avoid catching ImportError exceptions when loading plugins (#11566)
* Avoid catching ImportErrors when loading plugin URLs * Avoid catching ImportErrors when loading plugin resources
1 parent ccc108a commit fbc9fea

File tree

2 files changed

+42
-41
lines changed

2 files changed

+42
-41
lines changed

netbox/extras/plugins/__init__.py

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import collections
2+
from importlib.util import find_spec
23

34
from django.apps import AppConfig
45
from django.conf import settings
@@ -21,6 +22,15 @@
2122
'template_extensions': collections.defaultdict(list),
2223
}
2324

25+
DEFAULT_RESOURCE_PATHS = {
26+
'search_indexes': 'search.indexes',
27+
'graphql_schema': 'graphql.schema',
28+
'menu': 'navigation.menu',
29+
'menu_items': 'navigation.menu_items',
30+
'template_extensions': 'template_content.template_extensions',
31+
'user_preferences': 'preferences.preferences',
32+
}
33+
2434

2535
#
2636
# Plugin AppConfig class
@@ -58,58 +68,50 @@ class PluginConfig(AppConfig):
5868
# Django apps to append to INSTALLED_APPS when plugin requires them.
5969
django_apps = []
6070

61-
# Default integration paths. Plugin authors can override these to customize the paths to
62-
# integrated components.
63-
search_indexes = 'search.indexes'
64-
graphql_schema = 'graphql.schema'
65-
menu = 'navigation.menu'
66-
menu_items = 'navigation.menu_items'
67-
template_extensions = 'template_content.template_extensions'
68-
user_preferences = 'preferences.preferences'
71+
# Optional plugin resources
72+
search_indexes = None
73+
graphql_schema = None
74+
menu = None
75+
menu_items = None
76+
template_extensions = None
77+
user_preferences = None
78+
79+
def _load_resource(self, name):
80+
# Import from the configured path, if defined.
81+
if getattr(self, name):
82+
return import_string(f"{self.__module__}.{self.name}")
83+
# Fall back to the resource's default path. Return None if the module has not been provided.
84+
default_path = DEFAULT_RESOURCE_PATHS[name]
85+
default_module = f'{self.__module__}.{default_path}'.rsplit('.', 1)[0]
86+
if find_spec(default_module):
87+
setattr(self, name, default_path)
88+
return import_string(f"{self.__module__}.{default_path}")
6989

7090
def ready(self):
7191
plugin_name = self.name.rsplit('.', 1)[-1]
7292

7393
# Register search extensions (if defined)
74-
try:
75-
search_indexes = import_string(f"{self.__module__}.{self.search_indexes}")
76-
for idx in search_indexes:
77-
register_search(idx)
78-
except ImportError:
79-
pass
94+
search_indexes = self._load_resource('search_indexes') or []
95+
for idx in search_indexes:
96+
register_search(idx)
8097

8198
# Register template content (if defined)
82-
try:
83-
template_extensions = import_string(f"{self.__module__}.{self.template_extensions}")
99+
if template_extensions := self._load_resource('template_extensions'):
84100
register_template_extensions(template_extensions)
85-
except ImportError:
86-
pass
87101

88102
# Register navigation menu and/or menu items (if defined)
89-
try:
90-
menu = import_string(f"{self.__module__}.{self.menu}")
103+
if menu := self._load_resource('menu'):
91104
register_menu(menu)
92-
except ImportError:
93-
pass
94-
try:
95-
menu_items = import_string(f"{self.__module__}.{self.menu_items}")
105+
if menu_items := self._load_resource('menu_items'):
96106
register_menu_items(self.verbose_name, menu_items)
97-
except ImportError:
98-
pass
99107

100108
# Register GraphQL schema (if defined)
101-
try:
102-
graphql_schema = import_string(f"{self.__module__}.{self.graphql_schema}")
109+
if graphql_schema := self._load_resource('graphql_schema'):
103110
register_graphql_schema(graphql_schema)
104-
except ImportError:
105-
pass
106111

107112
# Register user preferences (if defined)
108-
try:
109-
user_preferences = import_string(f"{self.__module__}.{self.user_preferences}")
113+
if user_preferences := self._load_resource('user_preferences'):
110114
register_user_preferences(plugin_name, user_preferences)
111-
except ImportError:
112-
pass
113115

114116
@classmethod
115117
def validate(cls, user_config, netbox_version):

netbox/extras/plugins/urls.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
from importlib import import_module
2+
13
from django.apps import apps
24
from django.conf import settings
35
from django.conf.urls import include
46
from django.contrib.admin.views.decorators import staff_member_required
57
from django.urls import path
6-
from django.utils.module_loading import import_string
8+
from django.utils.module_loading import import_string, module_has_submodule
79

810
from . import views
911

@@ -19,24 +21,21 @@
1921

2022
# Register base/API URL patterns for each plugin
2123
for plugin_path in settings.PLUGINS:
24+
plugin = import_module(plugin_path)
2225
plugin_name = plugin_path.split('.')[-1]
2326
app = apps.get_app_config(plugin_name)
2427
base_url = getattr(app, 'base_url') or app.label
2528

2629
# Check if the plugin specifies any base URLs
27-
try:
30+
if module_has_submodule(plugin, 'urls'):
2831
urlpatterns = import_string(f"{plugin_path}.urls.urlpatterns")
2932
plugin_patterns.append(
3033
path(f"{base_url}/", include((urlpatterns, app.label)))
3134
)
32-
except ImportError:
33-
pass
3435

3536
# Check if the plugin specifies any API URLs
36-
try:
37+
if module_has_submodule(plugin, 'api.urls'):
3738
urlpatterns = import_string(f"{plugin_path}.api.urls.urlpatterns")
3839
plugin_api_patterns.append(
3940
path(f"{base_url}/", include((urlpatterns, f"{app.label}-api")))
4041
)
41-
except ImportError:
42-
pass

0 commit comments

Comments
 (0)