diff --git a/docs/graphql-api/overview.md b/docs/graphql-api/overview.md index b202d77af..f1ce4f455 100644 --- a/docs/graphql-api/overview.md +++ b/docs/graphql-api/overview.md @@ -42,8 +42,10 @@ The response will include the requested data formatted as JSON: NetBox provides both a singular and plural query field for each object type: -* `object`: Returns a single object. Must specify the object's unique ID as `(id: 123)`. -* `objects`: Returns a list of objects, optionally filtered by given parameters. +* `$OBJECT`: Returns a single object. Must specify the object's unique ID as `(id: 123)`. +* `$OBJECT_list`: Returns a list of objects, optionally filtered by given parameters. + +For example, query `device(id:123)` to fetch a specific device (identified by its unique ID), and query `device_list` (with an optional set of fitlers) to fetch all devices. For more detail on constructing GraphQL queries, see the [Graphene documentation](https://docs.graphene-python.org/en/latest/). diff --git a/netbox/circuits/graphql/schema.py b/netbox/circuits/graphql/schema.py index 5a3f42270..f65874239 100644 --- a/netbox/circuits/graphql/schema.py +++ b/netbox/circuits/graphql/schema.py @@ -6,16 +6,16 @@ from .types import * class CircuitsQuery(graphene.ObjectType): circuit = ObjectField(CircuitType) - circuits = ObjectListField(CircuitType) + circuit_list = ObjectListField(CircuitType) circuit_termination = ObjectField(CircuitTerminationType) - circuit_terminations = ObjectListField(CircuitTerminationType) + circuit_termination_list = ObjectListField(CircuitTerminationType) circuit_type = ObjectField(CircuitTypeType) - circuit_types = ObjectListField(CircuitTypeType) + circuit_type_list = ObjectListField(CircuitTypeType) provider = ObjectField(ProviderType) - providers = ObjectListField(ProviderType) + provider_list = ObjectListField(ProviderType) provider_network = ObjectField(ProviderNetworkType) - provider_networks = ObjectListField(ProviderNetworkType) + provider_network_list = ObjectListField(ProviderNetworkType) diff --git a/netbox/dcim/graphql/schema.py b/netbox/dcim/graphql/schema.py index d296c3ebc..13e0c20ec 100644 --- a/netbox/dcim/graphql/schema.py +++ b/netbox/dcim/graphql/schema.py @@ -6,101 +6,100 @@ from .types import * class DCIMQuery(graphene.ObjectType): cable = ObjectField(CableType) - cables = ObjectListField(CableType) + cable_list = ObjectListField(CableType) console_port = ObjectField(ConsolePortType) - console_ports = ObjectListField(ConsolePortType) + console_port_list = ObjectListField(ConsolePortType) console_port_template = ObjectField(ConsolePortTemplateType) - console_port_templates = ObjectListField(ConsolePortTemplateType) + console_port_template_list = ObjectListField(ConsolePortTemplateType) console_server_port = ObjectField(ConsoleServerPortType) - console_server_ports = ObjectListField(ConsoleServerPortType) + console_server_port_list = ObjectListField(ConsoleServerPortType) console_server_port_template = ObjectField(ConsoleServerPortTemplateType) - console_server_port_templates = ObjectListField(ConsoleServerPortTemplateType) + console_server_port_template_list = ObjectListField(ConsoleServerPortTemplateType) device = ObjectField(DeviceType) - devices = ObjectListField(DeviceType) + device_list = ObjectListField(DeviceType) device_bay = ObjectField(DeviceBayType) - device_bays = ObjectListField(DeviceBayType) + device_bay_list = ObjectListField(DeviceBayType) device_bay_template = ObjectField(DeviceBayTemplateType) - device_bay_templates = ObjectListField(DeviceBayTemplateType) + device_bay_template_list = ObjectListField(DeviceBayTemplateType) device_role = ObjectField(DeviceRoleType) - device_roles = ObjectListField(DeviceRoleType) + device_role_list = ObjectListField(DeviceRoleType) device_type = ObjectField(DeviceTypeType) - device_types = ObjectListField(DeviceTypeType) + device_type_list = ObjectListField(DeviceTypeType) front_port = ObjectField(FrontPortType) - front_ports = ObjectListField(FrontPortType) + front_port_list = ObjectListField(FrontPortType) front_port_template = ObjectField(FrontPortTemplateType) - front_port_templates = ObjectListField(FrontPortTemplateType) + front_port_template_list = ObjectListField(FrontPortTemplateType) interface = ObjectField(InterfaceType) - interfaces = ObjectListField(InterfaceType) + interface_list = ObjectListField(InterfaceType) interface_template = ObjectField(InterfaceTemplateType) - interface_templates = ObjectListField(InterfaceTemplateType) + interface_template_list = ObjectListField(InterfaceTemplateType) inventory_item = ObjectField(InventoryItemType) - inventory_items = ObjectListField(InventoryItemType) + inventory_item_list = ObjectListField(InventoryItemType) location = ObjectField(LocationType) - locations = ObjectListField(LocationType) + location_list = ObjectListField(LocationType) manufacturer = ObjectField(ManufacturerType) - manufacturers = ObjectListField(ManufacturerType) + manufacturer_list = ObjectListField(ManufacturerType) platform = ObjectField(PlatformType) - platforms = ObjectListField(PlatformType) + platform_list = ObjectListField(PlatformType) power_feed = ObjectField(PowerFeedType) - power_feeds = ObjectListField(PowerFeedType) + power_feed_list = ObjectListField(PowerFeedType) power_outlet = ObjectField(PowerOutletType) - power_outlets = ObjectListField(PowerOutletType) + power_outlet_list = ObjectListField(PowerOutletType) power_outlet_template = ObjectField(PowerOutletTemplateType) - power_outlet_templates = ObjectListField(PowerOutletTemplateType) + power_outlet_template_list = ObjectListField(PowerOutletTemplateType) power_panel = ObjectField(PowerPanelType) - power_panels = ObjectListField(PowerPanelType) + power_panel_list = ObjectListField(PowerPanelType) power_port = ObjectField(PowerPortType) - power_ports = ObjectListField(PowerPortType) + power_port_list = ObjectListField(PowerPortType) power_port_template = ObjectField(PowerPortTemplateType) - power_port_templates = ObjectListField(PowerPortTemplateType) + power_port_template_list = ObjectListField(PowerPortTemplateType) rack = ObjectField(RackType) - racks = ObjectListField(RackType) + rack_list = ObjectListField(RackType) rack_reservation = ObjectField(RackReservationType) - rack_reservations = ObjectListField(RackReservationType) + rack_reservation_list = ObjectListField(RackReservationType) rack_role = ObjectField(RackRoleType) - rack_roles = ObjectListField(RackRoleType) + rack_role_list = ObjectListField(RackRoleType) rear_port = ObjectField(RearPortType) - rear_ports = ObjectListField(RearPortType) + rear_port_list = ObjectListField(RearPortType) rear_port_template = ObjectField(RearPortTemplateType) - rear_port_templates = ObjectListField(RearPortTemplateType) + rear_port_template_list = ObjectListField(RearPortTemplateType) region = ObjectField(RegionType) - regions = ObjectListField(RegionType) + region_list = ObjectListField(RegionType) site = ObjectField(SiteType) - sites = ObjectListField(SiteType) + site_list = ObjectListField(SiteType) site_group = ObjectField(SiteGroupType) - site_groups = ObjectListField(SiteGroupType) + site_group_list = ObjectListField(SiteGroupType) virtual_chassis = ObjectField(VirtualChassisType) - # TODO: Rectify list field name virtual_chassis_list = ObjectListField(VirtualChassisType) diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index 9d238395e..5402405c5 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -1549,8 +1549,6 @@ class VirtualChassisTest(APIViewTestCases.GraphQLTestCase, APIViewTestCases.APIV model = VirtualChassis brief_fields = ['id', 'master', 'member_count', 'name', 'url'] - graphql_base_name_plural = 'virtual_chassis_list' - @classmethod def setUpTestData(cls): site = Site.objects.create(name='Test Site', slug='test-site') diff --git a/netbox/extras/graphql/schema.py b/netbox/extras/graphql/schema.py index a0456e7cc..3073976e8 100644 --- a/netbox/extras/graphql/schema.py +++ b/netbox/extras/graphql/schema.py @@ -6,25 +6,25 @@ from .types import * class ExtrasQuery(graphene.ObjectType): config_context = ObjectField(ConfigContextType) - config_contexts = ObjectListField(ConfigContextType) + config_context_list = ObjectListField(ConfigContextType) custom_field = ObjectField(CustomFieldType) - custom_fields = ObjectListField(CustomFieldType) + custom_field_list = ObjectListField(CustomFieldType) custom_link = ObjectField(CustomLinkType) - custom_links = ObjectListField(CustomLinkType) + custom_link_list = ObjectListField(CustomLinkType) export_template = ObjectField(ExportTemplateType) - export_templates = ObjectListField(ExportTemplateType) + export_template_list = ObjectListField(ExportTemplateType) image_attachment = ObjectField(ImageAttachmentType) - image_attachments = ObjectListField(ImageAttachmentType) + image_attachment_list = ObjectListField(ImageAttachmentType) journal_entry = ObjectField(JournalEntryType) - journal_entries = ObjectListField(JournalEntryType) + journal_entry_list = ObjectListField(JournalEntryType) tag = ObjectField(TagType) - tags = ObjectListField(TagType) + tag_list = ObjectListField(TagType) webhook = ObjectField(WebhookType) - webhooks = ObjectListField(WebhookType) + webhook_list = ObjectListField(WebhookType) diff --git a/netbox/ipam/graphql/schema.py b/netbox/ipam/graphql/schema.py index d0ba11fc7..0d4b931c7 100644 --- a/netbox/ipam/graphql/schema.py +++ b/netbox/ipam/graphql/schema.py @@ -6,31 +6,31 @@ from .types import * class IPAMQuery(graphene.ObjectType): aggregate = ObjectField(AggregateType) - aggregates = ObjectListField(AggregateType) + aggregate_list = ObjectListField(AggregateType) ip_address = ObjectField(IPAddressType) - ip_addresses = ObjectListField(IPAddressType) + ip_address_list = ObjectListField(IPAddressType) prefix = ObjectField(PrefixType) - prefixes = ObjectListField(PrefixType) + prefix_list = ObjectListField(PrefixType) rir = ObjectField(RIRType) - rirs = ObjectListField(RIRType) + rir_list = ObjectListField(RIRType) role = ObjectField(RoleType) - roles = ObjectListField(RoleType) + role_list = ObjectListField(RoleType) route_target = ObjectField(RouteTargetType) - route_targets = ObjectListField(RouteTargetType) + route_target_list = ObjectListField(RouteTargetType) service = ObjectField(ServiceType) - services = ObjectListField(ServiceType) + service_list = ObjectListField(ServiceType) vlan = ObjectField(VLANType) - vlans = ObjectListField(VLANType) + vlan_list = ObjectListField(VLANType) vlan_group = ObjectField(VLANGroupType) - vlan_groups = ObjectListField(VLANGroupType) + vlan_group_list = ObjectListField(VLANGroupType) vrf = ObjectField(VRFType) - vrfs = ObjectListField(VRFType) + vrf_list = ObjectListField(VRFType) diff --git a/netbox/tenancy/graphql/schema.py b/netbox/tenancy/graphql/schema.py index 5a2f7438b..f420eb787 100644 --- a/netbox/tenancy/graphql/schema.py +++ b/netbox/tenancy/graphql/schema.py @@ -6,7 +6,7 @@ from .types import * class TenancyQuery(graphene.ObjectType): tenant = ObjectField(TenantType) - tenants = ObjectListField(TenantType) + tenant_list = ObjectListField(TenantType) tenant_group = ObjectField(TenantGroupType) - tenant_groups = ObjectListField(TenantGroupType) + tenant_group_list = ObjectListField(TenantGroupType) diff --git a/netbox/users/graphql/schema.py b/netbox/users/graphql/schema.py index b25c8f33d..4a58be128 100644 --- a/netbox/users/graphql/schema.py +++ b/netbox/users/graphql/schema.py @@ -6,7 +6,7 @@ from .types import * class UsersQuery(graphene.ObjectType): group = ObjectField(GroupType) - groups = ObjectListField(GroupType) + group_list = ObjectListField(GroupType) user = ObjectField(UserType) - users = ObjectListField(UserType) + user_list = ObjectListField(UserType) diff --git a/netbox/utilities/testing/api.py b/netbox/utilities/testing/api.py index fd18259d1..89f463cdf 100644 --- a/netbox/utilities/testing/api.py +++ b/netbox/utilities/testing/api.py @@ -24,7 +24,7 @@ __all__ = ( # -# REST API Tests +# REST/GraphQL API Tests # class APITestCase(ModelTestCase): @@ -427,11 +427,13 @@ class APIViewTestCases: class GraphQLTestCase(APITestCase): - def _get_graphql_base_name(self, plural=False): - if plural: - return getattr(self, 'graphql_base_name_plural', - self.model._meta.verbose_name_plural.lower().replace(' ', '_')) - return getattr(self, 'graphql_base_name', self.model._meta.verbose_name.lower().replace(' ', '_')) + def _get_graphql_base_name(self): + """ + Return graphql_base_name, if set. Otherwise, construct the base name for the query + field from the model's verbose name. + """ + base_name = self.model._meta.verbose_name.lower().replace(' ', '_') + return getattr(self, 'graphql_base_name', base_name) def _build_query(self, name, **filters): type_class = get_graphql_type_for_model(self.model) @@ -466,9 +468,9 @@ class APIViewTestCases: @override_settings(LOGIN_REQUIRED=True) def test_graphql_get_object(self): url = reverse('graphql') - object_type = self._get_graphql_base_name() + field_name = self._get_graphql_base_name() object_id = self._get_queryset().first().pk - query = self._build_query(object_type, id=object_id) + query = self._build_query(field_name, id=object_id) # Non-authenticated requests should fail with disable_warnings('django.request'): @@ -491,8 +493,8 @@ class APIViewTestCases: @override_settings(LOGIN_REQUIRED=True) def test_graphql_list_objects(self): url = reverse('graphql') - object_type = self._get_graphql_base_name(plural=True) - query = self._build_query(object_type) + field_name = f'{self._get_graphql_base_name()}_list' + query = self._build_query(field_name) # Non-authenticated requests should fail with disable_warnings('django.request'): @@ -511,7 +513,7 @@ class APIViewTestCases: self.assertHttpStatus(response, status.HTTP_200_OK) data = json.loads(response.content) self.assertNotIn('errors', data) - self.assertGreater(len(data['data'][object_type]), 0) + self.assertGreater(len(data['data'][field_name]), 0) class APIViewTestCase( GetObjectViewTestCase, diff --git a/netbox/virtualization/graphql/schema.py b/netbox/virtualization/graphql/schema.py index f7d9528f0..e22532214 100644 --- a/netbox/virtualization/graphql/schema.py +++ b/netbox/virtualization/graphql/schema.py @@ -6,16 +6,16 @@ from .types import * class VirtualizationQuery(graphene.ObjectType): cluster = ObjectField(ClusterType) - clusters = ObjectListField(ClusterType) + cluster_list = ObjectListField(ClusterType) cluster_group = ObjectField(ClusterGroupType) - cluster_groups = ObjectListField(ClusterGroupType) + cluster_group_list = ObjectListField(ClusterGroupType) cluster_type = ObjectField(ClusterTypeType) - cluster_types = ObjectListField(ClusterTypeType) + cluster_type_list = ObjectListField(ClusterTypeType) virtual_machine = ObjectField(VirtualMachineType) - virtual_machines = ObjectListField(VirtualMachineType) + virtual_machine_list = ObjectListField(VirtualMachineType) vm_interface = ObjectField(VMInterfaceType) - vm_interfaces = ObjectListField(VMInterfaceType) + vm_interface_list = ObjectListField(VMInterfaceType) diff --git a/netbox/virtualization/tests/test_api.py b/netbox/virtualization/tests/test_api.py index 1712e93b2..9157b8780 100644 --- a/netbox/virtualization/tests/test_api.py +++ b/netbox/virtualization/tests/test_api.py @@ -211,9 +211,7 @@ class VMInterfaceTest(APIViewTestCases.GraphQLTestCase, APIViewTestCases.APIView bulk_update_data = { 'description': 'New description', } - graphql_base_name = 'vm_interface' - graphql_base_name_plural = 'vm_interfaces' @classmethod def setUpTestData(cls):