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

1from typing import TYPE_CHECKING, Annotated 

2 

3import strawberry 

4import strawberry_django 

5 

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 

21 

22from .filters import * 

23from .mixins import IPAddressesMixin 

24 

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 

39 

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) 

60 

61 

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 

72 

73 

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 

85 

86 sites: list[SiteType] 

87 providers: list[ProviderType] 

88 

89 

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 

101 

102 

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 

113 

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}') 

118 

119 

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')]] 

128 

129 

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')] 

138 

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 

155 

156 

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')]] 

171 

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}') 

176 

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 

195 

196 

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 

209 

210 

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 

223 

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}') 

228 

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 

249 

250 

251@register_type( 

252 models.RIR, 

253 fields='__all__', 

254 filters=RIRFilter, 

255 pagination=True 

256) 

257class RIRType(OrganizationalObjectType): 

258 

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')]] 

262 

263 

264@register_type( 

265 models.Role, 

266 fields='__all__', 

267 filters=RoleFilter, 

268 pagination=True 

269) 

270class RoleType(OrganizationalObjectType): 

271 

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')]] 

275 

276 

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 

285 

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')]] 

290 

291 

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." 

297 

298 

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')]] 

308 

309 @strawberry_django.field(deprecation_reason=_LEGACY_DEPRECATION) 

310 def protocol(self) -> str | None: 

311 return self.protocol 

312 

313 @strawberry_django.field(deprecation_reason=_LEGACY_DEPRECATION) 

314 def ports(self) -> list[int] | None: 

315 return self.ports 

316 

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 

335 

336 

337@register_type( 

338 models.ServiceTemplate, 

339 fields='__all__', 

340 filters=ServiceTemplateFilter, 

341 pagination=True 

342) 

343class ServiceTemplateType(PrimaryObjectType): 

344 port_mappings: list[str] 

345 

346 @strawberry_django.field(deprecation_reason=_LEGACY_DEPRECATION) 

347 def protocol(self) -> str | None: 

348 return self.protocol 

349 

350 @strawberry_django.field(deprecation_reason=_LEGACY_DEPRECATION) 

351 def ports(self) -> list[int] | None: 

352 return self.ports 

353 

354 

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 

366 

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')]] 

373 

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 

377 

378 

379@register_type( 

380 models.VLANGroup, 

381 exclude=['scope_type', 'scope_id'], 

382 filters=VLANGroupFilter, 

383 pagination=True 

384) 

385class VLANGroupType(OrganizationalObjectType): 

386 

387 vlans: list[VLANType] 

388 vid_ranges: list[str] 

389 total_vlan_ids: BigInt 

390 tenant: Annotated['TenantType', strawberry.lazy('tenancy.graphql.types')] | None 

391 

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 

420 

421 

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')]] 

430 

431 

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"]) 

443 

444 

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 

453 

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')]]