diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index ff246d2ca..8a9c61e22 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -40,6 +40,7 @@ A new `PluginMenu` class has been introduced, which enables a plugin to inject a * [#9045](https://github.com/netbox-community/netbox/issues/9045) - Remove legacy ASN field from provider model * [#9046](https://github.com/netbox-community/netbox/issues/9046) - Remove legacy contact fields from provider model * [#10358](https://github.com/netbox-community/netbox/issues/10358) - Raise minimum required PostgreSQL version from 10 to 11 +* [#10699](https://github.com/netbox-community/netbox/issues/10699) - Remove custom `import_object()` function ### REST API Changes diff --git a/netbox/extras/plugins/__init__.py b/netbox/extras/plugins/__init__.py index 78a056216..ea88c0f51 100644 --- a/netbox/extras/plugins/__init__.py +++ b/netbox/extras/plugins/__init__.py @@ -5,8 +5,8 @@ from packaging import version from django.apps import AppConfig from django.core.exceptions import ImproperlyConfigured from django.template.loader import get_template +from django.utils.module_loading import import_string -from extras.plugins.utils import import_object from extras.registry import registry from netbox.navigation import MenuGroup from netbox.search import register_search @@ -71,31 +71,46 @@ class PluginConfig(AppConfig): def ready(self): plugin_name = self.name.rsplit('.', 1)[-1] - # Search extensions - search_indexes = import_object(f"{self.__module__}.{self.search_indexes}") or [] - for idx in search_indexes: - register_search()(idx) + # Register search extensions (if defined) + try: + search_indexes = import_string(f"{self.__module__}.{self.search_indexes}") + for idx in search_indexes: + register_search()(idx) + except ImportError: + pass # Register template content (if defined) - template_extensions = import_object(f"{self.__module__}.{self.template_extensions}") - if template_extensions is not None: + try: + template_extensions = import_string(f"{self.__module__}.{self.template_extensions}") register_template_extensions(template_extensions) + except ImportError: + pass - # Register navigation menu or menu items (if defined) - if menu := import_object(f"{self.__module__}.{self.menu}"): + # Register navigation menu and/or menu items (if defined) + try: + menu = import_string(f"{self.__module__}.{self.menu}") register_menu(menu) - if menu_items := import_object(f"{self.__module__}.{self.menu_items}"): + except ImportError: + pass + try: + menu_items = import_string(f"{self.__module__}.{self.menu_items}") register_menu_items(self.verbose_name, menu_items) + except ImportError: + pass # Register GraphQL schema (if defined) - graphql_schema = import_object(f"{self.__module__}.{self.graphql_schema}") - if graphql_schema is not None: + try: + graphql_schema = import_string(f"{self.__module__}.{self.graphql_schema}") register_graphql_schema(graphql_schema) + except ImportError: + pass # Register user preferences (if defined) - user_preferences = import_object(f"{self.__module__}.{self.user_preferences}") - if user_preferences is not None: + try: + user_preferences = import_string(f"{self.__module__}.{self.user_preferences}") register_user_preferences(plugin_name, user_preferences) + except ImportError: + pass @classmethod def validate(cls, user_config, netbox_version): diff --git a/netbox/extras/plugins/urls.py b/netbox/extras/plugins/urls.py index 7ab293916..b4360dc9e 100644 --- a/netbox/extras/plugins/urls.py +++ b/netbox/extras/plugins/urls.py @@ -3,8 +3,7 @@ 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 extras.plugins.utils import import_object +from django.utils.module_loading import import_string from . import views @@ -25,15 +24,19 @@ for plugin_path in settings.PLUGINS: base_url = getattr(app, 'base_url') or app.label # Check if the plugin specifies any base URLs - urlpatterns = import_object(f"{plugin_path}.urls.urlpatterns") - if urlpatterns is not None: + try: + urlpatterns = import_string(f"{plugin_path}.urls.urlpatterns") plugin_patterns.append( path(f"{base_url}/", include((urlpatterns, app.label))) ) + except ImportError: + pass # Check if the plugin specifies any API URLs - urlpatterns = import_object(f"{plugin_path}.api.urls.urlpatterns") - if urlpatterns is not None: + 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 diff --git a/netbox/extras/plugins/utils.py b/netbox/extras/plugins/utils.py deleted file mode 100644 index 87240aba8..000000000 --- a/netbox/extras/plugins/utils.py +++ /dev/null @@ -1,33 +0,0 @@ -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)