mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-25 08:46:10 -06:00
Merge branch 'feature' into 8245-graphql-filter
This commit is contained in:
commit
bdb8ca380f
@ -20,9 +20,11 @@ A new `PluginMenu` class has been introduced, which enables a plugin to inject a
|
||||
* [#9249](https://github.com/netbox-community/netbox/issues/9249) - Device and virtual machine names are no longer case-sensitive
|
||||
* [#9478](https://github.com/netbox-community/netbox/issues/9478) - Add `link_peers` field to GraphQL types for cabled objects
|
||||
* [#9654](https://github.com/netbox-community/netbox/issues/9654) - Add `weight` field to racks, device types, and module types
|
||||
* [#9817](https://github.com/netbox-community/netbox/issues/9817) - Add `assigned_object` field to GraphQL type for IP addresses and L2VPN terminations
|
||||
* [#9892](https://github.com/netbox-community/netbox/issues/9892) - Add optional `name` field for FHRP groups
|
||||
* [#10348](https://github.com/netbox-community/netbox/issues/10348) - Add decimal custom field type
|
||||
* [#10556](https://github.com/netbox-community/netbox/issues/10556) - Include a `display` field in all GraphQL object types
|
||||
* [#10595](https://github.com/netbox-community/netbox/issues/10595) - Add GraphQL relationships for additional generic foreign key fields
|
||||
|
||||
### Plugins API
|
||||
|
||||
@ -55,3 +57,20 @@ A new `PluginMenu` class has been introduced, which enables a plugin to inject a
|
||||
|
||||
* All object types now include a `display` field
|
||||
* All cabled object types now include a `link_peers` field
|
||||
* Add a `contacts` relationship for all relevant models
|
||||
* dcim.Cable
|
||||
* Add A/B terminations fields
|
||||
* dcim.CableTermination
|
||||
* Add `termination` field
|
||||
* dcim.InventoryItem
|
||||
* Add `component` field
|
||||
* dcim.InventoryItemTemplate
|
||||
* Add `component` field
|
||||
* ipam.FHRPGroupAssignment
|
||||
* Add `interface` field
|
||||
* ipam.IPAddress
|
||||
* Add `assigned_object` field
|
||||
* ipam.L2VPNTermination
|
||||
* Add `assigned_object` field
|
||||
* ipam.VLANGroupType
|
||||
* Add `scope` field
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
|
95
netbox/ipam/graphql/gfk_mixins.py
Normal file
95
netbox/ipam/graphql/gfk_mixins.py
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user