Closes #22061: Add prefetch hints for GraphQL types

This commit is contained in:
Jeremy Stretch 2026-05-08 16:32:42 -04:00
parent 3e4ad4a5da
commit da9568b548
7 changed files with 82 additions and 32 deletions

View File

@ -74,7 +74,7 @@ class ProviderNetworkType(PrimaryObjectType):
class CircuitTerminationType(CustomFieldsMixin, TagsMixin, CabledObjectMixin, ObjectType):
circuit: Annotated['CircuitType', strawberry.lazy('circuits.graphql.types')]
@strawberry_django.field
@strawberry_django.field(prefetch_related='termination')
def termination(self) -> Annotated[
Annotated['LocationType', strawberry.lazy('dcim.graphql.types')]
| Annotated['RegionType', strawberry.lazy('dcim.graphql.types')]
@ -133,7 +133,7 @@ class CircuitGroupType(OrganizationalObjectType):
class CircuitGroupAssignmentType(TagsMixin, BaseObjectType):
group: Annotated['CircuitGroupType', strawberry.lazy('circuits.graphql.types')]
@strawberry_django.field
@strawberry_django.field(prefetch_related='member')
def member(self) -> Annotated[
Annotated['CircuitType', strawberry.lazy('circuits.graphql.types')]
| Annotated['VirtualCircuitType', strawberry.lazy('circuits.graphql.types')],

View File

@ -4,6 +4,7 @@ import strawberry
import strawberry_django
from django.db.models import Func, IntegerField
from circuits.models import CircuitTermination
from core.graphql.mixins import ChangelogMixin
from dcim import models
from extras.graphql.mixins import ConfigContextMixin, ContactsMixin, ImageAttachmentsMixin
@ -17,6 +18,8 @@ from netbox.graphql.types import (
PrimaryObjectType,
)
from users.graphql.mixins import OwnerMixin
from utilities.querysets import RestrictedPrefetch
from virtualization.models import Cluster
from .filters import *
from .mixins import CabledObjectMixin, PathEndpointMixin
@ -288,11 +291,11 @@ class DeviceType(ConfigContextMixin, ImageAttachmentsMixin, ContactsMixin, Prima
inventoryitems: list[Annotated["InventoryItemType", strawberry.lazy('dcim.graphql.types')]]
vdcs: list[Annotated["VirtualDeviceContextType", strawberry.lazy('dcim.graphql.types')]]
@strawberry_django.field
@strawberry_django.field(prefetch_related='vc_master_for')
def vc_master_for(self) -> Annotated["VirtualChassisType", strawberry.lazy('dcim.graphql.types')] | None:
return self.vc_master_for if hasattr(self, 'vc_master_for') else None
@strawberry_django.field
@strawberry_django.field(prefetch_related='parent_bay')
def parent_bay(self) -> Annotated["DeviceBayType", strawberry.lazy('dcim.graphql.types')] | None:
return self.parent_bay if hasattr(self, 'parent_bay') else None
@ -327,7 +330,7 @@ class InventoryItemTemplateType(ComponentTemplateType):
role: Annotated['InventoryItemRoleType', strawberry.lazy('dcim.graphql.types')] | None
manufacturer: Annotated['ManufacturerType', strawberry.lazy('dcim.graphql.types')]
@strawberry_django.field
@strawberry_django.field(prefetch_related='parent')
def parent(self) -> Annotated['InventoryItemTemplateType', strawberry.lazy('dcim.graphql.types')] | None:
return self.parent
@ -430,7 +433,7 @@ class FrontPortTemplateType(ModularComponentTemplateType):
class MACAddressType(PrimaryObjectType):
mac_address: str
@strawberry_django.field
@strawberry_django.field(prefetch_related='assigned_object')
def assigned_object(self) -> Annotated[
Annotated['InterfaceType', strawberry.lazy('dcim.graphql.types')]
| Annotated['VMInterfaceType', strawberry.lazy('virtualization.graphql.types')],
@ -494,7 +497,7 @@ class InventoryItemType(ComponentType):
child_items: list[Annotated['InventoryItemType', strawberry.lazy('dcim.graphql.types')]]
@strawberry_django.field
@strawberry_django.field(prefetch_related='parent')
def parent(self) -> Annotated['InventoryItemType', strawberry.lazy('dcim.graphql.types')] | None:
return self.parent
@ -541,11 +544,20 @@ class LocationType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, Nested
devices: list[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]
children: list[Annotated["LocationType", strawberry.lazy('dcim.graphql.types')]]
@strawberry_django.field
@strawberry_django.field(
prefetch_related=lambda info: RestrictedPrefetch(
'cluster_set', info.context.request.user, 'view', queryset=Cluster.objects.all()
),
)
def clusters(self) -> list[Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')]]:
return self.cluster_set.all()
@strawberry_django.field
@strawberry_django.field(
prefetch_related=lambda info: RestrictedPrefetch(
'circuit_terminations', info.context.request.user, 'view',
queryset=CircuitTermination.objects.all()
),
)
def circuit_terminations(self) -> list[
Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]
]:
@ -599,7 +611,7 @@ class ModuleBayType(ModularComponentType):
installed_module: Annotated["ModuleType", strawberry.lazy('dcim.graphql.types')] | None
children: list[Annotated["ModuleBayType", strawberry.lazy('dcim.graphql.types')]]
@strawberry_django.field
@strawberry_django.field(prefetch_related='parent')
def parent(self) -> Annotated["ModuleBayType", strawberry.lazy('dcim.graphql.types')] | None:
return self.parent
@ -864,15 +876,24 @@ class RegionType(VLANGroupsMixin, ContactsMixin, NestedGroupObjectType):
sites: list[Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]]
children: list[Annotated["RegionType", strawberry.lazy('dcim.graphql.types')]]
@strawberry_django.field
@strawberry_django.field(prefetch_related='parent')
def parent(self) -> Annotated["RegionType", strawberry.lazy('dcim.graphql.types')] | None:
return self.parent
@strawberry_django.field
@strawberry_django.field(
prefetch_related=lambda info: RestrictedPrefetch(
'cluster_set', info.context.request.user, 'view', queryset=Cluster.objects.all()
),
)
def clusters(self) -> list[Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')]]:
return self.cluster_set.all()
@strawberry_django.field
@strawberry_django.field(
prefetch_related=lambda info: RestrictedPrefetch(
'circuit_terminations', info.context.request.user, 'view',
queryset=CircuitTermination.objects.all()
),
)
def circuit_terminations(self) -> list[
Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]
]:
@ -903,11 +924,20 @@ class SiteType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, PrimaryObj
clusters: list[Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')]]
vlans: list[Annotated["VLANType", strawberry.lazy('ipam.graphql.types')]]
@strawberry_django.field
@strawberry_django.field(
prefetch_related=lambda info: RestrictedPrefetch(
'cluster_set', info.context.request.user, 'view', queryset=Cluster.objects.all()
),
)
def clusters(self) -> list[Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')]]:
return self.cluster_set.all()
@strawberry_django.field
@strawberry_django.field(
prefetch_related=lambda info: RestrictedPrefetch(
'circuit_terminations', info.context.request.user, 'view',
queryset=CircuitTermination.objects.all()
),
)
def circuit_terminations(self) -> list[
Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]
]:
@ -925,15 +955,24 @@ class SiteGroupType(VLANGroupsMixin, ContactsMixin, NestedGroupObjectType):
sites: list[Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]]
children: list[Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')]]
@strawberry_django.field
@strawberry_django.field(prefetch_related='parent')
def parent(self) -> Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')] | None:
return self.parent
@strawberry_django.field
@strawberry_django.field(
prefetch_related=lambda info: RestrictedPrefetch(
'cluster_set', info.context.request.user, 'view', queryset=Cluster.objects.all()
),
)
def clusters(self) -> list[Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')]]:
return self.cluster_set.all()
@strawberry_django.field
@strawberry_django.field(
prefetch_related=lambda info: RestrictedPrefetch(
'circuit_terminations', info.context.request.user, 'view',
queryset=CircuitTermination.objects.all()
),
)
def circuit_terminations(self) -> list[
Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]
]:

View File

@ -4,6 +4,9 @@ import strawberry
import strawberry_django
from strawberry.types import Info
from extras.models import ImageAttachment, JournalEntry
from utilities.querysets import RestrictedPrefetch
__all__ = (
'ConfigContextMixin',
'ContactsMixin',
@ -50,16 +53,24 @@ class CustomFieldsMixin:
@strawberry.type
class ImageAttachmentsMixin:
@strawberry_django.field
def image_attachments(self, info: Info) -> list[Annotated['ImageAttachmentType', strawberry.lazy('.types')]]:
return self.images.restrict(info.context.request.user, 'view')
@strawberry_django.field(
prefetch_related=lambda info: RestrictedPrefetch(
'images', info.context.request.user, 'view', queryset=ImageAttachment.objects.all()
),
)
def image_attachments(self) -> list[Annotated['ImageAttachmentType', strawberry.lazy('.types')]]:
return self.images.all()
@strawberry.type
class JournalEntriesMixin:
@strawberry_django.field
def journal_entries(self, info: Info) -> list[Annotated['JournalEntryType', strawberry.lazy('.types')]]:
@strawberry_django.field(
prefetch_related=lambda info: RestrictedPrefetch(
'journal_entries', info.context.request.user, 'view', queryset=JournalEntry.objects.all()
),
)
def journal_entries(self) -> list[Annotated['JournalEntryType', strawberry.lazy('.types')]]:
return self.journal_entries.all()

View File

@ -128,7 +128,7 @@ class FHRPGroupType(IPAddressesMixin, PrimaryObjectType):
class FHRPGroupAssignmentType(BaseObjectType):
group: Annotated['FHRPGroupType', strawberry.lazy('ipam.graphql.types')]
@strawberry_django.field
@strawberry_django.field(prefetch_related='interface')
def interface(self) -> Annotated[
Annotated['InterfaceType', strawberry.lazy('dcim.graphql.types')]
| Annotated['VMInterfaceType', strawberry.lazy('virtualization.graphql.types')],
@ -153,7 +153,7 @@ class IPAddressType(ContactsMixin, BaseIPAddressFamilyType, PrimaryObjectType):
tunnel_terminations: list[Annotated['TunnelTerminationType', strawberry.lazy('vpn.graphql.types')]]
services: list[Annotated['ServiceType', strawberry.lazy('ipam.graphql.types')]]
@strawberry_django.field
@strawberry_django.field(prefetch_related='assigned_object')
def assigned_object(self) -> Annotated[
Annotated['InterfaceType', strawberry.lazy('dcim.graphql.types')]
| Annotated['FHRPGroupType', strawberry.lazy('ipam.graphql.types')]
@ -190,7 +190,7 @@ class PrefixType(ContactsMixin, BaseIPAddressFamilyType, PrimaryObjectType):
vlan: Annotated['VLANType', strawberry.lazy('ipam.graphql.types')] | None
role: Annotated['RoleType', strawberry.lazy('ipam.graphql.types')] | None
@strawberry_django.field
@strawberry_django.field(prefetch_related='scope')
def scope(self) -> Annotated[
Annotated['LocationType', strawberry.lazy('dcim.graphql.types')]
| Annotated['RegionType', strawberry.lazy('dcim.graphql.types')]
@ -252,7 +252,7 @@ class ServiceType(ContactsMixin, PrimaryObjectType):
ports: list[int]
ipaddresses: list[Annotated['IPAddressType', strawberry.lazy('ipam.graphql.types')]]
@strawberry_django.field
@strawberry_django.field(prefetch_related='parent')
def parent(self) -> Annotated[
Annotated['DeviceType', strawberry.lazy('dcim.graphql.types')]
| Annotated['VirtualMachineType', strawberry.lazy('virtualization.graphql.types')]
@ -291,7 +291,7 @@ class VLANType(PrimaryObjectType):
interfaces_as_tagged: list[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]
vminterfaces_as_tagged: list[Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')]]
@strawberry_django.field
@strawberry_django.field(prefetch_related='qinq_svlan')
def qinq_svlan(self) -> Annotated["VLANType", strawberry.lazy('ipam.graphql.types')] | None:
return self.qinq_svlan
@ -309,7 +309,7 @@ class VLANGroupType(OrganizationalObjectType):
total_vlan_ids: BigInt
tenant: Annotated['TenantType', strawberry.lazy('tenancy.graphql.types')] | None
@strawberry_django.field
@strawberry_django.field(prefetch_related='scope')
def scope(self) -> Annotated[
Annotated['ClusterType', strawberry.lazy('virtualization.graphql.types')]
| Annotated['ClusterGroupType', strawberry.lazy('virtualization.graphql.types')]

View File

@ -59,7 +59,7 @@ class ClusterType(ContactsMixin, VLANGroupsMixin, PrimaryObjectType):
virtual_machines: list[Annotated["VirtualMachineType", strawberry.lazy('virtualization.graphql.types')]]
devices: list[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]
@strawberry_django.field
@strawberry_django.field(prefetch_related='scope')
def scope(self) -> Annotated[
Annotated['LocationType', strawberry.lazy('dcim.graphql.types')]
| Annotated['RegionType', strawberry.lazy('dcim.graphql.types')]

View File

@ -145,7 +145,7 @@ class L2VPNType(ContactsMixin, PrimaryObjectType):
class L2VPNTerminationType(NetBoxObjectType):
l2vpn: Annotated["L2VPNType", strawberry.lazy('vpn.graphql.types')]
@strawberry_django.field
@strawberry_django.field(prefetch_related='assigned_object')
def assigned_object(self) -> Annotated[
Annotated['InterfaceType', strawberry.lazy('dcim.graphql.types')]
| Annotated['VLANType', strawberry.lazy('ipam.graphql.types')]

View File

@ -46,7 +46,7 @@ class WirelessLANType(PrimaryObjectType):
interfaces: list[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]
@strawberry_django.field
@strawberry_django.field(prefetch_related='scope')
def scope(self) -> Annotated[
Annotated['LocationType', strawberry.lazy('dcim.graphql.types')]
| Annotated['RegionType', strawberry.lazy('dcim.graphql.types')]