Coverage for ipam/graphql/filters.py: 62%

298 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 netaddr 

4import strawberry 

5import strawberry_django 

6from django.db.models import Q 

7from netaddr.core import AddrFormatError 

8from strawberry.scalars import ID 

9from strawberry_django import BaseFilterLookup, ComparisonFilterLookup, DateFilterLookup, FilterLookup, StrFilterLookup 

10 

11from dcim.graphql.filter_mixins import ScopedFilterMixin 

12from dcim.models import Device 

13from ipam import models 

14from ipam.utils import normalize_port_mapping, port_mapping_q 

15from netbox.graphql.filters import ( 

16 ChangeLoggedModelFilter, 

17 NetBoxModelFilter, 

18 OrganizationalModelFilter, 

19 PrimaryModelFilter, 

20 register_filter, 

21) 

22from tenancy.graphql.filter_mixins import ContactFilterMixin, TenancyFilterMixin 

23from virtualization.models import VMInterface 

24 

25if TYPE_CHECKING: 25 ↛ 26line 25 didn't jump to line 26 because the condition on line 25 was never true

26 from circuits.graphql.filters import ProviderFilter 

27 from core.graphql.filters import ContentTypeFilter 

28 from dcim.graphql.filters import SiteFilter 

29 from netbox.graphql.filter_lookups import BigIntegerLookup, IntegerLookup, IntegerRangeArrayLookup 

30 from vpn.graphql.filters import L2VPNFilter 

31 

32 from .enums import * 

33 

34__all__ = ( 

35 'ASNFilter', 

36 'ASNRangeFilter', 

37 'AggregateFilter', 

38 'FHRPGroupAssignmentFilter', 

39 'FHRPGroupFilter', 

40 'IPAddressFilter', 

41 'IPRangeFilter', 

42 'PrefixFilter', 

43 'RIRFilter', 

44 'RoleFilter', 

45 'RouteTargetFilter', 

46 'ServiceFilter', 

47 'ServiceTemplateFilter', 

48 'VLANFilter', 

49 'VLANGroupFilter', 

50 'VLANTranslationPolicyFilter', 

51 'VLANTranslationRuleFilter', 

52 'VRFFilter', 

53) 

54 

55 

56@register_filter(models.ASN, lookups=True) 

57class ASNFilter(TenancyFilterMixin, PrimaryModelFilter): 

58 rir: Annotated['RIRFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() 

59 rir_id: ID | None = strawberry_django.filter_field() 

60 role: Annotated['RoleFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() 

61 role_id: ID | None = strawberry_django.filter_field() 

62 asn: Annotated['BigIntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( 

63 strawberry_django.filter_field() 

64 ) 

65 sites: ( 

66 Annotated['SiteFilter', strawberry.lazy('dcim.graphql.filters')] | None 

67 ) = strawberry_django.filter_field() 

68 providers: ( 

69 Annotated['ProviderFilter', strawberry.lazy('circuits.graphql.filters')] | None 

70 ) = strawberry_django.filter_field() 

71 

72 

73@register_filter(models.ASNRange, lookups=True) 

74class ASNRangeFilter(TenancyFilterMixin, OrganizationalModelFilter): 

75 name: StrFilterLookup | None = strawberry_django.filter_field() 

76 slug: StrFilterLookup | None = strawberry_django.filter_field() 

77 rir: Annotated['RIRFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() 

78 rir_id: ID | None = strawberry_django.filter_field() 

79 start: Annotated['BigIntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( 

80 strawberry_django.filter_field() 

81 ) 

82 end: Annotated['BigIntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( 

83 strawberry_django.filter_field() 

84 ) 

85 

86 

87@register_filter(models.Aggregate, lookups=True) 

88class AggregateFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter): 

89 prefix: StrFilterLookup | None = strawberry_django.filter_field() 

90 rir: Annotated['RIRFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() 

91 rir_id: ID | None = strawberry_django.filter_field() 

92 date_added: DateFilterLookup | None = strawberry_django.filter_field() 

93 

94 @strawberry_django.filter_field() 

95 def contains(self, value: list[str], prefix) -> Q: 

96 """ 

97 Return aggregates whose `prefix` contains any of the supplied networks. 

98 Mirrors PrefixFilter.contains but operates on the Aggregate.prefix field itself. 

99 """ 

100 if not value: 

101 return Q() 

102 q = Q() 

103 for subnet in value: 

104 try: 

105 query = str(netaddr.IPNetwork(subnet.strip()).cidr) 

106 except (AddrFormatError, ValueError): 

107 continue 

108 q |= Q(**{f"{prefix}prefix__net_contains": query}) 

109 return q 

110 

111 @strawberry_django.filter_field() 

112 def family( 

113 self, 

114 value: Annotated['IPAddressFamilyEnum', strawberry.lazy('ipam.graphql.enums')], 

115 prefix, 

116 ) -> Q: 

117 return Q(**{f"{prefix}prefix__family": value.value}) 

118 

119 

120@register_filter(models.FHRPGroup, lookups=True) 

121class FHRPGroupFilter(PrimaryModelFilter): 

122 group_id: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( 

123 strawberry_django.filter_field() 

124 ) 

125 name: StrFilterLookup | None = strawberry_django.filter_field() 

126 protocol: BaseFilterLookup[Annotated['FHRPGroupProtocolEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( 

127 strawberry_django.filter_field() 

128 ) 

129 auth_type: BaseFilterLookup[Annotated['FHRPGroupAuthTypeEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( 

130 strawberry_django.filter_field() 

131 ) 

132 auth_key: StrFilterLookup | None = strawberry_django.filter_field() 

133 ip_addresses: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( 

134 strawberry_django.filter_field() 

135 ) 

136 

137 

138@register_filter(models.FHRPGroupAssignment, lookups=True) 

139class FHRPGroupAssignmentFilter(ChangeLoggedModelFilter): 

140 interface_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( 

141 strawberry_django.filter_field() 

142 ) 

143 interface_id: StrFilterLookup | None = strawberry_django.filter_field() 

144 group: Annotated['FHRPGroupFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( 

145 strawberry_django.filter_field() 

146 ) 

147 group_id: ID | None = strawberry_django.filter_field() 

148 priority: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( 

149 strawberry_django.filter_field() 

150 ) 

151 

152 @strawberry_django.filter_field() 

153 def device_id(self, value: list[str], prefix) -> Q: 

154 return self.filter_device('id', value, prefix) 

155 

156 @strawberry_django.filter_field() 

157 def device(self, value: list[str], prefix) -> Q: 

158 return self.filter_device('name', value, prefix) 

159 

160 @strawberry_django.filter_field() 

161 def virtual_machine_id(self, value: list[str], prefix) -> Q: 

162 return Q(**{f"{prefix}interface_id__in": VMInterface.objects.filter(virtual_machine_id__in=value)}) 

163 

164 @strawberry_django.filter_field() 

165 def virtual_machine(self, value: list[str], prefix) -> Q: 

166 return Q(**{f"{prefix}interface_id__in": VMInterface.objects.filter(virtual_machine__name__in=value)}) 

167 

168 def filter_device(self, field, value, prefix) -> Q: 

169 """Helper to standardize logic for device and device_id filters""" 

170 devices = Device.objects.filter(**{f'{field}__in': value}) 

171 interface_ids = [] 

172 for device in devices: 

173 interface_ids.extend(device.vc_interfaces().values_list('id', flat=True)) 

174 return Q(**{f"{prefix}interface_id__in": interface_ids}) 

175 

176 

177@register_filter(models.IPAddress, lookups=True) 

178class IPAddressFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter): 

179 address: StrFilterLookup | None = strawberry_django.filter_field() 

180 vrf: Annotated['VRFFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() 

181 vrf_id: ID | None = strawberry_django.filter_field() 

182 status: BaseFilterLookup[Annotated['IPAddressStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( 

183 strawberry_django.filter_field() 

184 ) 

185 role: BaseFilterLookup[Annotated['IPAddressRoleEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( 

186 strawberry_django.filter_field() 

187 ) 

188 assigned_object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( 

189 strawberry_django.filter_field() 

190 ) 

191 assigned_object_id: ID | None = strawberry_django.filter_field() 

192 nat_inside: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( 

193 strawberry_django.filter_field() 

194 ) 

195 nat_inside_id: ID | None = strawberry_django.filter_field() 

196 nat_outside: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( 

197 strawberry_django.filter_field() 

198 ) 

199 nat_outside_id: ID | None = strawberry_django.filter_field() 

200 dns_name: StrFilterLookup | None = strawberry_django.filter_field() 

201 

202 @strawberry_django.filter_field() 

203 def assigned(self, value: bool, prefix) -> Q: 

204 return Q(**{f"{prefix}assigned_object_id__isnull": not value}) 

205 

206 @strawberry_django.filter_field() 

207 def parent(self, value: list[str], prefix) -> Q: 

208 if not value: 

209 return Q() 

210 q = Q() 

211 for subnet in value: 

212 try: 

213 query = str(netaddr.IPNetwork(subnet.strip()).cidr) 

214 except (AddrFormatError, ValueError): 

215 continue 

216 q |= Q(**{f"{prefix}address__net_host_contained": query}) 

217 return q 

218 

219 @strawberry_django.filter_field() 

220 def family( 

221 self, 

222 value: Annotated['IPAddressFamilyEnum', strawberry.lazy('ipam.graphql.enums')], 

223 prefix, 

224 ) -> Q: 

225 return Q(**{f"{prefix}address__family": value.value}) 

226 

227 

228@register_filter(models.IPRange, lookups=True) 

229class IPRangeFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter): 

230 start_address: StrFilterLookup | None = strawberry_django.filter_field() 

231 end_address: StrFilterLookup | None = strawberry_django.filter_field() 

232 size: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( 

233 strawberry_django.filter_field() 

234 ) 

235 vrf: Annotated['VRFFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() 

236 vrf_id: ID | None = strawberry_django.filter_field() 

237 status: BaseFilterLookup[Annotated['IPRangeStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( 

238 strawberry_django.filter_field() 

239 ) 

240 role: Annotated['RoleFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() 

241 mark_utilized: FilterLookup[bool] | None = strawberry_django.filter_field() 

242 

243 @strawberry_django.filter_field() 

244 def parent(self, value: list[str], prefix) -> Q: 

245 if not value: 

246 return Q() 

247 q = Q() 

248 for subnet in value: 

249 try: 

250 query = str(netaddr.IPNetwork(subnet.strip()).cidr) 

251 except (AddrFormatError, ValueError): 

252 continue 

253 q |= Q( 

254 **{ 

255 f"{prefix}start_address__net_host_contained": query, 

256 f"{prefix}end_address__net_host_contained": query, 

257 } 

258 ) 

259 return q 

260 

261 @strawberry_django.filter_field() 

262 def contains(self, value: list[str], prefix) -> Q: 

263 if not value: 

264 return Q() 

265 q = Q() 

266 for subnet in value: 

267 try: 

268 net = netaddr.IPNetwork(subnet.strip()) 

269 query_start = str(netaddr.IPAddress(net.first)) 

270 query_end = str(netaddr.IPAddress(net.last)) 

271 except (AddrFormatError, ValueError): 

272 continue 

273 q |= Q( 

274 **{ 

275 f"{prefix}start_address__host__inet__lte": query_start, 

276 f"{prefix}end_address__host__inet__gte": query_end, 

277 } 

278 ) 

279 return q 

280 

281 

282@register_filter(models.Prefix, lookups=True) 

283class PrefixFilter(ContactFilterMixin, ScopedFilterMixin, TenancyFilterMixin, PrimaryModelFilter): 

284 prefix: StrFilterLookup | None = strawberry_django.filter_field() 

285 vrf: Annotated['VRFFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() 

286 vrf_id: ID | None = strawberry_django.filter_field() 

287 vlan: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() 

288 vlan_id: ID | None = strawberry_django.filter_field() 

289 status: BaseFilterLookup[Annotated['PrefixStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( 

290 strawberry_django.filter_field() 

291 ) 

292 role: Annotated['RoleFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() 

293 role_id: ID | None = strawberry_django.filter_field() 

294 is_pool: FilterLookup[bool] | None = strawberry_django.filter_field() 

295 mark_utilized: FilterLookup[bool] | None = strawberry_django.filter_field() 

296 

297 @strawberry_django.filter_field() 

298 def contains(self, value: list[str], prefix) -> Q: 

299 if not value: 

300 return Q() 

301 q = Q() 

302 for subnet in value: 

303 try: 

304 query = str(netaddr.IPNetwork(subnet.strip()).cidr) 

305 except (AddrFormatError, ValueError): 

306 continue 

307 q |= Q(**{f"{prefix}prefix__net_contains": query}) 

308 return q 

309 

310 @strawberry_django.filter_field() 

311 def family( 

312 self, 

313 value: Annotated['IPAddressFamilyEnum', strawberry.lazy('ipam.graphql.enums')], 

314 prefix, 

315 ) -> Q: 

316 return Q(**{f"{prefix}prefix__family": value.value}) 

317 

318 

319@register_filter(models.RIR, lookups=True) 

320class RIRFilter(OrganizationalModelFilter): 

321 is_private: FilterLookup[bool] | None = strawberry_django.filter_field() 

322 

323 

324@register_filter(models.Role, lookups=True) 

325class RoleFilter(OrganizationalModelFilter): 

326 weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( 

327 strawberry_django.filter_field() 

328 ) 

329 

330 

331@register_filter(models.RouteTarget, lookups=True) 

332class RouteTargetFilter(TenancyFilterMixin, PrimaryModelFilter): 

333 name: StrFilterLookup | None = strawberry_django.filter_field() 

334 importing_vrfs: Annotated['VRFFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( 

335 strawberry_django.filter_field() 

336 ) 

337 exporting_vrfs: Annotated['VRFFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( 

338 strawberry_django.filter_field() 

339 ) 

340 importing_l2vpns: Annotated['L2VPNFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( 

341 strawberry_django.filter_field() 

342 ) 

343 exporting_l2vpns: Annotated['L2VPNFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( 

344 strawberry_django.filter_field() 

345 ) 

346 

347 

348# Custom (method-based) GraphQL filters can't be inherited from a mixin — strawberry_django only picks 

349# up filter_field methods declared on the filter_type class itself — so the filters below keep thin 

350# wrappers here. Each method reads *all* of its siblings' values off ``self`` so a combined 

351# protocol/port query matches a single mapping rather than each condition independently. 

352 

353# GraphQL port filter name -> the port lookup it applies, in the order the conditions are built. The 

354# names deliberately carry the same `port__gt` double-underscore form as their REST counterparts in 

355# ipam.filtersets.SERVICE_PORT_FILTERS, so both APIs offer an identically-spelled set of lookups. 

356# (GraphQL reserves only *leading* double underscores, for introspection.) The schema is built with 

357# auto_camel_case=False, so these names reach the schema verbatim. 

358# See ipam.utils.PORT_MAPPING_LOOKUPS for the lookup -> SQL operator mapping. 

359GRAPHQL_PORT_FILTERS = { 

360 'port': 'exact', 

361 'port__gt': 'gt', 

362 'port__gte': 'gte', 

363 'port__lt': 'lt', 

364 'port__lte': 'lte', 

365} 

366 

367# `protocol` plus every port lookup, in the order their conditions are built. All of these must be 

368# satisfied by one single mapping, so they can't be applied as each resolver runs; the first one 

369# actually supplied owns the combined predicate (see _owns_predicate). 

370CORRELATED_PORT_FILTERS = ('protocol', *GRAPHQL_PORT_FILTERS) 

371 

372 

373def _supplied(filters, name): 

374 """ 

375 Return the list of values supplied for a sibling filter field, or None if it was not supplied. Only 

376 a list is a real value: an omitted field is None/UNSET, and an unset method-based filter can resolve 

377 to the bound method itself, so anything non-list is reported as absent rather than surfacing as a 

378 TypeError downstream. 

379 """ 

380 value = getattr(filters, name, None) 

381 return list(value) if isinstance(value, (list, tuple)) else None 

382 

383 

384def _port_mapping_args(filters): 

385 """ 

386 Collect the correlated protocol/port arguments from every sibling field on the filter instance, in 

387 the ``(protocols, port_tests)`` shapes ``port_mapping_q()`` expects. 

388 """ 

389 protocols = [v.value for v in _supplied(filters, 'protocol') or ()] 

390 port_tests = [ 

391 (lookup, values) 

392 for name, lookup in GRAPHQL_PORT_FILTERS.items() 

393 if (values := _supplied(filters, name)) 

394 ] 

395 return protocols, port_tests 

396 

397 

398def _owns_predicate(filters, name): 

399 """ 

400 True for exactly one of the correlated filter fields: the first one supplied, in 

401 ``CORRELATED_PORT_FILTERS`` order. The others contribute nothing, so a query combining N of them 

402 builds the (deliberately sequential) scan once rather than ANDing N identical copies of it. 

403 """ 

404 for candidate in CORRELATED_PORT_FILTERS: 

405 if _supplied(filters, candidate) is not None: 

406 return candidate == name 

407 return False 

408 

409 

410def _port_mapping_prefix_q(model, protocols, port_tests, prefix): 

411 qs_filter = port_mapping_q(protocols, port_tests) 

412 if prefix: 

413 # Nested relation (e.g. prefix='services__'): the incoming queryset is a *different* model, so 

414 # resolve the matching PKs on the target model and match them through the prefix. 

415 return Q(**{f'{prefix}pk__in': model.objects.filter(qs_filter).values('pk')}) 

416 # Root query: the incoming queryset already targets this model, so return the lookup directly rather 

417 # than wrapping it in an extra pk__in self-subquery. 

418 return qs_filter 

419 

420 

421def _make_port_mapping_filters(model): 

422 # strawberry_django only collects filter_field methods declared on the filter_type class itself (not 

423 # from a mixin), so the Service/ServiceTemplate filters are produced by this factory and assigned 

424 # into each class body. This keeps the protocol/port correlation logic in a single place. 

425 

426 def correlated(filters, name, prefix): 

427 # Deliberately ignores the resolver's own `value` in favour of reading every sibling off 

428 # `filters`: only the owning field applies the predicate, and it needs them all. 

429 if not _owns_predicate(filters, name): 

430 return Q() 

431 protocols, port_tests = _port_mapping_args(filters) 

432 return _port_mapping_prefix_q(model, protocols, port_tests, prefix) 

433 

434 @strawberry_django.filter_field 

435 def protocol( 

436 self, 

437 queryset, 

438 value: list[Annotated['ServiceProtocolEnum', strawberry.lazy('ipam.graphql.enums')]], 

439 prefix, 

440 ): 

441 return correlated(self, 'protocol', prefix) 

442 

443 # `port` and its range lookups. Values within one lookup are OR'd (as ?port=80&port=443 is on the 

444 # REST API); the lookups themselves are AND'd, and so must hold for one single mapping. 

445 @strawberry_django.filter_field 

446 def port(self, queryset, value: list[int], prefix): 

447 return correlated(self, 'port', prefix) 

448 

449 @strawberry_django.filter_field 

450 def port__gt(self, queryset, value: list[int], prefix): 

451 return correlated(self, 'port__gt', prefix) 

452 

453 @strawberry_django.filter_field 

454 def port__gte(self, queryset, value: list[int], prefix): 

455 return correlated(self, 'port__gte', prefix) 

456 

457 @strawberry_django.filter_field 

458 def port__lt(self, queryset, value: list[int], prefix): 

459 return correlated(self, 'port__lt', prefix) 

460 

461 @strawberry_django.filter_field 

462 def port__lte(self, queryset, value: list[int], prefix): 

463 return correlated(self, 'port__lte', prefix) 

464 

465 @strawberry_django.filter_field 

466 def port_mappings(self, queryset, value: list[str], prefix): 

467 # Whole-mapping lookup (e.g. ["tcp/80", "udp/53"], matching any). Each value names one complete 

468 # protocol/port pair, so unlike protocol/port this needs no correlation and reduces to a 

469 # GIN-indexable array overlap. Values are normalized so 'TCP/080' finds the stored 'tcp/80'. 

470 mappings = [normalize_port_mapping(mapping) for mapping in value] 

471 return Q(**{f'{prefix}port_mappings__overlap': mappings}) 

472 

473 return protocol, port, port__gt, port__gte, port__lt, port__lte, port_mappings 

474 

475 

476@register_filter(models.Service, lookups=True) 

477class ServiceFilter(ContactFilterMixin, PrimaryModelFilter): 

478 name: StrFilterLookup | None = strawberry_django.filter_field() 

479 ip_addresses: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( 

480 strawberry_django.filter_field() 

481 ) 

482 parent_object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( 

483 strawberry_django.filter_field() 

484 ) 

485 parent_object_id: ID | None = strawberry_django.filter_field() 

486 protocol, port, port__gt, port__gte, port__lt, port__lte, port_mappings = ( 

487 _make_port_mapping_filters(models.Service) 

488 ) 

489 

490 

491@register_filter(models.ServiceTemplate, lookups=True) 

492class ServiceTemplateFilter(PrimaryModelFilter): 

493 name: StrFilterLookup | None = strawberry_django.filter_field() 

494 protocol, port, port__gt, port__gte, port__lt, port__lte, port_mappings = ( 

495 _make_port_mapping_filters(models.ServiceTemplate) 

496 ) 

497 

498 

499@register_filter(models.VLAN, lookups=True) 

500class VLANFilter(TenancyFilterMixin, PrimaryModelFilter): 

501 site: Annotated['SiteFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() 

502 site_id: ID | None = strawberry_django.filter_field() 

503 group: Annotated['VLANGroupFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( 

504 strawberry_django.filter_field() 

505 ) 

506 group_id: ID | None = strawberry_django.filter_field() 

507 vid: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( 

508 strawberry_django.filter_field() 

509 ) 

510 name: StrFilterLookup | None = strawberry_django.filter_field() 

511 status: BaseFilterLookup[Annotated['VLANStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( 

512 strawberry_django.filter_field() 

513 ) 

514 role: Annotated['RoleFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() 

515 role_id: ID | None = strawberry_django.filter_field() 

516 qinq_svlan: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( 

517 strawberry_django.filter_field() 

518 ) 

519 qinq_svlan_id: ID | None = strawberry_django.filter_field() 

520 qinq_cvlans: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( 

521 strawberry_django.filter_field() 

522 ) 

523 qinq_role: BaseFilterLookup[Annotated['VLANQinQRoleEnum', strawberry.lazy('ipam.graphql.enums')]] | None = ( 

524 strawberry_django.filter_field() 

525 ) 

526 l2vpn_terminations: Annotated['L2VPNFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( 

527 strawberry_django.filter_field() 

528 ) 

529 

530 

531@register_filter(models.VLANGroup, lookups=True) 

532class VLANGroupFilter(ScopedFilterMixin, OrganizationalModelFilter): 

533 vid_ranges: Annotated['IntegerRangeArrayLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( 

534 strawberry_django.filter_field() 

535 ) 

536 total_vlan_ids: ComparisonFilterLookup[int] | None = strawberry_django.filter_field() 

537 

538 

539@register_filter(models.VLANTranslationPolicy, lookups=True) 

540class VLANTranslationPolicyFilter(PrimaryModelFilter): 

541 name: StrFilterLookup | None = strawberry_django.filter_field() 

542 

543 

544@register_filter(models.VLANTranslationRule, lookups=True) 

545class VLANTranslationRuleFilter(NetBoxModelFilter): 

546 policy: Annotated['VLANTranslationPolicyFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( 

547 strawberry_django.filter_field() 

548 ) 

549 policy_id: ID | None = strawberry_django.filter_field() 

550 description: StrFilterLookup | None = strawberry_django.filter_field() 

551 local_vid: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( 

552 strawberry_django.filter_field() 

553 ) 

554 remote_vid: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( 

555 strawberry_django.filter_field() 

556 ) 

557 

558 

559@register_filter(models.VRF, lookups=True) 

560class VRFFilter(TenancyFilterMixin, PrimaryModelFilter): 

561 name: StrFilterLookup | None = strawberry_django.filter_field() 

562 rd: StrFilterLookup | None = strawberry_django.filter_field() 

563 enforce_unique: FilterLookup[bool] | None = strawberry_django.filter_field() 

564 import_targets: Annotated['RouteTargetFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( 

565 strawberry_django.filter_field() 

566 ) 

567 export_targets: Annotated['RouteTargetFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( 

568 strawberry_django.filter_field() 

569 )