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 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
__all__ = (
@ -20,8 +22,7 @@ class CircuitTerminationType(CustomFieldsMixin, TagsMixin, CabledObjectMixin, Ob
filterset_class = filtersets.CircuitTerminationFilterSet
class CircuitType(NetBoxObjectType):
class CircuitType(NetBoxObjectType, ContactsMixin):
class Meta:
model = models.Circuit
fields = '__all__'
@ -36,7 +37,7 @@ class CircuitTypeType(OrganizationalObjectType):
filterset_class = filtersets.CircuitTypeFilterSet
class ProviderType(NetBoxObjectType):
class ProviderType(NetBoxObjectType, ContactsMixin):
class Meta:
model = models.Provider

View File

@ -2,24 +2,38 @@ 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,
)
@ -57,3 +71,99 @@ class LinkPeerType(graphene.Union):
return PowerPortType
if type(instance) == RearPort:
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 extras.graphql.mixins import (
ChangelogMixin, ConfigContextMixin, CustomFieldsMixin, ImageAttachmentsMixin, TagsMixin,
ChangelogMixin, ConfigContextMixin, ContactsMixin, CustomFieldsMixin, ImageAttachmentsMixin, TagsMixin,
)
from ipam.graphql.mixins import IPAddressesMixin, VLANGroupsMixin
from netbox.graphql.scalars import BigInt
@ -87,6 +87,8 @@ class ComponentTemplateObjectType(
#
class CableType(NetBoxObjectType):
a_terminations = graphene.List('dcim.graphql.gfk_mixins.CableTerminationTerminationType')
b_terminations = graphene.List('dcim.graphql.gfk_mixins.CableTerminationTerminationType')
class Meta:
model = models.Cable
@ -99,12 +101,19 @@ class CableType(NetBoxObjectType):
def resolve_length_unit(self, info):
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):
termination = graphene.Field('dcim.graphql.gfk_mixins.CableTerminationTerminationType')
class Meta:
model = models.CableTermination
fields = '__all__'
exclude = ('termination_type', 'termination_id')
filterset_class = filtersets.CableTerminationFilterSet
@ -152,7 +161,7 @@ class ConsoleServerPortTemplateType(ComponentTemplateObjectType):
return self.type or None
class DeviceType(ConfigContextMixin, ImageAttachmentsMixin, NetBoxObjectType):
class DeviceType(ConfigContextMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObjectType):
class Meta:
model = models.Device
@ -183,10 +192,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 +279,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
@ -284,7 +295,7 @@ class InventoryItemRoleType(OrganizationalObjectType):
filterset_class = filtersets.InventoryItemRoleFilterSet
class LocationType(VLANGroupsMixin, ImageAttachmentsMixin, OrganizationalObjectType):
class LocationType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, OrganizationalObjectType):
class Meta:
model = models.Location
@ -292,7 +303,7 @@ class LocationType(VLANGroupsMixin, ImageAttachmentsMixin, OrganizationalObjectT
filterset_class = filtersets.LocationFilterSet
class ManufacturerType(OrganizationalObjectType):
class ManufacturerType(OrganizationalObjectType, ContactsMixin):
class Meta:
model = models.Manufacturer
@ -379,7 +390,7 @@ class PowerOutletTemplateType(ComponentTemplateObjectType):
return self.type or None
class PowerPanelType(NetBoxObjectType):
class PowerPanelType(NetBoxObjectType, ContactsMixin):
class Meta:
model = models.PowerPanel
@ -409,7 +420,7 @@ class PowerPortTemplateType(ComponentTemplateObjectType):
return self.type or None
class RackType(VLANGroupsMixin, ImageAttachmentsMixin, NetBoxObjectType):
class RackType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObjectType):
class Meta:
model = models.Rack
@ -458,7 +469,7 @@ class RearPortTemplateType(ComponentTemplateObjectType):
filterset_class = filtersets.RearPortTemplateFilterSet
class RegionType(VLANGroupsMixin, OrganizationalObjectType):
class RegionType(VLANGroupsMixin, ContactsMixin, OrganizationalObjectType):
class Meta:
model = models.Region
@ -466,7 +477,7 @@ class RegionType(VLANGroupsMixin, OrganizationalObjectType):
filterset_class = filtersets.RegionFilterSet
class SiteType(VLANGroupsMixin, ImageAttachmentsMixin, NetBoxObjectType):
class SiteType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObjectType):
asn = graphene.Field(BigInt)
class Meta:
@ -475,7 +486,7 @@ class SiteType(VLANGroupsMixin, ImageAttachmentsMixin, NetBoxObjectType):
filterset_class = filtersets.SiteFilterSet
class SiteGroupType(VLANGroupsMixin, OrganizationalObjectType):
class SiteGroupType(VLANGroupsMixin, ContactsMixin, OrganizationalObjectType):
class Meta:
model = models.SiteGroup

View File

@ -59,3 +59,10 @@ class TagsMixin:
def resolve_tags(self, info):
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
from graphene_django import DjangoObjectType
from extras.graphql.mixins import ContactsMixin
from ipam import filtersets, models
from netbox.graphql.scalars import BigInt
from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, NetBoxObjectType
@ -54,18 +56,20 @@ 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
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):
@ -140,10 +144,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
@ -155,7 +160,7 @@ class VRFType(NetBoxObjectType):
filterset_class = filtersets.VRFFilterSet
class L2VPNType(NetBoxObjectType):
class L2VPNType(ContactsMixin, NetBoxObjectType):
class Meta:
model = models.L2VPN
fields = '__all__'
@ -163,7 +168,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

View File

@ -450,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 inspect.isclass(field.type.of_type) and issubclass(field.type.of_type, GQLUnion):
# Union types dont' have an id or consistent values
continue