__init__.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import collections
  2. from importlib import import_module
  3. from django.apps import AppConfig
  4. from django.conf import settings
  5. from django.core.exceptions import ImproperlyConfigured
  6. from django.utils.module_loading import import_string
  7. from packaging import version
  8. from netbox.registry import registry
  9. from netbox.search import register_search
  10. from .navigation import *
  11. from .registration import *
  12. from .templates import *
  13. # Initialize plugin registry
  14. registry['plugins'] = {
  15. 'graphql_schemas': [],
  16. 'menus': [],
  17. 'menu_items': {},
  18. 'preferences': {},
  19. 'template_extensions': collections.defaultdict(list),
  20. }
  21. DEFAULT_RESOURCE_PATHS = {
  22. 'search_indexes': 'search.indexes',
  23. 'graphql_schema': 'graphql.schema',
  24. 'menu': 'navigation.menu',
  25. 'menu_items': 'navigation.menu_items',
  26. 'template_extensions': 'template_content.template_extensions',
  27. 'user_preferences': 'preferences.preferences',
  28. }
  29. #
  30. # Plugin AppConfig class
  31. #
  32. class PluginConfig(AppConfig):
  33. """
  34. Subclass of Django's built-in AppConfig class, to be used for NetBox plugins.
  35. """
  36. # Plugin metadata
  37. author = ''
  38. author_email = ''
  39. description = ''
  40. version = ''
  41. # Root URL path under /plugins. If not set, the plugin's label will be used.
  42. base_url = None
  43. # Minimum/maximum compatible versions of NetBox
  44. min_version = None
  45. max_version = None
  46. # Default configuration parameters
  47. default_settings = {}
  48. # Mandatory configuration parameters
  49. required_settings = []
  50. # Middleware classes provided by the plugin
  51. middleware = []
  52. # Django-rq queues dedicated to the plugin
  53. queues = []
  54. # Django apps to append to INSTALLED_APPS when plugin requires them.
  55. django_apps = []
  56. # Optional plugin resources
  57. search_indexes = None
  58. graphql_schema = None
  59. menu = None
  60. menu_items = None
  61. template_extensions = None
  62. user_preferences = None
  63. def _load_resource(self, name):
  64. # Import from the configured path, if defined.
  65. if path := getattr(self, name, None):
  66. return import_string(f"{self.__module__}.{path}")
  67. # Fall back to the resource's default path. Return None if the module has not been provided.
  68. default_path = f'{self.__module__}.{DEFAULT_RESOURCE_PATHS[name]}'
  69. default_module, resource_name = default_path.rsplit('.', 1)
  70. try:
  71. module = import_module(default_module)
  72. return getattr(module, resource_name, None)
  73. except ModuleNotFoundError:
  74. pass
  75. def ready(self):
  76. plugin_name = self.name.rsplit('.', 1)[-1]
  77. # Register search extensions (if defined)
  78. search_indexes = self._load_resource('search_indexes') or []
  79. for idx in search_indexes:
  80. register_search(idx)
  81. # Register template content (if defined)
  82. if template_extensions := self._load_resource('template_extensions'):
  83. register_template_extensions(template_extensions)
  84. # Register navigation menu and/or menu items (if defined)
  85. if menu := self._load_resource('menu'):
  86. register_menu(menu)
  87. if menu_items := self._load_resource('menu_items'):
  88. register_menu_items(self.verbose_name, menu_items)
  89. # Register GraphQL schema (if defined)
  90. if graphql_schema := self._load_resource('graphql_schema'):
  91. register_graphql_schema(graphql_schema)
  92. # Register user preferences (if defined)
  93. if user_preferences := self._load_resource('user_preferences'):
  94. register_user_preferences(plugin_name, user_preferences)
  95. @classmethod
  96. def validate(cls, user_config, netbox_version):
  97. # Enforce version constraints
  98. current_version = version.parse(netbox_version)
  99. if cls.min_version is not None:
  100. min_version = version.parse(cls.min_version)
  101. if current_version < min_version:
  102. raise ImproperlyConfigured(
  103. f"Plugin {cls.__module__} requires NetBox minimum version {cls.min_version}."
  104. )
  105. if cls.max_version is not None:
  106. max_version = version.parse(cls.max_version)
  107. if current_version > max_version:
  108. raise ImproperlyConfigured(
  109. f"Plugin {cls.__module__} requires NetBox maximum version {cls.max_version}."
  110. )
  111. # Verify required configuration settings
  112. for setting in cls.required_settings:
  113. if setting not in user_config:
  114. raise ImproperlyConfigured(
  115. f"Plugin {cls.__module__} requires '{setting}' to be present in the PLUGINS_CONFIG section of "
  116. f"configuration.py."
  117. )
  118. # Apply default configuration values
  119. for setting, value in cls.default_settings.items():
  120. if setting not in user_config:
  121. user_config[setting] = value
  122. #
  123. # Utilities
  124. #
  125. def get_plugin_config(plugin_name, parameter, default=None):
  126. """
  127. Return the value of the specified plugin configuration parameter.
  128. Args:
  129. plugin_name: The name of the plugin
  130. parameter: The name of the configuration parameter
  131. default: The value to return if the parameter is not defined (default: None)
  132. """
  133. try:
  134. plugin_config = settings.PLUGINS_CONFIG[plugin_name]
  135. return plugin_config.get(parameter, default)
  136. except KeyError:
  137. raise ImproperlyConfigured(f"Plugin {plugin_name} is not registered.")