mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Merge pull request #8562 from netbox-community/8405-plugins-graphql
Closes #8405: GraphQL support for plugins
This commit is contained in:
@@ -12,10 +12,13 @@ from utilities.choices import ButtonColorChoices
|
||||
from extras.plugins.utils import import_object
|
||||
|
||||
|
||||
# Initialize plugin registry stores
|
||||
registry['plugin_template_extensions'] = collections.defaultdict(list)
|
||||
registry['plugin_menu_items'] = {}
|
||||
registry['plugin_preferences'] = {}
|
||||
# Initialize plugin registry
|
||||
registry['plugins'] = {
|
||||
'graphql_schemas': [],
|
||||
'menu_items': {},
|
||||
'preferences': {},
|
||||
'template_extensions': collections.defaultdict(list),
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
@@ -53,13 +56,15 @@ class PluginConfig(AppConfig):
|
||||
|
||||
# Default integration paths. Plugin authors can override these to customize the paths to
|
||||
# integrated components.
|
||||
template_extensions = 'template_content.template_extensions'
|
||||
graphql_schema = 'graphql.schema'
|
||||
menu_items = 'navigation.menu_items'
|
||||
template_extensions = 'template_content.template_extensions'
|
||||
user_preferences = 'preferences.preferences'
|
||||
|
||||
def ready(self):
|
||||
plugin_name = self.name.rsplit('.', 1)[1]
|
||||
|
||||
# Register template content
|
||||
# Register template content (if defined)
|
||||
template_extensions = import_object(f"{self.__module__}.{self.template_extensions}")
|
||||
if template_extensions is not None:
|
||||
register_template_extensions(template_extensions)
|
||||
@@ -69,10 +74,14 @@ class PluginConfig(AppConfig):
|
||||
if menu_items is not None:
|
||||
register_menu_items(self.verbose_name, menu_items)
|
||||
|
||||
# Register user preferences
|
||||
# Register GraphQL schema (if defined)
|
||||
graphql_schema = import_object(f"{self.__module__}.{self.graphql_schema}")
|
||||
if graphql_schema is not None:
|
||||
register_graphql_schema(graphql_schema)
|
||||
|
||||
# Register user preferences (if defined)
|
||||
user_preferences = import_object(f"{self.__module__}.{self.user_preferences}")
|
||||
if user_preferences is not None:
|
||||
plugin_name = self.name.rsplit('.', 1)[1]
|
||||
register_user_preferences(plugin_name, user_preferences)
|
||||
|
||||
@classmethod
|
||||
@@ -178,13 +187,13 @@ def register_template_extensions(class_list):
|
||||
# Validation
|
||||
for template_extension in class_list:
|
||||
if not inspect.isclass(template_extension):
|
||||
raise TypeError(f"PluginTemplateExtension class {template_extension} was passes as an instance!")
|
||||
raise TypeError(f"PluginTemplateExtension class {template_extension} was passed as an instance!")
|
||||
if not issubclass(template_extension, PluginTemplateExtension):
|
||||
raise TypeError(f"{template_extension} is not a subclass of extras.plugins.PluginTemplateExtension!")
|
||||
if template_extension.model is None:
|
||||
raise TypeError(f"PluginTemplateExtension class {template_extension} does not define a valid model!")
|
||||
|
||||
registry['plugin_template_extensions'][template_extension.model].append(template_extension)
|
||||
registry['plugins']['template_extensions'][template_extension.model].append(template_extension)
|
||||
|
||||
|
||||
#
|
||||
@@ -249,7 +258,18 @@ def register_menu_items(section_name, class_list):
|
||||
if not isinstance(button, PluginMenuButton):
|
||||
raise TypeError(f"{button} must be an instance of extras.plugins.PluginMenuButton")
|
||||
|
||||
registry['plugin_menu_items'][section_name] = class_list
|
||||
registry['plugins']['menu_items'][section_name] = class_list
|
||||
|
||||
|
||||
#
|
||||
# GraphQL schemas
|
||||
#
|
||||
|
||||
def register_graphql_schema(graphql_schema):
|
||||
"""
|
||||
Register a GraphQL schema class for inclusion in NetBox's GraphQL API.
|
||||
"""
|
||||
registry['plugins']['graphql_schemas'].append(graphql_schema)
|
||||
|
||||
|
||||
#
|
||||
@@ -260,4 +280,4 @@ def register_user_preferences(plugin_name, preferences):
|
||||
"""
|
||||
Register a list of user preferences defined by a plugin.
|
||||
"""
|
||||
registry['plugin_preferences'][plugin_name] = preferences
|
||||
registry['plugins']['preferences'][plugin_name] = preferences
|
||||
|
@@ -23,7 +23,7 @@ def _get_registered_content(obj, method, template_context):
|
||||
}
|
||||
|
||||
model_name = obj._meta.label_lower
|
||||
template_extensions = registry['plugin_template_extensions'].get(model_name, [])
|
||||
template_extensions = registry['plugins']['template_extensions'].get(model_name, [])
|
||||
for template_extension in template_extensions:
|
||||
|
||||
# If the class has not overridden the specified method, we can skip it (because we know it
|
||||
|
21
netbox/extras/tests/dummy_plugin/graphql.py
Normal file
21
netbox/extras/tests/dummy_plugin/graphql.py
Normal file
@@ -0,0 +1,21 @@
|
||||
import graphene
|
||||
from graphene_django import DjangoObjectType
|
||||
|
||||
from netbox.graphql.fields import ObjectField, ObjectListField
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
class DummyModelType(DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.DummyModel
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class DummyQuery(graphene.ObjectType):
|
||||
dummymodel = ObjectField(DummyModelType)
|
||||
dummymodel_list = ObjectListField(DummyModelType)
|
||||
|
||||
|
||||
schema = DummyQuery
|
@@ -7,6 +7,7 @@ from django.urls import reverse
|
||||
|
||||
from extras.registry import registry
|
||||
from extras.tests.dummy_plugin import config as dummy_config
|
||||
from netbox.graphql.schema import Query
|
||||
|
||||
|
||||
@skipIf('extras.tests.dummy_plugin' not in settings.PLUGINS, "dummy_plugin not in settings.PLUGINS")
|
||||
@@ -61,8 +62,8 @@ class PluginTest(TestCase):
|
||||
"""
|
||||
Check that plugin MenuItems and MenuButtons are registered.
|
||||
"""
|
||||
self.assertIn('Dummy plugin', registry['plugin_menu_items'])
|
||||
menu_items = registry['plugin_menu_items']['Dummy plugin']
|
||||
self.assertIn('Dummy plugin', registry['plugins']['menu_items'])
|
||||
menu_items = registry['plugins']['menu_items']['Dummy plugin']
|
||||
self.assertEqual(len(menu_items), 2)
|
||||
self.assertEqual(len(menu_items[0].buttons), 2)
|
||||
|
||||
@@ -72,14 +73,14 @@ class PluginTest(TestCase):
|
||||
"""
|
||||
from extras.tests.dummy_plugin.template_content import SiteContent
|
||||
|
||||
self.assertIn(SiteContent, registry['plugin_template_extensions']['dcim.site'])
|
||||
self.assertIn(SiteContent, registry['plugins']['template_extensions']['dcim.site'])
|
||||
|
||||
def test_user_preferences(self):
|
||||
"""
|
||||
Check that plugin UserPreferences are registered.
|
||||
"""
|
||||
self.assertIn('dummy_plugin', registry['plugin_preferences'])
|
||||
user_preferences = registry['plugin_preferences']['dummy_plugin']
|
||||
self.assertIn('dummy_plugin', registry['plugins']['preferences'])
|
||||
user_preferences = registry['plugins']['preferences']['dummy_plugin']
|
||||
self.assertEqual(type(user_preferences), dict)
|
||||
self.assertEqual(list(user_preferences.keys()), ['pref1', 'pref2'])
|
||||
|
||||
@@ -143,3 +144,12 @@ class PluginTest(TestCase):
|
||||
user_config = {'bar': 456}
|
||||
DummyConfigWithDefaultSettings.validate(user_config, settings.VERSION)
|
||||
self.assertEqual(user_config['bar'], 456)
|
||||
|
||||
def test_graphql(self):
|
||||
"""
|
||||
Validate the registration and operation of plugin-provided GraphQL schemas.
|
||||
"""
|
||||
from extras.tests.dummy_plugin.graphql import DummyQuery
|
||||
|
||||
self.assertIn(DummyQuery, registry['plugins']['graphql_schemas'])
|
||||
self.assertTrue(issubclass(Query, DummyQuery))
|
||||
|
Reference in New Issue
Block a user