From dc7e793c1e2f8617c89f64d2b39dd2e143f710a5 Mon Sep 17 00:00:00 2001 From: Arthur Date: Wed, 5 Oct 2022 16:37:28 -0700 Subject: [PATCH 1/6] 9817 add graphql l2vpntermination assigned_object --- netbox/ipam/graphql/gfk_mixins.py | 25 +++++++++++++++++++++++++ netbox/ipam/graphql/types.py | 5 ++++- 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 netbox/ipam/graphql/gfk_mixins.py diff --git a/netbox/ipam/graphql/gfk_mixins.py b/netbox/ipam/graphql/gfk_mixins.py new file mode 100644 index 000000000..055be911e --- /dev/null +++ b/netbox/ipam/graphql/gfk_mixins.py @@ -0,0 +1,25 @@ +import graphene +from dcim.graphql.types import InterfaceType +from dcim.models import Interface +from ipam.graphql.types import VLANType +from ipam.models import VLAN +from virtualization.graphql.types import VMInterfaceType +from virtualization.models import VMInterface + + +class L2VPNAssignmentType(graphene.Union): + class Meta: + types = ( + InterfaceType, + VLANType, + VMInterfaceType, + ) + + @classmethod + def resolve_type(cls, instance, info): + if type(instance) == Interface: + return InterfaceType + if type(instance) == VLAN: + return VLANType + if type(instance) == VMInterface: + return VMInterfaceType diff --git a/netbox/ipam/graphql/types.py b/netbox/ipam/graphql/types.py index 5af2ca72a..649e80c03 100644 --- a/netbox/ipam/graphql/types.py +++ b/netbox/ipam/graphql/types.py @@ -1,5 +1,6 @@ import graphene +from graphene_django import DjangoObjectType from ipam import filtersets, models from netbox.graphql.scalars import BigInt from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, NetBoxObjectType @@ -163,7 +164,9 @@ class L2VPNType(NetBoxObjectType): class L2VPNTerminationType(NetBoxObjectType): + assigned_object = graphene.Field('ipam.graphql.gfk_mixins.L2VPNAssignmentType') + class Meta: model = models.L2VPNTermination - fields = '__all__' + exclude = ('assigned_object_type', 'assigned_object_id') filtersets_class = filtersets.L2VPNTerminationFilterSet From 61b22745044bbcdbd6124c212ce8370aec666e7e Mon Sep 17 00:00:00 2001 From: Arthur Date: Wed, 5 Oct 2022 16:52:28 -0700 Subject: [PATCH 2/6] 9817 add graphql ipaddressassignment assigned_object --- netbox/ipam/graphql/gfk_mixins.py | 22 ++++++++++++++++++++-- netbox/ipam/graphql/types.py | 3 ++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/netbox/ipam/graphql/gfk_mixins.py b/netbox/ipam/graphql/gfk_mixins.py index 055be911e..c9ca617d2 100644 --- a/netbox/ipam/graphql/gfk_mixins.py +++ b/netbox/ipam/graphql/gfk_mixins.py @@ -1,12 +1,30 @@ import graphene from dcim.graphql.types import InterfaceType from dcim.models import Interface -from ipam.graphql.types import VLANType -from ipam.models import VLAN +from ipam.graphql.types import FHRPGroupType, VLANType +from ipam.models import FHRPGroup, VLAN from virtualization.graphql.types import VMInterfaceType from virtualization.models import VMInterface +class IPAddressAssignmentType(graphene.Union): + class Meta: + types = ( + InterfaceType, + FHRPGroupType, + VMInterfaceType, + ) + + @classmethod + def resolve_type(cls, instance, info): + if type(instance) == Interface: + return InterfaceType + if type(instance) == FHRPGroup: + return FHRPGroupType + if type(instance) == VMInterface: + return VMInterfaceType + + class L2VPNAssignmentType(graphene.Union): class Meta: types = ( diff --git a/netbox/ipam/graphql/types.py b/netbox/ipam/graphql/types.py index 649e80c03..4514c3acb 100644 --- a/netbox/ipam/graphql/types.py +++ b/netbox/ipam/graphql/types.py @@ -63,10 +63,11 @@ class FHRPGroupAssignmentType(BaseObjectType): class IPAddressType(NetBoxObjectType): + assigned_object = graphene.Field('ipam.graphql.gfk_mixins.IPAddressAssignmentType') class Meta: model = models.IPAddress - fields = '__all__' + exclude = ('assigned_object_type', 'assigned_object_id') filterset_class = filtersets.IPAddressFilterSet def resolve_role(self, info): From 0b38d40b48cd4e0376979c971c75f05e19d3306f Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 6 Oct 2022 10:07:09 -0700 Subject: [PATCH 3/6] 9817 ipan graphql gfk --- netbox/ipam/graphql/gfk_mixins.py | 62 ++++++++++++++++++++++++++++--- netbox/ipam/graphql/types.py | 6 ++- 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/netbox/ipam/graphql/gfk_mixins.py b/netbox/ipam/graphql/gfk_mixins.py index c9ca617d2..31742c4a4 100644 --- a/netbox/ipam/graphql/gfk_mixins.py +++ b/netbox/ipam/graphql/gfk_mixins.py @@ -1,10 +1,17 @@ import graphene -from dcim.graphql.types import InterfaceType -from dcim.models import Interface +from dcim.graphql.types import ( + InterfaceType, + LocationType, + RackType, + RegionType, + SiteGroupType, + SiteType, +) +from dcim.models import Interface, Location, Rack, Region, Site, SiteGroup from ipam.graphql.types import FHRPGroupType, VLANType -from ipam.models import FHRPGroup, VLAN -from virtualization.graphql.types import VMInterfaceType -from virtualization.models import VMInterface +from ipam.models import VLAN, FHRPGroup +from virtualization.graphql.types import ClusterGroupType, ClusterType, VMInterfaceType +from virtualization.models import Cluster, ClusterGroup, VMInterface class IPAddressAssignmentType(graphene.Union): @@ -41,3 +48,48 @@ class L2VPNAssignmentType(graphene.Union): return VLANType if type(instance) == VMInterface: return VMInterfaceType + + +class FHRPGroupInterfaceType(graphene.Union): + class Meta: + types = ( + InterfaceType, + VMInterfaceType, + ) + + @classmethod + def resolve_type(cls, instance, info): + if type(instance) == Interface: + return InterfaceType + if type(instance) == VMInterface: + return VMInterfaceType + + +class VLANGroupScopeType(graphene.Union): + class Meta: + types = ( + ClusterType, + ClusterGroupType, + LocationType, + RackType, + RegionType, + SiteType, + SiteGroupType, + ) + + @classmethod + def resolve_type(cls, instance, info): + if type(instance) == Cluster: + return ClusterType + if type(instance) == ClusterGroup: + return ClusterGroupType + if type(instance) == Location: + return LocationType + if type(instance) == Rack: + return RackType + if type(instance) == Region: + return RegionType + if type(instance) == Site: + return SiteType + if type(instance) == SiteGroup: + return SiteGroupType diff --git a/netbox/ipam/graphql/types.py b/netbox/ipam/graphql/types.py index 4514c3acb..4937b6a9f 100644 --- a/netbox/ipam/graphql/types.py +++ b/netbox/ipam/graphql/types.py @@ -55,10 +55,11 @@ class FHRPGroupType(NetBoxObjectType): class FHRPGroupAssignmentType(BaseObjectType): + interface = graphene.Field('ipam.graphql.gfk_mixins.FHRPGroupInterfaceType') class Meta: model = models.FHRPGroupAssignment - fields = '__all__' + exclude = ('interface_type', 'interface_id') filterset_class = filtersets.FHRPGroupAssignmentFilterSet @@ -142,10 +143,11 @@ class VLANType(NetBoxObjectType): class VLANGroupType(OrganizationalObjectType): + scope = graphene.Field('ipam.graphql.gfk_mixins.VLANGroupScopeType') class Meta: model = models.VLANGroup - fields = '__all__' + exclude = ('scope_type', 'scope_id') filterset_class = filtersets.VLANGroupFilterSet From 4fdf49de2a4f34b0db53df7801e7d000db8e4742 Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 6 Oct 2022 10:41:48 -0700 Subject: [PATCH 4/6] 9817 dcim graphql gfk --- netbox/dcim/graphql/types.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/netbox/dcim/graphql/types.py b/netbox/dcim/graphql/types.py index 78cabbcd1..33e07adaf 100644 --- a/netbox/dcim/graphql/types.py +++ b/netbox/dcim/graphql/types.py @@ -101,10 +101,11 @@ class CableType(NetBoxObjectType): class CableTerminationType(NetBoxObjectType): + termination = graphene.Field('dcim.graphql.gfk_mixins.CableTerminationTerminationType') class Meta: model = models.CableTermination - fields = '__all__' + exclude = ('termination_type', 'termination_id') filterset_class = filtersets.CableTerminationFilterSet @@ -183,10 +184,11 @@ class DeviceBayTemplateType(ComponentTemplateObjectType): class InventoryItemTemplateType(ComponentTemplateObjectType): + component = graphene.Field('dcim.graphql.gfk_mixins.InventoryItemTemplateComponentType') class Meta: model = models.InventoryItemTemplate - fields = '__all__' + exclude = ('component_type', 'component_id') filterset_class = filtersets.InventoryItemTemplateFilterSet @@ -269,10 +271,11 @@ class InterfaceTemplateType(ComponentTemplateObjectType): class InventoryItemType(ComponentObjectType): + component = graphene.Field('dcim.graphql.gfk_mixins.InventoryItemComponentType') class Meta: model = models.InventoryItem - fields = '__all__' + exclude = ('component_type', 'component_id') filterset_class = filtersets.InventoryItemFilterSet From 6bfec51efe52d9e0ac3486b3217e1e1d9d4f67bf Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 6 Oct 2022 10:42:00 -0700 Subject: [PATCH 5/6] 9817 dcim graphql gfk --- netbox/dcim/graphql/gfk_mixins.py | 133 ++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 netbox/dcim/graphql/gfk_mixins.py diff --git a/netbox/dcim/graphql/gfk_mixins.py b/netbox/dcim/graphql/gfk_mixins.py new file mode 100644 index 000000000..e8cb62ad2 --- /dev/null +++ b/netbox/dcim/graphql/gfk_mixins.py @@ -0,0 +1,133 @@ +import graphene +from circuits.graphql.types import CircuitTerminationType +from circuits.models import CircuitTermination +from dcim.graphql.types import ( + ConsolePortTemplateType, + ConsolePortType, + ConsoleServerPortTemplateType, + ConsoleServerPortType, + FrontPortTemplateType, + FrontPortType, + InterfaceTemplateType, + InterfaceType, + PowerFeedType, + PowerOutletTemplateType, + PowerOutletType, + PowerPortTemplateType, + PowerPortType, + RearPortTemplateType, + RearPortType, +) +from dcim.models import ( + ConsolePort, + ConsolePortTemplate, + ConsoleServerPort, + ConsoleServerPortTemplate, + FrontPort, + FrontPortTemplate, + Interface, + InterfaceTemplate, + PowerFeed, + PowerOutlet, + PowerOutletTemplate, + PowerPort, + PowerPortTemplate, + RearPort, + RearPortTemplate, +) + + +class CableTerminationTerminationType(graphene.Union): + class Meta: + types = ( + CircuitTerminationType, + ConsolePortType, + ConsoleServerPortType, + FrontPortType, + InterfaceType, + PowerFeedType, + PowerOutletType, + PowerPortType, + RearPortType, + ) + + @classmethod + def resolve_type(cls, instance, info): + if type(instance) == CircuitTermination: + return CircuitTerminationType + if type(instance) == ConsolePortType: + return ConsolePortType + if type(instance) == ConsoleServerPort: + return ConsoleServerPortType + if type(instance) == FrontPort: + return FrontPortType + if type(instance) == Interface: + return InterfaceType + if type(instance) == PowerFeed: + return PowerFeedType + if type(instance) == PowerOutlet: + return PowerOutletType + if type(instance) == PowerPort: + return PowerPortType + if type(instance) == RearPort: + return RearPortType + + +class InventoryItemTemplateComponentType(graphene.Union): + class Meta: + types = ( + ConsolePortTemplateType, + ConsoleServerPortTemplateType, + FrontPortTemplateType, + InterfaceTemplateType, + PowerOutletTemplateType, + PowerPortTemplateType, + RearPortTemplateType, + ) + + @classmethod + def resolve_type(cls, instance, info): + if type(instance) == ConsolePortTemplate: + return ConsolePortTemplateType + if type(instance) == ConsoleServerPortTemplate: + return ConsoleServerPortTemplateType + if type(instance) == FrontPortTemplate: + return FrontPortTemplateType + if type(instance) == InterfaceTemplate: + return InterfaceTemplateType + if type(instance) == PowerOutletTemplate: + return PowerOutletTemplateType + if type(instance) == PowerPortTemplate: + return PowerPortTemplateType + if type(instance) == RearPortTemplate: + return RearPortTemplateType + + +class InventoryItemComponentType(graphene.Union): + class Meta: + types = ( + ConsolePortType, + ConsoleServerPortType, + FrontPortType, + InterfaceType, + PowerOutletType, + PowerPortType, + RearPortType, + ) + + @classmethod + def resolve_type(cls, instance, info): + if type(instance) == ConsolePort: + return ConsolePortType + if type(instance) == ConsoleServerPort: + return ConsoleServerPortType + if type(instance) == FrontPort: + return FrontPortType + if type(instance) == Interface: + return InterfaceType + if type(instance) == PowerOutlet: + return PowerOutletType + if type(instance) == PowerPort: + return PowerPortType + if type(instance) == RearPort: + return RearPortType From 745edd82bf9bae0cd99ccb935f3601f00c08493c Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 6 Oct 2022 11:03:45 -0700 Subject: [PATCH 6/6] 9817 fix tests --- netbox/utilities/testing/api.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/netbox/utilities/testing/api.py b/netbox/utilities/testing/api.py index f26e5fffc..6a3494370 100644 --- a/netbox/utilities/testing/api.py +++ b/netbox/utilities/testing/api.py @@ -1,3 +1,4 @@ +import inspect import json from django.conf import settings @@ -5,7 +6,7 @@ from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType from django.urls import reverse from django.test import override_settings -from graphene.types import Dynamic as GQLDynamic, List as GQLList +from graphene.types import Dynamic as GQLDynamic, List as GQLList, Union as GQLUnion from rest_framework import status from rest_framework.test import APIClient @@ -449,6 +450,9 @@ class APIViewTestCases: if type(field) is GQLDynamic: # Dynamic fields must specify a subselection fields_string += f'{field_name} {{ id }}\n' + elif inspect.isclass(field.type) and issubclass(field.type, GQLUnion): + # Union types dont' have an id or consistent values + continue elif type(field.type) is GQLList and field_name != 'choices': # TODO: Come up with something more elegant # Temporary hack to support automated testing of reverse generic relations