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
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
1from typing import TYPE_CHECKING, Annotated
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
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
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
32 from .enums import *
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)
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()
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 )
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()
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
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})
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 )
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 )
152 @strawberry_django.filter_field()
153 def device_id(self, value: list[str], prefix) -> Q:
154 return self.filter_device('id', value, prefix)
156 @strawberry_django.filter_field()
157 def device(self, value: list[str], prefix) -> Q:
158 return self.filter_device('name', value, prefix)
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)})
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)})
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})
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()
202 @strawberry_django.filter_field()
203 def assigned(self, value: bool, prefix) -> Q:
204 return Q(**{f"{prefix}assigned_object_id__isnull": not value})
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
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})
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()
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
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
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()
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
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})
319@register_filter(models.RIR, lookups=True)
320class RIRFilter(OrganizationalModelFilter):
321 is_private: FilterLookup[bool] | None = strawberry_django.filter_field()
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 )
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 )
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.
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}
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)
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
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
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
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
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.
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)
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)
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)
449 @strawberry_django.filter_field
450 def port__gt(self, queryset, value: list[int], prefix):
451 return correlated(self, 'port__gt', prefix)
453 @strawberry_django.filter_field
454 def port__gte(self, queryset, value: list[int], prefix):
455 return correlated(self, 'port__gte', prefix)
457 @strawberry_django.filter_field
458 def port__lt(self, queryset, value: list[int], prefix):
459 return correlated(self, 'port__lt', prefix)
461 @strawberry_django.filter_field
462 def port__lte(self, queryset, value: list[int], prefix):
463 return correlated(self, 'port__lte', prefix)
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})
473 return protocol, port, port__gt, port__gte, port__lt, port__lte, port_mappings
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 )
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 )
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 )
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()
539@register_filter(models.VLANTranslationPolicy, lookups=True)
540class VLANTranslationPolicyFilter(PrimaryModelFilter):
541 name: StrFilterLookup | None = strawberry_django.filter_field()
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 )
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 )