From df95115e2e559afc5866390d6d06f66ae7db8ae0 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 27 Jan 2022 13:37:24 -0500 Subject: [PATCH 1/3] Refactor plugins registry --- netbox/extras/plugins/__init__.py | 16 +++++++++------- netbox/extras/templatetags/plugins.py | 2 +- netbox/extras/tests/test_plugins.py | 10 +++++----- netbox/netbox/navigation_menu.py | 4 ++-- netbox/netbox/preferences.py | 4 ++-- 5 files changed, 19 insertions(+), 17 deletions(-) diff --git a/netbox/extras/plugins/__init__.py b/netbox/extras/plugins/__init__.py index 5b02b5ab7..f297d43e1 100644 --- a/netbox/extras/plugins/__init__.py +++ b/netbox/extras/plugins/__init__.py @@ -12,10 +12,12 @@ 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'] = { + 'template_extensions': collections.defaultdict(list), + 'menu_items': {}, + 'preferences': {}, +} # @@ -184,7 +186,7 @@ def register_template_extensions(class_list): 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 +251,7 @@ 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 # @@ -260,4 +262,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 diff --git a/netbox/extras/templatetags/plugins.py b/netbox/extras/templatetags/plugins.py index 200b78e34..df3024a16 100644 --- a/netbox/extras/templatetags/plugins.py +++ b/netbox/extras/templatetags/plugins.py @@ -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 diff --git a/netbox/extras/tests/test_plugins.py b/netbox/extras/tests/test_plugins.py index 4bea9933e..608fc58dc 100644 --- a/netbox/extras/tests/test_plugins.py +++ b/netbox/extras/tests/test_plugins.py @@ -61,8 +61,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 +72,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']) diff --git a/netbox/netbox/navigation_menu.py b/netbox/netbox/navigation_menu.py index 85d86a47a..9a55c263e 100644 --- a/netbox/netbox/navigation_menu.py +++ b/netbox/netbox/navigation_menu.py @@ -390,10 +390,10 @@ MENUS = [ # Add plugin menus # -if registry['plugin_menu_items']: +if registry['plugins']['menu_items']: plugin_menu_groups = [] - for plugin_name, items in registry['plugin_menu_items'].items(): + for plugin_name, items in registry['plugins']['menu_items'].items(): plugin_menu_groups.append( MenuGroup( label=plugin_name, diff --git a/netbox/netbox/preferences.py b/netbox/netbox/preferences.py index aec8bc752..6bf56b562 100644 --- a/netbox/netbox/preferences.py +++ b/netbox/netbox/preferences.py @@ -49,10 +49,10 @@ PREFERENCES = { } # Register plugin preferences -if registry['plugin_preferences']: +if registry['plugins']['preferences']: plugin_preferences = {} - for plugin_name, preferences in registry['plugin_preferences'].items(): + for plugin_name, preferences in registry['plugins']['preferences'].items(): for name, userpreference in preferences.items(): PREFERENCES[f'plugins.{plugin_name}.{name}'] = userpreference From 03ea25771141dd8406e4ed9eb4797e4e8d8f70bf Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 27 Jan 2022 14:18:25 -0500 Subject: [PATCH 2/3] Initial work on GraphQL --- docs/plugins/development/graphql.md | 41 +++++++++++++++++++++ docs/plugins/development/index.md | 37 ++++++++++--------- mkdocs.yml | 1 + netbox/extras/plugins/__init__.py | 30 ++++++++++++--- netbox/extras/tests/dummy_plugin/graphql.py | 21 +++++++++++ netbox/extras/tests/test_plugins.py | 10 +++++ netbox/netbox/graphql/fields.py | 13 +++---- netbox/netbox/graphql/schema.py | 2 + 8 files changed, 124 insertions(+), 31 deletions(-) create mode 100644 docs/plugins/development/graphql.md create mode 100644 netbox/extras/tests/dummy_plugin/graphql.py diff --git a/docs/plugins/development/graphql.md b/docs/plugins/development/graphql.md new file mode 100644 index 000000000..372498516 --- /dev/null +++ b/docs/plugins/development/graphql.md @@ -0,0 +1,41 @@ +# GraphQL API + +## Defining the Schema Class + +A plugin can extend NetBox's GraphQL API by registering its own schema class. By default, NetBox will attempt to import `graphql.schema` from the plugin, if it exists. This path can be overridden by defining `graphql_schema` on the PluginConfig instance as the dotted path to the desired Python class. This class must be a subclass of `graphene.ObjectType`. + +### Example + +```python +# graphql.py +import graphene +from netbox.graphql.fields import ObjectField, ObjectListField +from . import filtersets, models + +class MyModelType(graphene.ObjectType): + + class Meta: + model = models.MyModel + fields = '__all__' + filterset_class = filtersets.MyModelFilterSet + +class MyQuery(graphene.ObjectType): + mymodel = ObjectField(MyModelType) + mymodel_list = ObjectListField(MyModelType) + +schema = MyQuery +``` + +## GraphQL Fields + +::: netbox.graphql.fields.ObjectField + selection: + members: false + rendering: + show_source: false + +::: netbox.graphql.fields.ObjectListField + selection: + members: false + rendering: + show_source: false diff --git a/docs/plugins/development/index.md b/docs/plugins/development/index.md index 07a04f39f..fa8dfa556 100644 --- a/docs/plugins/development/index.md +++ b/docs/plugins/development/index.md @@ -22,7 +22,7 @@ However, keep in mind that each piece of functionality is entirely optional. For ### Plugin Structure -Although the specific structure of a plugin is largely left to the discretion of its authors, a typical NetBox plugin looks something like this: +Although the specific structure of a plugin is largely left to the discretion of its authors, a typical NetBox plugin might look something like this: ```no-highlight project-name/ @@ -102,23 +102,24 @@ NetBox looks for the `config` variable within a plugin's `__init__.py` to load i #### PluginConfig Attributes -| Name | Description | -| ---- |---------------------------------------------------------------------------------------------------------------| -| `name` | Raw plugin name; same as the plugin's source directory | -| `verbose_name` | Human-friendly name for the plugin | -| `version` | Current release ([semantic versioning](https://semver.org/) is encouraged) | -| `description` | Brief description of the plugin's purpose | -| `author` | Name of plugin's author | -| `author_email` | Author's public email address | -| `base_url` | Base path to use for plugin URLs (optional). If not specified, the project's `name` will be used. | -| `required_settings` | A list of any configuration parameters that **must** be defined by the user | -| `default_settings` | A dictionary of configuration parameters and their default values | -| `min_version` | Minimum version of NetBox with which the plugin is compatible | -| `max_version` | Maximum version of NetBox with which the plugin is compatible | -| `middleware` | A list of middleware classes to append after NetBox's build-in middleware | -| `template_extensions` | The dotted path to the list of template extension classes (default: `template_content.template_extensions`) | -| `menu_items` | The dotted path to the list of menu items provided by the plugin (default: `navigation.menu_items`) | -| `user_preferences` | The dotted path to the dictionary mapping of user preferences defined by the plugin (default: `preferences.preferences`) | +| Name | Description | +|-----------------------|--------------------------------------------------------------------------------------------------------------------------| +| `name` | Raw plugin name; same as the plugin's source directory | +| `verbose_name` | Human-friendly name for the plugin | +| `version` | Current release ([semantic versioning](https://semver.org/) is encouraged) | +| `description` | Brief description of the plugin's purpose | +| `author` | Name of plugin's author | +| `author_email` | Author's public email address | +| `base_url` | Base path to use for plugin URLs (optional). If not specified, the project's `name` will be used. | +| `required_settings` | A list of any configuration parameters that **must** be defined by the user | +| `default_settings` | A dictionary of configuration parameters and their default values | +| `min_version` | Minimum version of NetBox with which the plugin is compatible | +| `max_version` | Maximum version of NetBox with which the plugin is compatible | +| `middleware` | A list of middleware classes to append after NetBox's build-in middleware | +| `template_extensions` | The dotted path to the list of template extension classes (default: `template_content.template_extensions`) | +| `menu_items` | The dotted path to the list of menu items provided by the plugin (default: `navigation.menu_items`) | +| `graphql_schema` | The dotted path to the plugin's GraphQL schema class, if any (default: `graphql.schema`) | +| `user_preferences` | The dotted path to the dictionary mapping of user preferences defined by the plugin (default: `preferences.preferences`) | All required settings must be configured by the user. If a configuration parameter is listed in both `required_settings` and `default_settings`, the default setting will be ignored. diff --git a/mkdocs.yml b/mkdocs.yml index 004f21c5e..bc582cb2f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -108,6 +108,7 @@ nav: - Forms: 'plugins/development/forms.md' - Filter Sets: 'plugins/development/filtersets.md' - REST API: 'plugins/development/rest-api.md' + - GraphQL API: 'plugins/development/graphql.md' - Background Tasks: 'plugins/development/background-tasks.md' - Administration: - Authentication: 'administration/authentication.md' diff --git a/netbox/extras/plugins/__init__.py b/netbox/extras/plugins/__init__.py index f297d43e1..cef537bd8 100644 --- a/netbox/extras/plugins/__init__.py +++ b/netbox/extras/plugins/__init__.py @@ -14,9 +14,10 @@ from extras.plugins.utils import import_object # Initialize plugin registry registry['plugins'] = { - 'template_extensions': collections.defaultdict(list), + 'graphql_schemas': [], 'menu_items': {}, 'preferences': {}, + 'template_extensions': collections.defaultdict(list), } @@ -55,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) @@ -71,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 @@ -180,7 +187,7 @@ 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: @@ -254,6 +261,17 @@ def register_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) + + # # User preferences # diff --git a/netbox/extras/tests/dummy_plugin/graphql.py b/netbox/extras/tests/dummy_plugin/graphql.py new file mode 100644 index 000000000..27ecd9ce0 --- /dev/null +++ b/netbox/extras/tests/dummy_plugin/graphql.py @@ -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 diff --git a/netbox/extras/tests/test_plugins.py b/netbox/extras/tests/test_plugins.py index 608fc58dc..299cab9ef 100644 --- a/netbox/extras/tests/test_plugins.py +++ b/netbox/extras/tests/test_plugins.py @@ -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") @@ -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)) diff --git a/netbox/netbox/graphql/fields.py b/netbox/netbox/graphql/fields.py index e3ef39f4a..57685389e 100644 --- a/netbox/netbox/graphql/fields.py +++ b/netbox/netbox/graphql/fields.py @@ -41,15 +41,14 @@ class ObjectListField(DjangoListField): Retrieve a list of objects, optionally filtered by one or more FilterSet filters. """ def __init__(self, _type, *args, **kwargs): - - assert hasattr(_type._meta, 'filterset_class'), "DjangoFilterListField must define filterset_class under Meta" - filterset_class = _type._meta.filterset_class + filter_kwargs = {} # Get FilterSet kwargs - filter_kwargs = {} - for filter_name, filter_field in filterset_class.get_filters().items(): - field_type = get_graphene_type(type(filter_field)) - filter_kwargs[filter_name] = graphene.Argument(field_type) + filterset_class = getattr(_type._meta, 'filterset_class', None) + if filterset_class: + for filter_name, filter_field in filterset_class.get_filters().items(): + field_type = get_graphene_type(type(filter_field)) + filter_kwargs[filter_name] = graphene.Argument(field_type) super().__init__(_type, args=filter_kwargs, *args, **kwargs) diff --git a/netbox/netbox/graphql/schema.py b/netbox/netbox/graphql/schema.py index 812c1656d..f0bc8559c 100644 --- a/netbox/netbox/graphql/schema.py +++ b/netbox/netbox/graphql/schema.py @@ -3,6 +3,7 @@ import graphene from circuits.graphql.schema import CircuitsQuery from dcim.graphql.schema import DCIMQuery from extras.graphql.schema import ExtrasQuery +from extras.registry import registry from ipam.graphql.schema import IPAMQuery from tenancy.graphql.schema import TenancyQuery from users.graphql.schema import UsersQuery @@ -19,6 +20,7 @@ class Query( UsersQuery, VirtualizationQuery, WirelessQuery, + *registry['plugins']['graphql_schemas'], # Append plugin schemas graphene.ObjectType ): pass From dae5c94be0d9a8fb22dbe0f0e4c9cf94db6d0bb0 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 4 Feb 2022 15:07:35 -0500 Subject: [PATCH 3/3] Expose BaseObjectType and NetBoxObjectType for plugins --- docs/plugins/development/graphql.md | 18 ++++++++++++++++++ netbox/circuits/graphql/types.py | 8 ++++---- netbox/dcim/graphql/types.py | 22 +++++++++++----------- netbox/ipam/graphql/types.py | 24 ++++++++++++------------ netbox/netbox/graphql/types.py | 9 +++++---- netbox/tenancy/graphql/types.py | 6 +++--- netbox/virtualization/graphql/types.py | 6 +++--- netbox/wireless/graphql/types.py | 6 +++--- 8 files changed, 59 insertions(+), 40 deletions(-) diff --git a/docs/plugins/development/graphql.md b/docs/plugins/development/graphql.md index 372498516..411b16936 100644 --- a/docs/plugins/development/graphql.md +++ b/docs/plugins/development/graphql.md @@ -26,8 +26,26 @@ class MyQuery(graphene.ObjectType): schema = MyQuery ``` +## GraphQL Objects + +NetBox provides two object type classes for use by plugins. + +::: netbox.graphql.types.BaseObjectType + selection: + members: false + rendering: + show_source: false + +::: netbox.graphql.types.NetBoxObjectType + selection: + members: false + rendering: + show_source: false + ## GraphQL Fields +NetBox provides two field classes for use by plugins. + ::: netbox.graphql.fields.ObjectField selection: members: false diff --git a/netbox/circuits/graphql/types.py b/netbox/circuits/graphql/types.py index a6c28c4cd..027b53203 100644 --- a/netbox/circuits/graphql/types.py +++ b/netbox/circuits/graphql/types.py @@ -1,5 +1,5 @@ from circuits import filtersets, models -from netbox.graphql.types import ObjectType, OrganizationalObjectType, PrimaryObjectType +from netbox.graphql.types import ObjectType, OrganizationalObjectType, NetBoxObjectType __all__ = ( 'CircuitTerminationType', @@ -18,7 +18,7 @@ class CircuitTerminationType(ObjectType): filterset_class = filtersets.CircuitTerminationFilterSet -class CircuitType(PrimaryObjectType): +class CircuitType(NetBoxObjectType): class Meta: model = models.Circuit @@ -34,7 +34,7 @@ class CircuitTypeType(OrganizationalObjectType): filterset_class = filtersets.CircuitTypeFilterSet -class ProviderType(PrimaryObjectType): +class ProviderType(NetBoxObjectType): class Meta: model = models.Provider @@ -42,7 +42,7 @@ class ProviderType(PrimaryObjectType): filterset_class = filtersets.ProviderFilterSet -class ProviderNetworkType(PrimaryObjectType): +class ProviderNetworkType(NetBoxObjectType): class Meta: model = models.ProviderNetwork diff --git a/netbox/dcim/graphql/types.py b/netbox/dcim/graphql/types.py index a47ca40ca..d25a6bba6 100644 --- a/netbox/dcim/graphql/types.py +++ b/netbox/dcim/graphql/types.py @@ -6,7 +6,7 @@ from extras.graphql.mixins import ( ) from ipam.graphql.mixins import IPAddressesMixin, VLANGroupsMixin from netbox.graphql.scalars import BigInt -from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, PrimaryObjectType +from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, NetBoxObjectType __all__ = ( 'CableType', @@ -85,7 +85,7 @@ class ComponentTemplateObjectType( # Model types # -class CableType(PrimaryObjectType): +class CableType(NetBoxObjectType): class Meta: model = models.Cable @@ -143,7 +143,7 @@ class ConsoleServerPortTemplateType(ComponentTemplateObjectType): return self.type or None -class DeviceType(ConfigContextMixin, ImageAttachmentsMixin, PrimaryObjectType): +class DeviceType(ConfigContextMixin, ImageAttachmentsMixin, NetBoxObjectType): class Meta: model = models.Device @@ -189,7 +189,7 @@ class DeviceRoleType(OrganizationalObjectType): filterset_class = filtersets.DeviceRoleFilterSet -class DeviceTypeType(PrimaryObjectType): +class DeviceTypeType(NetBoxObjectType): class Meta: model = models.DeviceType @@ -300,7 +300,7 @@ class ModuleBayTemplateType(ComponentTemplateObjectType): filterset_class = filtersets.ModuleBayTemplateFilterSet -class ModuleTypeType(PrimaryObjectType): +class ModuleTypeType(NetBoxObjectType): class Meta: model = models.ModuleType @@ -316,7 +316,7 @@ class PlatformType(OrganizationalObjectType): filterset_class = filtersets.PlatformFilterSet -class PowerFeedType(PrimaryObjectType): +class PowerFeedType(NetBoxObjectType): class Meta: model = models.PowerFeed @@ -352,7 +352,7 @@ class PowerOutletTemplateType(ComponentTemplateObjectType): return self.type or None -class PowerPanelType(PrimaryObjectType): +class PowerPanelType(NetBoxObjectType): class Meta: model = models.PowerPanel @@ -382,7 +382,7 @@ class PowerPortTemplateType(ComponentTemplateObjectType): return self.type or None -class RackType(VLANGroupsMixin, ImageAttachmentsMixin, PrimaryObjectType): +class RackType(VLANGroupsMixin, ImageAttachmentsMixin, NetBoxObjectType): class Meta: model = models.Rack @@ -396,7 +396,7 @@ class RackType(VLANGroupsMixin, ImageAttachmentsMixin, PrimaryObjectType): return self.outer_unit or None -class RackReservationType(PrimaryObjectType): +class RackReservationType(NetBoxObjectType): class Meta: model = models.RackReservation @@ -436,7 +436,7 @@ class RegionType(VLANGroupsMixin, OrganizationalObjectType): filterset_class = filtersets.RegionFilterSet -class SiteType(VLANGroupsMixin, ImageAttachmentsMixin, PrimaryObjectType): +class SiteType(VLANGroupsMixin, ImageAttachmentsMixin, NetBoxObjectType): asn = graphene.Field(BigInt) class Meta: @@ -453,7 +453,7 @@ class SiteGroupType(VLANGroupsMixin, OrganizationalObjectType): filterset_class = filtersets.SiteGroupFilterSet -class VirtualChassisType(PrimaryObjectType): +class VirtualChassisType(NetBoxObjectType): class Meta: model = models.VirtualChassis diff --git a/netbox/ipam/graphql/types.py b/netbox/ipam/graphql/types.py index 8dd122a0c..ca206b4b8 100644 --- a/netbox/ipam/graphql/types.py +++ b/netbox/ipam/graphql/types.py @@ -2,7 +2,7 @@ import graphene from ipam import filtersets, models from netbox.graphql.scalars import BigInt -from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, PrimaryObjectType +from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, NetBoxObjectType __all__ = ( 'ASNType', @@ -23,7 +23,7 @@ __all__ = ( ) -class ASNType(PrimaryObjectType): +class ASNType(NetBoxObjectType): asn = graphene.Field(BigInt) class Meta: @@ -32,7 +32,7 @@ class ASNType(PrimaryObjectType): filterset_class = filtersets.ASNFilterSet -class AggregateType(PrimaryObjectType): +class AggregateType(NetBoxObjectType): class Meta: model = models.Aggregate @@ -40,7 +40,7 @@ class AggregateType(PrimaryObjectType): filterset_class = filtersets.AggregateFilterSet -class FHRPGroupType(PrimaryObjectType): +class FHRPGroupType(NetBoxObjectType): class Meta: model = models.FHRPGroup @@ -59,7 +59,7 @@ class FHRPGroupAssignmentType(BaseObjectType): filterset_class = filtersets.FHRPGroupAssignmentFilterSet -class IPAddressType(PrimaryObjectType): +class IPAddressType(NetBoxObjectType): class Meta: model = models.IPAddress @@ -70,7 +70,7 @@ class IPAddressType(PrimaryObjectType): return self.role or None -class IPRangeType(PrimaryObjectType): +class IPRangeType(NetBoxObjectType): class Meta: model = models.IPRange @@ -81,7 +81,7 @@ class IPRangeType(PrimaryObjectType): return self.role or None -class PrefixType(PrimaryObjectType): +class PrefixType(NetBoxObjectType): class Meta: model = models.Prefix @@ -105,7 +105,7 @@ class RoleType(OrganizationalObjectType): filterset_class = filtersets.RoleFilterSet -class RouteTargetType(PrimaryObjectType): +class RouteTargetType(NetBoxObjectType): class Meta: model = models.RouteTarget @@ -113,7 +113,7 @@ class RouteTargetType(PrimaryObjectType): filterset_class = filtersets.RouteTargetFilterSet -class ServiceType(PrimaryObjectType): +class ServiceType(NetBoxObjectType): class Meta: model = models.Service @@ -121,7 +121,7 @@ class ServiceType(PrimaryObjectType): filterset_class = filtersets.ServiceFilterSet -class ServiceTemplateType(PrimaryObjectType): +class ServiceTemplateType(NetBoxObjectType): class Meta: model = models.ServiceTemplate @@ -129,7 +129,7 @@ class ServiceTemplateType(PrimaryObjectType): filterset_class = filtersets.ServiceTemplateFilterSet -class VLANType(PrimaryObjectType): +class VLANType(NetBoxObjectType): class Meta: model = models.VLAN @@ -145,7 +145,7 @@ class VLANGroupType(OrganizationalObjectType): filterset_class = filtersets.VLANGroupFilterSet -class VRFType(PrimaryObjectType): +class VRFType(NetBoxObjectType): class Meta: model = models.VRF diff --git a/netbox/netbox/graphql/types.py b/netbox/netbox/graphql/types.py index 7d71bd1fb..7d1b26f84 100644 --- a/netbox/netbox/graphql/types.py +++ b/netbox/netbox/graphql/types.py @@ -5,8 +5,9 @@ from extras.graphql.mixins import ChangelogMixin, CustomFieldsMixin, JournalEntr __all__ = ( 'BaseObjectType', + 'ObjectType', 'OrganizationalObjectType', - 'PrimaryObjectType', + 'NetBoxObjectType', ) @@ -16,7 +17,7 @@ __all__ = ( class BaseObjectType(DjangoObjectType): """ - Base GraphQL object type for all NetBox objects + Base GraphQL object type for all NetBox objects. Restricts the model queryset to enforce object permissions. """ class Meta: abstract = True @@ -51,7 +52,7 @@ class OrganizationalObjectType( abstract = True -class PrimaryObjectType( +class NetBoxObjectType( ChangelogMixin, CustomFieldsMixin, JournalEntriesMixin, @@ -59,7 +60,7 @@ class PrimaryObjectType( BaseObjectType ): """ - Base type for primary models + GraphQL type for most NetBox models. Includes support for custom fields, change logging, journaling, and tags. """ class Meta: abstract = True diff --git a/netbox/tenancy/graphql/types.py b/netbox/tenancy/graphql/types.py index ce00eafa3..e0b99c2eb 100644 --- a/netbox/tenancy/graphql/types.py +++ b/netbox/tenancy/graphql/types.py @@ -1,7 +1,7 @@ import graphene from tenancy import filtersets, models -from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, PrimaryObjectType +from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, NetBoxObjectType __all__ = ( 'ContactAssignmentType', @@ -24,7 +24,7 @@ class ContactAssignmentsMixin: # Tenants # -class TenantType(PrimaryObjectType): +class TenantType(NetBoxObjectType): class Meta: model = models.Tenant @@ -44,7 +44,7 @@ class TenantGroupType(OrganizationalObjectType): # Contacts # -class ContactType(ContactAssignmentsMixin, PrimaryObjectType): +class ContactType(ContactAssignmentsMixin, NetBoxObjectType): class Meta: model = models.Contact diff --git a/netbox/virtualization/graphql/types.py b/netbox/virtualization/graphql/types.py index c21b6f9a1..96b0fc875 100644 --- a/netbox/virtualization/graphql/types.py +++ b/netbox/virtualization/graphql/types.py @@ -1,7 +1,7 @@ from dcim.graphql.types import ComponentObjectType from extras.graphql.mixins import ConfigContextMixin from ipam.graphql.mixins import IPAddressesMixin, VLANGroupsMixin -from netbox.graphql.types import OrganizationalObjectType, PrimaryObjectType +from netbox.graphql.types import OrganizationalObjectType, NetBoxObjectType from virtualization import filtersets, models __all__ = ( @@ -13,7 +13,7 @@ __all__ = ( ) -class ClusterType(VLANGroupsMixin, PrimaryObjectType): +class ClusterType(VLANGroupsMixin, NetBoxObjectType): class Meta: model = models.Cluster @@ -37,7 +37,7 @@ class ClusterTypeType(OrganizationalObjectType): filterset_class = filtersets.ClusterTypeFilterSet -class VirtualMachineType(ConfigContextMixin, PrimaryObjectType): +class VirtualMachineType(ConfigContextMixin, NetBoxObjectType): class Meta: model = models.VirtualMachine diff --git a/netbox/wireless/graphql/types.py b/netbox/wireless/graphql/types.py index c3235e72e..2fc477dfa 100644 --- a/netbox/wireless/graphql/types.py +++ b/netbox/wireless/graphql/types.py @@ -1,5 +1,5 @@ from wireless import filtersets, models -from netbox.graphql.types import OrganizationalObjectType, PrimaryObjectType +from netbox.graphql.types import OrganizationalObjectType, NetBoxObjectType __all__ = ( 'WirelessLANType', @@ -16,7 +16,7 @@ class WirelessLANGroupType(OrganizationalObjectType): filterset_class = filtersets.WirelessLANGroupFilterSet -class WirelessLANType(PrimaryObjectType): +class WirelessLANType(NetBoxObjectType): class Meta: model = models.WirelessLAN @@ -30,7 +30,7 @@ class WirelessLANType(PrimaryObjectType): return self.auth_cipher or None -class WirelessLinkType(PrimaryObjectType): +class WirelessLinkType(NetBoxObjectType): class Meta: model = models.WirelessLink