Coverage for ipam/graphql/types.py: 81%
100 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
1from typing import TYPE_CHECKING, Annotated
3import strawberry
4import strawberry_django
6from circuits.graphql.types import ProviderType
7from dcim.graphql.types import SiteType
8from dcim.models import Device, Interface, Location, Rack, RackGroup, Region, Site, SiteGroup
9from extras.graphql.mixins import ContactsMixin
10from ipam import models
11from netbox.graphql.optimization import build_gfk_prefetch
12from netbox.graphql.scalars import BigInt
13from netbox.graphql.types import (
14 BaseObjectType,
15 NetBoxObjectType,
16 OrganizationalObjectType,
17 PrimaryObjectType,
18 register_type,
19)
20from virtualization.models import Cluster, ClusterGroup, VirtualMachine, VMInterface
22from .filters import *
23from .mixins import IPAddressesMixin
25if TYPE_CHECKING: 25 ↛ 26line 25 didn't jump to line 26 because the condition on line 25 was never true
26 from dcim.graphql.types import (
27 DeviceType,
28 InterfaceType,
29 LocationType,
30 RackGroupType,
31 RackType,
32 RegionType,
33 SiteGroupType,
34 )
35 from tenancy.graphql.types import TenantType
36 from virtualization.graphql.types import ClusterGroupType, ClusterType, VirtualMachineType, VMInterfaceType
37 from vpn.graphql.types import L2VPNType, TunnelTerminationType
38 from wireless.graphql.types import WirelessLANType
40__all__ = (
41 'ASNRangeType',
42 'ASNType',
43 'AggregateType',
44 'FHRPGroupAssignmentType',
45 'FHRPGroupType',
46 'IPAddressType',
47 'IPRangeType',
48 'PrefixType',
49 'RIRType',
50 'RoleType',
51 'RouteTargetType',
52 'ServiceTemplateType',
53 'ServiceType',
54 'VLANGroupType',
55 'VLANTranslationPolicyType',
56 'VLANTranslationRuleType',
57 'VLANType',
58 'VRFType',
59)
62@strawberry.type
63class IPAddressFamilyType:
64 """
65 The address family (4 or 6) of a model's IP address or prefix column. Each type exposing this
66 declares its own `family` resolver, hinted with the column backing it so that the query optimizer
67 does not defer that column. `value` is non-null because those columns are: the models' `family`
68 properties return None only for an unsaved instance with no address assigned.
69 """
70 value: int
71 label: str
74@register_type(
75 models.ASN,
76 fields='__all__',
77 filters=ASNFilter,
78 pagination=True
79)
80class ASNType(ContactsMixin, PrimaryObjectType):
81 asn: BigInt
82 rir: Annotated["RIRType", strawberry.lazy('ipam.graphql.types')] | None
83 role: Annotated["RoleType", strawberry.lazy('ipam.graphql.types')] | None
84 tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
86 sites: list[SiteType]
87 providers: list[ProviderType]
90@register_type(
91 models.ASNRange,
92 fields='__all__',
93 filters=ASNRangeFilter,
94 pagination=True
95)
96class ASNRangeType(OrganizationalObjectType):
97 start: BigInt
98 end: BigInt
99 rir: Annotated["RIRType", strawberry.lazy('ipam.graphql.types')] | None
100 tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
103@register_type(
104 models.Aggregate,
105 fields='__all__',
106 filters=AggregateFilter,
107 pagination=True
108)
109class AggregateType(ContactsMixin, PrimaryObjectType):
110 prefix: str
111 rir: Annotated["RIRType", strawberry.lazy('ipam.graphql.types')] | None
112 tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
114 # Note that self is the Django model instance, so self.family resolves to the model's family property
115 @strawberry_django.field(only=['prefix'])
116 def family(self) -> IPAddressFamilyType:
117 return IPAddressFamilyType(value=self.family, label=f'IPv{self.family}')
120@register_type(
121 models.FHRPGroup,
122 fields='__all__',
123 filters=FHRPGroupFilter,
124 pagination=True
125)
126class FHRPGroupType(IPAddressesMixin, PrimaryObjectType):
127 fhrpgroupassignment_set: list[Annotated["FHRPGroupAssignmentType", strawberry.lazy('ipam.graphql.types')]]
130@register_type(
131 models.FHRPGroupAssignment,
132 exclude=['interface_type', 'interface_id'],
133 filters=FHRPGroupAssignmentFilter,
134 pagination=True
135)
136class FHRPGroupAssignmentType(BaseObjectType):
137 group: Annotated['FHRPGroupType', strawberry.lazy('ipam.graphql.types')]
139 @strawberry_django.field(
140 prefetch_related=build_gfk_prefetch(
141 'interface',
142 [
143 Interface,
144 VMInterface,
145 ],
146 ),
147 only=['interface_type', 'interface_id'],
148 )
149 def interface(self) -> Annotated[
150 Annotated['InterfaceType', strawberry.lazy('dcim.graphql.types')]
151 | Annotated['VMInterfaceType', strawberry.lazy('virtualization.graphql.types')],
152 strawberry.union('FHRPGroupInterfaceType'),
153 ]:
154 return self.interface
157@register_type(
158 models.IPAddress,
159 exclude=['assigned_object_type', 'assigned_object_id', 'address'],
160 filters=IPAddressFilter,
161 pagination=True
162)
163class IPAddressType(ContactsMixin, PrimaryObjectType):
164 address: str
165 vrf: Annotated['VRFType', strawberry.lazy('ipam.graphql.types')] | None
166 tenant: Annotated['TenantType', strawberry.lazy('tenancy.graphql.types')] | None
167 nat_inside: Annotated['IPAddressType', strawberry.lazy('ipam.graphql.types')] | None
168 nat_outside: list[Annotated['IPAddressType', strawberry.lazy('ipam.graphql.types')]]
169 tunnel_terminations: list[Annotated['TunnelTerminationType', strawberry.lazy('vpn.graphql.types')]]
170 services: list[Annotated['ServiceType', strawberry.lazy('ipam.graphql.types')]]
172 # Note that self is the Django model instance, so self.family resolves to the model's family property
173 @strawberry_django.field(only=['address'])
174 def family(self) -> IPAddressFamilyType:
175 return IPAddressFamilyType(value=self.family, label=f'IPv{self.family}')
177 @strawberry_django.field(
178 prefetch_related=build_gfk_prefetch(
179 'assigned_object',
180 [
181 models.FHRPGroup,
182 Interface,
183 VMInterface,
184 ],
185 ),
186 only=['assigned_object_type', 'assigned_object_id'],
187 )
188 def assigned_object(self) -> Annotated[
189 Annotated['InterfaceType', strawberry.lazy('dcim.graphql.types')]
190 | Annotated['FHRPGroupType', strawberry.lazy('ipam.graphql.types')]
191 | Annotated['VMInterfaceType', strawberry.lazy('virtualization.graphql.types')],
192 strawberry.union('IPAddressAssignmentType'),
193 ] | None:
194 return self.assigned_object
197@register_type(
198 models.IPRange,
199 fields='__all__',
200 filters=IPRangeFilter,
201 pagination=True
202)
203class IPRangeType(ContactsMixin, PrimaryObjectType):
204 start_address: str
205 end_address: str
206 vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None
207 tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
208 role: Annotated["RoleType", strawberry.lazy('ipam.graphql.types')] | None
211@register_type(
212 models.Prefix,
213 exclude=['scope_type', 'scope_id', '_location', '_region', '_site', '_site_group'],
214 filters=PrefixFilter,
215 pagination=True
216)
217class PrefixType(ContactsMixin, PrimaryObjectType):
218 prefix: str
219 vrf: Annotated['VRFType', strawberry.lazy('ipam.graphql.types')] | None
220 tenant: Annotated['TenantType', strawberry.lazy('tenancy.graphql.types')] | None
221 vlan: Annotated['VLANType', strawberry.lazy('ipam.graphql.types')] | None
222 role: Annotated['RoleType', strawberry.lazy('ipam.graphql.types')] | None
224 # Note that self is the Django model instance, so self.family resolves to the model's family property
225 @strawberry_django.field(only=['prefix'])
226 def family(self) -> IPAddressFamilyType:
227 return IPAddressFamilyType(value=self.family, label=f'IPv{self.family}')
229 @strawberry_django.field(
230 prefetch_related=build_gfk_prefetch(
231 'scope',
232 [
233 Region,
234 SiteGroup,
235 Site,
236 Location,
237 ],
238 ),
239 only=['scope_type', 'scope_id'],
240 )
241 def scope(self) -> Annotated[
242 Annotated['LocationType', strawberry.lazy('dcim.graphql.types')]
243 | Annotated['RegionType', strawberry.lazy('dcim.graphql.types')]
244 | Annotated['SiteGroupType', strawberry.lazy('dcim.graphql.types')]
245 | Annotated['SiteType', strawberry.lazy('dcim.graphql.types')],
246 strawberry.union('PrefixScopeType'),
247 ] | None:
248 return self.scope
251@register_type(
252 models.RIR,
253 fields='__all__',
254 filters=RIRFilter,
255 pagination=True
256)
257class RIRType(OrganizationalObjectType):
259 asn_ranges: list[Annotated["ASNRangeType", strawberry.lazy('ipam.graphql.types')]]
260 asns: list[Annotated["ASNType", strawberry.lazy('ipam.graphql.types')]]
261 aggregates: list[Annotated["AggregateType", strawberry.lazy('ipam.graphql.types')]]
264@register_type(
265 models.Role,
266 fields='__all__',
267 filters=RoleFilter,
268 pagination=True
269)
270class RoleType(OrganizationalObjectType):
272 prefixes: list[Annotated["PrefixType", strawberry.lazy('ipam.graphql.types')]]
273 ip_ranges: list[Annotated["IPRangeType", strawberry.lazy('ipam.graphql.types')]]
274 vlans: list[Annotated["VLANType", strawberry.lazy('ipam.graphql.types')]]
277@register_type(
278 models.RouteTarget,
279 fields='__all__',
280 filters=RouteTargetFilter,
281 pagination=True
282)
283class RouteTargetType(PrimaryObjectType):
284 tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
286 importing_l2vpns: list[Annotated["L2VPNType", strawberry.lazy('vpn.graphql.types')]]
287 exporting_l2vpns: list[Annotated["L2VPNType", strawberry.lazy('vpn.graphql.types')]]
288 importing_vrfs: list[Annotated["VRFType", strawberry.lazy('ipam.graphql.types')]]
289 exporting_vrfs: list[Annotated["VRFType", strawberry.lazy('ipam.graphql.types')]]
292# Shared deprecation reason for the legacy port-mapping GraphQL fields. The fields themselves are
293# declared on each type (rather than via a mixin) so they reliably override the auto-generated model
294# field of the same name; each delegates to the model's protocol/ports properties (the single source of
295# truth for the legacy view, derived from port_mappings on each access).
296_LEGACY_DEPRECATION = "Deprecated; use port_mappings. Populated only for single-protocol services."
299@register_type(
300 models.Service,
301 exclude=['parent_object_type', 'parent_object_id'],
302 filters=ServiceFilter,
303 pagination=True
304)
305class ServiceType(ContactsMixin, PrimaryObjectType):
306 port_mappings: list[str]
307 ipaddresses: list[Annotated['IPAddressType', strawberry.lazy('ipam.graphql.types')]]
309 @strawberry_django.field(deprecation_reason=_LEGACY_DEPRECATION)
310 def protocol(self) -> str | None:
311 return self.protocol
313 @strawberry_django.field(deprecation_reason=_LEGACY_DEPRECATION)
314 def ports(self) -> list[int] | None:
315 return self.ports
317 @strawberry_django.field(
318 prefetch_related=build_gfk_prefetch(
319 'parent',
320 [
321 Device,
322 VirtualMachine,
323 models.FHRPGroup,
324 ],
325 ),
326 only=['parent_object_type', 'parent_object_id'],
327 )
328 def parent(self) -> Annotated[
329 Annotated['DeviceType', strawberry.lazy('dcim.graphql.types')]
330 | Annotated['VirtualMachineType', strawberry.lazy('virtualization.graphql.types')]
331 | Annotated['FHRPGroupType', strawberry.lazy('ipam.graphql.types')],
332 strawberry.union('ServiceParentType'),
333 ] | None:
334 return self.parent
337@register_type(
338 models.ServiceTemplate,
339 fields='__all__',
340 filters=ServiceTemplateFilter,
341 pagination=True
342)
343class ServiceTemplateType(PrimaryObjectType):
344 port_mappings: list[str]
346 @strawberry_django.field(deprecation_reason=_LEGACY_DEPRECATION)
347 def protocol(self) -> str | None:
348 return self.protocol
350 @strawberry_django.field(deprecation_reason=_LEGACY_DEPRECATION)
351 def ports(self) -> list[int] | None:
352 return self.ports
355@register_type(
356 models.VLAN,
357 exclude=['qinq_svlan'],
358 filters=VLANFilter,
359 pagination=True
360)
361class VLANType(PrimaryObjectType):
362 site: Annotated["SiteType", strawberry.lazy('ipam.graphql.types')] | None
363 group: Annotated["VLANGroupType", strawberry.lazy('ipam.graphql.types')] | None
364 tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
365 role: Annotated["RoleType", strawberry.lazy('ipam.graphql.types')] | None
367 interfaces_as_untagged: list[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]
368 vminterfaces_as_untagged: list[Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')]]
369 wirelesslan_set: list[Annotated["WirelessLANType", strawberry.lazy('wireless.graphql.types')]]
370 prefixes: list[Annotated["PrefixType", strawberry.lazy('ipam.graphql.types')]]
371 interfaces_as_tagged: list[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]
372 vminterfaces_as_tagged: list[Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')]]
374 @strawberry_django.field(prefetch_related='qinq_svlan', only=['qinq_svlan_id'])
375 def qinq_svlan(self) -> Annotated["VLANType", strawberry.lazy('ipam.graphql.types')] | None:
376 return self.qinq_svlan
379@register_type(
380 models.VLANGroup,
381 exclude=['scope_type', 'scope_id'],
382 filters=VLANGroupFilter,
383 pagination=True
384)
385class VLANGroupType(OrganizationalObjectType):
387 vlans: list[VLANType]
388 vid_ranges: list[str]
389 total_vlan_ids: BigInt
390 tenant: Annotated['TenantType', strawberry.lazy('tenancy.graphql.types')] | None
392 @strawberry_django.field(
393 prefetch_related=build_gfk_prefetch(
394 'scope',
395 [
396 Cluster,
397 ClusterGroup,
398 Location,
399 Rack,
400 RackGroup,
401 Region,
402 Site,
403 SiteGroup,
404 ],
405 ),
406 only=['scope_type', 'scope_id'],
407 )
408 def scope(self) -> Annotated[
409 Annotated['ClusterType', strawberry.lazy('virtualization.graphql.types')]
410 | Annotated['ClusterGroupType', strawberry.lazy('virtualization.graphql.types')]
411 | Annotated['LocationType', strawberry.lazy('dcim.graphql.types')]
412 | Annotated['RackGroupType', strawberry.lazy('dcim.graphql.types')]
413 | Annotated['RackType', strawberry.lazy('dcim.graphql.types')]
414 | Annotated['RegionType', strawberry.lazy('dcim.graphql.types')]
415 | Annotated['SiteType', strawberry.lazy('dcim.graphql.types')]
416 | Annotated['SiteGroupType', strawberry.lazy('dcim.graphql.types')],
417 strawberry.union('VLANGroupScopeType'),
418 ] | None:
419 return self.scope
422@register_type(
423 models.VLANTranslationPolicy,
424 fields='__all__',
425 filters=VLANTranslationPolicyFilter,
426 pagination=True
427)
428class VLANTranslationPolicyType(PrimaryObjectType):
429 rules: list[Annotated["VLANTranslationRuleType", strawberry.lazy('ipam.graphql.types')]]
432@register_type(
433 models.VLANTranslationRule,
434 fields='__all__',
435 filters=VLANTranslationRuleFilter,
436 pagination=True
437)
438class VLANTranslationRuleType(NetBoxObjectType):
439 policy: Annotated[
440 "VLANTranslationPolicyType",
441 strawberry.lazy('ipam.graphql.types')
442 ] = strawberry_django.field(select_related=["policy"])
445@register_type(
446 models.VRF,
447 fields='__all__',
448 filters=VRFFilter,
449 pagination=True
450)
451class VRFType(PrimaryObjectType):
452 tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
454 interfaces: list[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]
455 ip_addresses: list[Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')]]
456 vminterfaces: list[Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')]]
457 ip_ranges: list[Annotated["IPRangeType", strawberry.lazy('ipam.graphql.types')]]
458 export_targets: list[Annotated["RouteTargetType", strawberry.lazy('ipam.graphql.types')]]
459 import_targets: list[Annotated["RouteTargetType", strawberry.lazy('ipam.graphql.types')]]
460 prefixes: list[Annotated["PrefixType", strawberry.lazy('ipam.graphql.types')]]