10595 extend graphql relationships (#10603)

* 9817 add graphql l2vpntermination assigned_object

* 9817 add graphql ipaddressassignment assigned_object

* 9817 ipan graphql gfk

* 9817 dcim graphql gfk

* 9817 dcim graphql gfk

* 9817 fix tests

* 10595 cable a_terminations to graphql

* 10595 add contacts to graphql

* 10595 move contacts to Provider
This commit is contained in:
Arthur Hanson 2022-10-11 09:26:18 -07:00 committed by GitHub
parent ffce5d968d
commit 9ca4c7315b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 255 additions and 21 deletions

View File

@ -1,6 +1,8 @@
import graphene
from circuits import filtersets, models from circuits import filtersets, models
from dcim.graphql.mixins import CabledObjectMixin from dcim.graphql.mixins import CabledObjectMixin
from extras.graphql.mixins import CustomFieldsMixin, TagsMixin from extras.graphql.mixins import CustomFieldsMixin, TagsMixin, ContactsMixin
from netbox.graphql.types import ObjectType, OrganizationalObjectType, NetBoxObjectType from netbox.graphql.types import ObjectType, OrganizationalObjectType, NetBoxObjectType
__all__ = ( __all__ = (
@ -20,8 +22,7 @@ class CircuitTerminationType(CustomFieldsMixin, TagsMixin, CabledObjectMixin, Ob
filterset_class = filtersets.CircuitTerminationFilterSet filterset_class = filtersets.CircuitTerminationFilterSet
class CircuitType(NetBoxObjectType): class CircuitType(NetBoxObjectType, ContactsMixin):
class Meta: class Meta:
model = models.Circuit model = models.Circuit
fields = '__all__' fields = '__all__'
@ -36,7 +37,7 @@ class CircuitTypeType(OrganizationalObjectType):
filterset_class = filtersets.CircuitTypeFilterSet filterset_class = filtersets.CircuitTypeFilterSet
class ProviderType(NetBoxObjectType): class ProviderType(NetBoxObjectType, ContactsMixin):
class Meta: class Meta:
model = models.Provider model = models.Provider

View File

@ -2,24 +2,38 @@ import graphene
from circuits.graphql.types import CircuitTerminationType from circuits.graphql.types import CircuitTerminationType
from circuits.models import CircuitTermination from circuits.models import CircuitTermination
from dcim.graphql.types import ( from dcim.graphql.types import (
ConsolePortTemplateType,
ConsolePortType, ConsolePortType,
ConsoleServerPortTemplateType,
ConsoleServerPortType, ConsoleServerPortType,
FrontPortTemplateType,
FrontPortType, FrontPortType,
InterfaceTemplateType,
InterfaceType, InterfaceType,
PowerFeedType, PowerFeedType,
PowerOutletTemplateType,
PowerOutletType, PowerOutletType,
PowerPortTemplateType,
PowerPortType, PowerPortType,
RearPortTemplateType,
RearPortType, RearPortType,
) )
from dcim.models import ( from dcim.models import (
ConsolePort, ConsolePort,
ConsolePortTemplate,
ConsoleServerPort, ConsoleServerPort,
ConsoleServerPortTemplate,
FrontPort, FrontPort,
FrontPortTemplate,
Interface, Interface,
InterfaceTemplate,
PowerFeed, PowerFeed,
PowerOutlet, PowerOutlet,
PowerOutletTemplate,
PowerPort, PowerPort,
PowerPortTemplate,
RearPort, RearPort,
RearPortTemplate,
) )
@ -57,3 +71,99 @@ class LinkPeerType(graphene.Union):
return PowerPortType return PowerPortType
if type(instance) == RearPort: if type(instance) == RearPort:
return RearPortType return RearPortType
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

View File

@ -2,7 +2,7 @@ import graphene
from dcim import filtersets, models from dcim import filtersets, models
from extras.graphql.mixins import ( from extras.graphql.mixins import (
ChangelogMixin, ConfigContextMixin, CustomFieldsMixin, ImageAttachmentsMixin, TagsMixin, ChangelogMixin, ConfigContextMixin, ContactsMixin, CustomFieldsMixin, ImageAttachmentsMixin, TagsMixin,
) )
from ipam.graphql.mixins import IPAddressesMixin, VLANGroupsMixin from ipam.graphql.mixins import IPAddressesMixin, VLANGroupsMixin
from netbox.graphql.scalars import BigInt from netbox.graphql.scalars import BigInt
@ -87,6 +87,8 @@ class ComponentTemplateObjectType(
# #
class CableType(NetBoxObjectType): class CableType(NetBoxObjectType):
a_terminations = graphene.List('dcim.graphql.gfk_mixins.CableTerminationTerminationType')
b_terminations = graphene.List('dcim.graphql.gfk_mixins.CableTerminationTerminationType')
class Meta: class Meta:
model = models.Cable model = models.Cable
@ -99,12 +101,19 @@ class CableType(NetBoxObjectType):
def resolve_length_unit(self, info): def resolve_length_unit(self, info):
return self.length_unit or None return self.length_unit or None
def resolve_a_terminations(self, info):
return self.a_terminations
def resolve_b_terminations(self, info):
return self.b_terminations
class CableTerminationType(NetBoxObjectType): class CableTerminationType(NetBoxObjectType):
termination = graphene.Field('dcim.graphql.gfk_mixins.CableTerminationTerminationType')
class Meta: class Meta:
model = models.CableTermination model = models.CableTermination
fields = '__all__' exclude = ('termination_type', 'termination_id')
filterset_class = filtersets.CableTerminationFilterSet filterset_class = filtersets.CableTerminationFilterSet
@ -152,7 +161,7 @@ class ConsoleServerPortTemplateType(ComponentTemplateObjectType):
return self.type or None return self.type or None
class DeviceType(ConfigContextMixin, ImageAttachmentsMixin, NetBoxObjectType): class DeviceType(ConfigContextMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObjectType):
class Meta: class Meta:
model = models.Device model = models.Device
@ -183,10 +192,11 @@ class DeviceBayTemplateType(ComponentTemplateObjectType):
class InventoryItemTemplateType(ComponentTemplateObjectType): class InventoryItemTemplateType(ComponentTemplateObjectType):
component = graphene.Field('dcim.graphql.gfk_mixins.InventoryItemTemplateComponentType')
class Meta: class Meta:
model = models.InventoryItemTemplate model = models.InventoryItemTemplate
fields = '__all__' exclude = ('component_type', 'component_id')
filterset_class = filtersets.InventoryItemTemplateFilterSet filterset_class = filtersets.InventoryItemTemplateFilterSet
@ -269,10 +279,11 @@ class InterfaceTemplateType(ComponentTemplateObjectType):
class InventoryItemType(ComponentObjectType): class InventoryItemType(ComponentObjectType):
component = graphene.Field('dcim.graphql.gfk_mixins.InventoryItemComponentType')
class Meta: class Meta:
model = models.InventoryItem model = models.InventoryItem
fields = '__all__' exclude = ('component_type', 'component_id')
filterset_class = filtersets.InventoryItemFilterSet filterset_class = filtersets.InventoryItemFilterSet
@ -284,7 +295,7 @@ class InventoryItemRoleType(OrganizationalObjectType):
filterset_class = filtersets.InventoryItemRoleFilterSet filterset_class = filtersets.InventoryItemRoleFilterSet
class LocationType(VLANGroupsMixin, ImageAttachmentsMixin, OrganizationalObjectType): class LocationType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, OrganizationalObjectType):
class Meta: class Meta:
model = models.Location model = models.Location
@ -292,7 +303,7 @@ class LocationType(VLANGroupsMixin, ImageAttachmentsMixin, OrganizationalObjectT
filterset_class = filtersets.LocationFilterSet filterset_class = filtersets.LocationFilterSet
class ManufacturerType(OrganizationalObjectType): class ManufacturerType(OrganizationalObjectType, ContactsMixin):
class Meta: class Meta:
model = models.Manufacturer model = models.Manufacturer
@ -379,7 +390,7 @@ class PowerOutletTemplateType(ComponentTemplateObjectType):
return self.type or None return self.type or None
class PowerPanelType(NetBoxObjectType): class PowerPanelType(NetBoxObjectType, ContactsMixin):
class Meta: class Meta:
model = models.PowerPanel model = models.PowerPanel
@ -409,7 +420,7 @@ class PowerPortTemplateType(ComponentTemplateObjectType):
return self.type or None return self.type or None
class RackType(VLANGroupsMixin, ImageAttachmentsMixin, NetBoxObjectType): class RackType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObjectType):
class Meta: class Meta:
model = models.Rack model = models.Rack
@ -458,7 +469,7 @@ class RearPortTemplateType(ComponentTemplateObjectType):
filterset_class = filtersets.RearPortTemplateFilterSet filterset_class = filtersets.RearPortTemplateFilterSet
class RegionType(VLANGroupsMixin, OrganizationalObjectType): class RegionType(VLANGroupsMixin, ContactsMixin, OrganizationalObjectType):
class Meta: class Meta:
model = models.Region model = models.Region
@ -466,7 +477,7 @@ class RegionType(VLANGroupsMixin, OrganizationalObjectType):
filterset_class = filtersets.RegionFilterSet filterset_class = filtersets.RegionFilterSet
class SiteType(VLANGroupsMixin, ImageAttachmentsMixin, NetBoxObjectType): class SiteType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObjectType):
asn = graphene.Field(BigInt) asn = graphene.Field(BigInt)
class Meta: class Meta:
@ -475,7 +486,7 @@ class SiteType(VLANGroupsMixin, ImageAttachmentsMixin, NetBoxObjectType):
filterset_class = filtersets.SiteFilterSet filterset_class = filtersets.SiteFilterSet
class SiteGroupType(VLANGroupsMixin, OrganizationalObjectType): class SiteGroupType(VLANGroupsMixin, ContactsMixin, OrganizationalObjectType):
class Meta: class Meta:
model = models.SiteGroup model = models.SiteGroup

View File

@ -59,3 +59,10 @@ class TagsMixin:
def resolve_tags(self, info): def resolve_tags(self, info):
return self.tags.all() return self.tags.all()
class ContactsMixin:
contacts = graphene.List('tenancy.graphql.types.ContactAssignmentType')
def resolve_contacts(self, info):
return list(self.contacts.all())

View File

@ -0,0 +1,95 @@
import graphene
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 VLAN, FHRPGroup
from virtualization.graphql.types import ClusterGroupType, ClusterType, VMInterfaceType
from virtualization.models import Cluster, ClusterGroup, 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 = (
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
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

View File

@ -1,5 +1,7 @@
import graphene import graphene
from graphene_django import DjangoObjectType
from extras.graphql.mixins import ContactsMixin
from ipam import filtersets, models from ipam import filtersets, models
from netbox.graphql.scalars import BigInt from netbox.graphql.scalars import BigInt
from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, NetBoxObjectType from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, NetBoxObjectType
@ -54,18 +56,20 @@ class FHRPGroupType(NetBoxObjectType):
class FHRPGroupAssignmentType(BaseObjectType): class FHRPGroupAssignmentType(BaseObjectType):
interface = graphene.Field('ipam.graphql.gfk_mixins.FHRPGroupInterfaceType')
class Meta: class Meta:
model = models.FHRPGroupAssignment model = models.FHRPGroupAssignment
fields = '__all__' exclude = ('interface_type', 'interface_id')
filterset_class = filtersets.FHRPGroupAssignmentFilterSet filterset_class = filtersets.FHRPGroupAssignmentFilterSet
class IPAddressType(NetBoxObjectType): class IPAddressType(NetBoxObjectType):
assigned_object = graphene.Field('ipam.graphql.gfk_mixins.IPAddressAssignmentType')
class Meta: class Meta:
model = models.IPAddress model = models.IPAddress
fields = '__all__' exclude = ('assigned_object_type', 'assigned_object_id')
filterset_class = filtersets.IPAddressFilterSet filterset_class = filtersets.IPAddressFilterSet
def resolve_role(self, info): def resolve_role(self, info):
@ -140,10 +144,11 @@ class VLANType(NetBoxObjectType):
class VLANGroupType(OrganizationalObjectType): class VLANGroupType(OrganizationalObjectType):
scope = graphene.Field('ipam.graphql.gfk_mixins.VLANGroupScopeType')
class Meta: class Meta:
model = models.VLANGroup model = models.VLANGroup
fields = '__all__' exclude = ('scope_type', 'scope_id')
filterset_class = filtersets.VLANGroupFilterSet filterset_class = filtersets.VLANGroupFilterSet
@ -155,7 +160,7 @@ class VRFType(NetBoxObjectType):
filterset_class = filtersets.VRFFilterSet filterset_class = filtersets.VRFFilterSet
class L2VPNType(NetBoxObjectType): class L2VPNType(ContactsMixin, NetBoxObjectType):
class Meta: class Meta:
model = models.L2VPN model = models.L2VPN
fields = '__all__' fields = '__all__'
@ -163,7 +168,9 @@ class L2VPNType(NetBoxObjectType):
class L2VPNTerminationType(NetBoxObjectType): class L2VPNTerminationType(NetBoxObjectType):
assigned_object = graphene.Field('ipam.graphql.gfk_mixins.L2VPNAssignmentType')
class Meta: class Meta:
model = models.L2VPNTermination model = models.L2VPNTermination
fields = '__all__' exclude = ('assigned_object_type', 'assigned_object_id')
filtersets_class = filtersets.L2VPNTerminationFilterSet filtersets_class = filtersets.L2VPNTerminationFilterSet

View File

@ -450,6 +450,9 @@ class APIViewTestCases:
if type(field) is GQLDynamic: if type(field) is GQLDynamic:
# Dynamic fields must specify a subselection # Dynamic fields must specify a subselection
fields_string += f'{field_name} {{ id }}\n' 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 inspect.isclass(field.type.of_type) and issubclass(field.type.of_type, GQLUnion): elif type(field.type) is GQLList and inspect.isclass(field.type.of_type) and issubclass(field.type.of_type, GQLUnion):
# Union types dont' have an id or consistent values # Union types dont' have an id or consistent values
continue continue