Просмотр исходного кода

Merge pull request #20829 from netbox-community/19338-graphql-in_list-on-feature

Closes: #19338 - GraphQL: Adds in_list lookups for id and enum fields
bctiemann 2 месяцев назад
Родитель
Сommit
1505285aff

+ 12 - 6
netbox/circuits/graphql/filters.py

@@ -4,7 +4,7 @@ from typing import Annotated, TYPE_CHECKING
 import strawberry
 import strawberry_django
 from strawberry.scalars import ID
-from strawberry_django import FilterLookup, DateFilterLookup
+from strawberry_django import BaseFilterLookup, FilterLookup, DateFilterLookup
 
 from circuits import models
 from core.graphql.filter_mixins import BaseObjectTypeFilterMixin, ChangeLogFilterMixin
@@ -52,7 +52,9 @@ class CircuitTerminationFilter(
     circuit: Annotated['CircuitFilter', strawberry.lazy('circuits.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
-    term_side: Annotated['CircuitTerminationSideEnum', strawberry.lazy('circuits.graphql.enums')] | None = (
+    term_side: (
+        BaseFilterLookup[Annotated['CircuitTerminationSideEnum', strawberry.lazy('circuits.graphql.enums')]] | None
+    ) = (
         strawberry_django.filter_field()
     )
     termination_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = (
@@ -108,7 +110,7 @@ class CircuitFilter(
         strawberry_django.filter_field()
     )
     type_id: ID | None = strawberry_django.filter_field()
-    status: Annotated['CircuitStatusEnum', strawberry.lazy('circuits.graphql.enums')] | None = (
+    status: BaseFilterLookup[Annotated['CircuitStatusEnum', strawberry.lazy('circuits.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     install_date: DateFilterLookup[date] | None = strawberry_django.filter_field()
@@ -143,7 +145,7 @@ class CircuitGroupAssignmentFilter(
         strawberry_django.filter_field()
     )
     group_id: ID | None = strawberry_django.filter_field()
-    priority: Annotated['CircuitPriorityEnum', strawberry.lazy('circuits.graphql.enums')] | None = (
+    priority: BaseFilterLookup[Annotated['CircuitPriorityEnum', strawberry.lazy('circuits.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
 
@@ -198,7 +200,7 @@ class VirtualCircuitFilter(TenancyFilterMixin, PrimaryModelFilterMixin):
         strawberry_django.filter_field()
     )
     type_id: ID | None = strawberry_django.filter_field()
-    status: Annotated['CircuitStatusEnum', strawberry.lazy('circuits.graphql.enums')] | None = (
+    status: BaseFilterLookup[Annotated['CircuitStatusEnum', strawberry.lazy('circuits.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     group_assignments: Annotated['CircuitGroupAssignmentFilter', strawberry.lazy('circuits.graphql.filters')] | None = (
@@ -214,7 +216,11 @@ class VirtualCircuitTerminationFilter(
         strawberry_django.filter_field()
     )
     virtual_circuit_id: ID | None = strawberry_django.filter_field()
-    role: Annotated['VirtualCircuitTerminationRoleEnum', strawberry.lazy('circuits.graphql.enums')] | None = (
+    role: (
+        BaseFilterLookup[
+            Annotated['VirtualCircuitTerminationRoleEnum', strawberry.lazy('circuits.graphql.enums')]
+        ] | None
+    ) = (
         strawberry_django.filter_field()
     )
     interface: Annotated['InterfaceFilter', strawberry.lazy('dcim.graphql.filters')] | None = (

+ 11 - 0
netbox/core/graphql/enums.py

@@ -0,0 +1,11 @@
+import strawberry
+
+from core.choices import *
+
+__all__ = (
+    'DataSourceStatusEnum',
+    'ObjectChangeActionEnum',
+)
+
+DataSourceStatusEnum = strawberry.enum(DataSourceStatusChoices.as_enum(prefix='status'))
+ObjectChangeActionEnum = strawberry.enum(ObjectChangeActionChoices.as_enum(prefix='action'))

+ 4 - 3
netbox/core/graphql/filter_mixins.py

@@ -5,7 +5,7 @@ from typing import Annotated, TYPE_CHECKING
 import strawberry
 import strawberry_django
 from strawberry import ID
-from strawberry_django import DatetimeFilterLookup
+from strawberry_django import FilterLookup, DatetimeFilterLookup
 
 if TYPE_CHECKING:
     from .filters import *
@@ -23,12 +23,13 @@ class BaseFilterMixin: ...
 
 @dataclass
 class BaseObjectTypeFilterMixin(BaseFilterMixin):
-    id: ID | None = strawberry.UNSET
+    id: FilterLookup[ID] | None = strawberry_django.filter_field()
 
 
 @dataclass
 class ChangeLogFilterMixin(BaseFilterMixin):
-    id: ID | None = strawberry.UNSET
+    id: FilterLookup[ID] | None = strawberry_django.filter_field()
+    # TODO: "changelog" is not a valid field name; needs to be updated for ObjectChange
     changelog: Annotated['ObjectChangeFilter', strawberry.lazy('core.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )

+ 11 - 6
netbox/core/graphql/filters.py

@@ -5,11 +5,12 @@ import strawberry
 import strawberry_django
 from django.contrib.contenttypes.models import ContentType as DjangoContentType
 from strawberry.scalars import ID
-from strawberry_django import DatetimeFilterLookup, FilterLookup
+from strawberry_django import BaseFilterLookup, DatetimeFilterLookup, FilterLookup
 
 from core import models
 from core.graphql.filter_mixins import BaseFilterMixin
 from netbox.graphql.filter_mixins import PrimaryModelFilterMixin
+from .enums import *
 
 if TYPE_CHECKING:
     from netbox.graphql.filter_lookups import IntegerLookup, JSONFilter
@@ -25,7 +26,7 @@ __all__ = (
 
 @strawberry_django.filter_type(models.DataFile, lookups=True)
 class DataFileFilter(BaseFilterMixin):
-    id: ID | None = strawberry_django.filter_field()
+    id: FilterLookup[ID] | None = strawberry_django.filter_field()
     created: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field()
     last_updated: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field()
     source: Annotated['DataSourceFilter', strawberry.lazy('core.graphql.filters')] | None = (
@@ -44,7 +45,9 @@ class DataSourceFilter(PrimaryModelFilterMixin):
     name: FilterLookup[str] | None = strawberry_django.filter_field()
     type: FilterLookup[str] | None = strawberry_django.filter_field()
     source_url: FilterLookup[str] | None = strawberry_django.filter_field()
-    status: FilterLookup[str] | None = strawberry_django.filter_field()
+    status: (
+        BaseFilterLookup[Annotated['DataSourceStatusEnum', strawberry.lazy('core.graphql.enums')]] | None
+    ) = strawberry_django.filter_field()
     enabled: FilterLookup[bool] | None = strawberry_django.filter_field()
     ignore_rules: FilterLookup[str] | None = strawberry_django.filter_field()
     parameters: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
@@ -58,12 +61,14 @@ class DataSourceFilter(PrimaryModelFilterMixin):
 
 @strawberry_django.filter_type(models.ObjectChange, lookups=True)
 class ObjectChangeFilter(BaseFilterMixin):
-    id: ID | None = strawberry_django.filter_field()
+    id: FilterLookup[ID] | None = strawberry_django.filter_field()
     time: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field()
     user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field()
     user_name: FilterLookup[str] | None = strawberry_django.filter_field()
     request_id: FilterLookup[str] | None = strawberry_django.filter_field()
-    action: FilterLookup[str] | None = strawberry_django.filter_field()
+    action: (
+        BaseFilterLookup[Annotated['ObjectChangeActionEnum', strawberry.lazy('core.graphql.enums')]] | None
+    ) = strawberry_django.filter_field()
     changed_object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
@@ -84,6 +89,6 @@ class ObjectChangeFilter(BaseFilterMixin):
 
 @strawberry_django.filter_type(DjangoContentType, lookups=True)
 class ContentTypeFilter(BaseFilterMixin):
-    id: ID | None = strawberry_django.filter_field()
+    id: FilterLookup[ID] | None = strawberry_django.filter_field()
     app_label: FilterLookup[str] | None = strawberry_django.filter_field()
     model: FilterLookup[str] | None = strawberry_django.filter_field()

+ 4 - 0
netbox/dcim/graphql/enums.py

@@ -28,11 +28,13 @@ __all__ = (
     'PowerFeedSupplyEnum',
     'PowerFeedTypeEnum',
     'PowerOutletFeedLegEnum',
+    'PowerOutletStatusEnum',
     'PowerOutletTypeEnum',
     'PowerPortTypeEnum',
     'RackAirflowEnum',
     'RackDimensionUnitEnum',
     'RackFormFactorEnum',
+    'RackReservationStatusEnum',
     'RackStatusEnum',
     'RackWidthEnum',
     'SiteStatusEnum',
@@ -65,11 +67,13 @@ PowerFeedStatusEnum = strawberry.enum(PowerFeedStatusChoices.as_enum(prefix='sta
 PowerFeedSupplyEnum = strawberry.enum(PowerFeedSupplyChoices.as_enum(prefix='supply'))
 PowerFeedTypeEnum = strawberry.enum(PowerFeedTypeChoices.as_enum(prefix='type'))
 PowerOutletFeedLegEnum = strawberry.enum(PowerOutletFeedLegChoices.as_enum(prefix='feed_leg'))
+PowerOutletStatusEnum = strawberry.enum(PowerOutletStatusChoices.as_enum(prefix='status'))
 PowerOutletTypeEnum = strawberry.enum(PowerOutletTypeChoices.as_enum(prefix='type'))
 PowerPortTypeEnum = strawberry.enum(PowerPortTypeChoices.as_enum(prefix='type'))
 RackAirflowEnum = strawberry.enum(RackAirflowChoices.as_enum())
 RackDimensionUnitEnum = strawberry.enum(RackDimensionUnitChoices.as_enum(prefix='unit'))
 RackFormFactorEnum = strawberry.enum(RackFormFactorChoices.as_enum(prefix='type'))
+RackReservationStatusEnum = strawberry.enum(RackReservationStatusChoices.as_enum(prefix='status'))
 RackStatusEnum = strawberry.enum(RackStatusChoices.as_enum(prefix='status'))
 RackWidthEnum = strawberry.enum(RackWidthChoices.as_enum(prefix='width'))
 SiteStatusEnum = strawberry.enum(SiteStatusChoices.as_enum(prefix='status'))

+ 14 - 7
netbox/dcim/graphql/filter_mixins.py

@@ -4,7 +4,7 @@ from typing import Annotated, TYPE_CHECKING
 import strawberry
 import strawberry_django
 from strawberry import ID
-from strawberry_django import FilterLookup
+from strawberry_django import BaseFilterLookup, FilterLookup
 
 from core.graphql.filter_mixins import BaseFilterMixin, ChangeLogFilterMixin
 from core.graphql.filters import ContentTypeFilter
@@ -60,7 +60,9 @@ class ModularComponentModelFilterMixin(ComponentModelFilterMixin):
 class CabledObjectModelFilterMixin(BaseFilterMixin):
     cable: Annotated['CableFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
     cable_id: ID | None = strawberry_django.filter_field()
-    cable_end: CableEndEnum | None = strawberry_django.filter_field()
+    cable_end: (
+        BaseFilterLookup[Annotated['CableEndEnum', strawberry.lazy('dcim.graphql.enums')]] | None
+    ) = strawberry_django.filter_field()
     mark_connected: FilterLookup[bool] | None = strawberry_django.filter_field()
 
 
@@ -96,7 +98,9 @@ class InterfaceBaseFilterMixin(BaseFilterMixin):
     mtu: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
-    mode: InterfaceModeEnum | None = strawberry_django.filter_field()
+    mode: (
+        BaseFilterLookup[Annotated['InterfaceModeEnum', strawberry.lazy('dcim.graphql.enums')]] | None
+    ) = strawberry_django.filter_field()
     bridge: Annotated['InterfaceFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
@@ -110,8 +114,9 @@ class InterfaceBaseFilterMixin(BaseFilterMixin):
     qinq_svlan: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
-    vlan_translation_policy: Annotated['VLANTranslationPolicyFilter', strawberry.lazy('ipam.graphql.filters')] | None \
-        = strawberry_django.filter_field()
+    vlan_translation_policy: (
+            Annotated['VLANTranslationPolicyFilter', strawberry.lazy('ipam.graphql.filters')] | None
+    ) = strawberry_django.filter_field()
     primary_mac_address: Annotated['MACAddressFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
@@ -120,7 +125,9 @@ class InterfaceBaseFilterMixin(BaseFilterMixin):
 
 @dataclass
 class RackBaseFilterMixin(WeightFilterMixin, PrimaryModelFilterMixin):
-    width: Annotated['RackWidthEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field()
+    width: BaseFilterLookup[Annotated['RackWidthEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
     u_height: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
@@ -137,7 +144,7 @@ class RackBaseFilterMixin(WeightFilterMixin, PrimaryModelFilterMixin):
     outer_depth: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
-    outer_unit: Annotated['RackDimensionUnitEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    outer_unit: BaseFilterLookup[Annotated['RackDimensionUnitEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     mounting_depth: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (

+ 103 - 59
netbox/dcim/graphql/filters.py

@@ -4,7 +4,7 @@ from django.db.models import Q
 import strawberry
 import strawberry_django
 from strawberry.scalars import ID
-from strawberry_django import ComparisonFilterLookup, FilterLookup
+from strawberry_django import BaseFilterLookup, ComparisonFilterLookup, FilterLookup
 
 from core.graphql.filter_mixins import ChangeLogFilterMixin
 from dcim import models
@@ -97,14 +97,20 @@ __all__ = (
 
 @strawberry_django.filter_type(models.Cable, lookups=True)
 class CableFilter(PrimaryModelFilterMixin, TenancyFilterMixin):
-    type: Annotated['CableTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field()
-    status: Annotated['LinkStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field()
+    type: BaseFilterLookup[Annotated['CableTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
+    status: BaseFilterLookup[Annotated['LinkStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
     label: FilterLookup[str] | None = strawberry_django.filter_field()
-    color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field()
+    color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
     length: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
-    length_unit: Annotated['CableLengthUnitEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    length_unit: BaseFilterLookup[Annotated['CableLengthUnitEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     terminations: Annotated['CableTerminationFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
@@ -116,7 +122,7 @@ class CableFilter(PrimaryModelFilterMixin, TenancyFilterMixin):
 class CableTerminationFilter(ChangeLogFilterMixin):
     cable: Annotated['CableFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
     cable_id: ID | None = strawberry_django.filter_field()
-    cable_end: Annotated['CableEndEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    cable_end: BaseFilterLookup[Annotated['CableEndEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     termination_type: Annotated['CableTerminationFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
@@ -127,34 +133,34 @@ class CableTerminationFilter(ChangeLogFilterMixin):
 
 @strawberry_django.filter_type(models.ConsolePort, lookups=True)
 class ConsolePortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin):
-    type: Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    type: BaseFilterLookup[Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
-    speed: Annotated['ConsolePortSpeedEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    speed: BaseFilterLookup[Annotated['ConsolePortSpeedEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
 
 
 @strawberry_django.filter_type(models.ConsolePortTemplate, lookups=True)
 class ConsolePortTemplateFilter(ModularComponentTemplateFilterMixin):
-    type: Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    type: BaseFilterLookup[Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
 
 
 @strawberry_django.filter_type(models.ConsoleServerPort, lookups=True)
 class ConsoleServerPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin):
-    type: Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    type: BaseFilterLookup[Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
-    speed: Annotated['ConsolePortSpeedEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    speed: BaseFilterLookup[Annotated['ConsolePortSpeedEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
 
 
 @strawberry_django.filter_type(models.ConsoleServerPortTemplate, lookups=True)
 class ConsoleServerPortTemplateFilter(ModularComponentTemplateFilterMixin):
-    type: Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    type: BaseFilterLookup[Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
 
@@ -195,11 +201,13 @@ class DeviceFilter(
     position: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
-    face: Annotated['DeviceFaceEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field()
-    status: Annotated['DeviceStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    face: BaseFilterLookup[Annotated['DeviceFaceEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
+    status: BaseFilterLookup[Annotated['DeviceStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
-    airflow: Annotated['DeviceAirflowEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    airflow: BaseFilterLookup[Annotated['DeviceAirflowEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     primary_ip4: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = (
@@ -311,7 +319,9 @@ class InventoryItemTemplateFilter(ComponentTemplateFilterMixin):
 
 @strawberry_django.filter_type(models.DeviceRole, lookups=True)
 class DeviceRoleFilter(OrganizationalModelFilterMixin, RenderConfigFilterMixin):
-    color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field()
+    color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
     vm_role: FilterLookup[bool] | None = strawberry_django.filter_field()
 
 
@@ -336,10 +346,10 @@ class DeviceTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, Weig
     )
     exclude_from_utilization: FilterLookup[bool] | None = strawberry_django.filter_field()
     is_full_depth: FilterLookup[bool] | None = strawberry_django.filter_field()
-    subdevice_role: Annotated['SubdeviceRoleEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    subdevice_role: BaseFilterLookup[Annotated['SubdeviceRoleEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
-    airflow: Annotated['DeviceAirflowEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    airflow: BaseFilterLookup[Annotated['DeviceAirflowEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     front_image: Annotated['ImageAttachmentFilter', strawberry.lazy('extras.graphql.filters')] | None = (
@@ -393,8 +403,12 @@ class DeviceTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, Weig
 
 @strawberry_django.filter_type(models.FrontPort, lookups=True)
 class FrontPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin):
-    type: Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field()
-    color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field()
+    type: BaseFilterLookup[Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
+    color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
     rear_port: Annotated['RearPortFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
@@ -406,8 +420,12 @@ class FrontPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterM
 
 @strawberry_django.filter_type(models.FrontPortTemplate, lookups=True)
 class FrontPortTemplateFilter(ModularComponentTemplateFilterMixin):
-    type: Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field()
-    color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field()
+    type: BaseFilterLookup[Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
+    color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
     rear_port: Annotated['RearPortTemplateFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
@@ -451,14 +469,14 @@ class InterfaceFilter(ModularComponentModelFilterMixin, InterfaceBaseFilterMixin
     )
     lag: Annotated['InterfaceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
     lag_id: ID | None = strawberry_django.filter_field()
-    type: Annotated['InterfaceTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    type: BaseFilterLookup[Annotated['InterfaceTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     mgmt_only: FilterLookup[bool] | None = strawberry_django.filter_field()
     speed: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
-    duplex: Annotated['InterfaceDuplexEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    duplex: BaseFilterLookup[Annotated['InterfaceDuplexEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     wwn: FilterLookup[str] | None = strawberry_django.filter_field()
@@ -466,10 +484,10 @@ class InterfaceFilter(ModularComponentModelFilterMixin, InterfaceBaseFilterMixin
         strawberry_django.filter_field()
     )
     parent_id: ID | None = strawberry_django.filter_field()
-    rf_role: Annotated['WirelessRoleEnum', strawberry.lazy('wireless.graphql.enums')] | None = (
+    rf_role: BaseFilterLookup[Annotated['WirelessRoleEnum', strawberry.lazy('wireless.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
-    rf_channel: Annotated['WirelessChannelEnum', strawberry.lazy('wireless.graphql.enums')] | None = (
+    rf_channel: BaseFilterLookup[Annotated['WirelessChannelEnum', strawberry.lazy('wireless.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     rf_channel_frequency: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
@@ -481,10 +499,10 @@ class InterfaceFilter(ModularComponentModelFilterMixin, InterfaceBaseFilterMixin
     tx_power: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
-    poe_mode: Annotated['InterfacePoEModeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    poe_mode: BaseFilterLookup[Annotated['InterfacePoEModeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
-    poe_type: Annotated['InterfacePoETypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    poe_type: BaseFilterLookup[Annotated['InterfacePoETypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     wireless_link: Annotated['WirelessLinkFilter', strawberry.lazy('wireless.graphql.filters')] | None = (
@@ -536,7 +554,7 @@ class InterfaceFilter(ModularComponentModelFilterMixin, InterfaceBaseFilterMixin
 
 @strawberry_django.filter_type(models.InterfaceTemplate, lookups=True)
 class InterfaceTemplateFilter(ModularComponentTemplateFilterMixin):
-    type: Annotated['InterfaceTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    type: BaseFilterLookup[Annotated['InterfaceTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     enabled: FilterLookup[bool] | None = strawberry_django.filter_field()
@@ -545,13 +563,13 @@ class InterfaceTemplateFilter(ModularComponentTemplateFilterMixin):
         strawberry_django.filter_field()
     )
     bridge_id: ID | None = strawberry_django.filter_field()
-    poe_mode: Annotated['InterfacePoEModeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    poe_mode: BaseFilterLookup[Annotated['InterfacePoEModeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
-    poe_type: Annotated['InterfacePoETypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    poe_type: BaseFilterLookup[Annotated['InterfacePoETypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
-    rf_role: Annotated['WirelessRoleEnum', strawberry.lazy('wireless.graphql.enums')] | None = (
+    rf_role: BaseFilterLookup[Annotated['WirelessRoleEnum', strawberry.lazy('wireless.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
 
@@ -566,7 +584,7 @@ class InventoryItemFilter(ComponentModelFilterMixin):
         strawberry_django.filter_field()
     )
     component_id: ID | None = strawberry_django.filter_field()
-    status: Annotated['InventoryItemStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    status: BaseFilterLookup[Annotated['InventoryItemStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     role: Annotated['InventoryItemRoleFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
@@ -585,14 +603,16 @@ class InventoryItemFilter(ComponentModelFilterMixin):
 
 @strawberry_django.filter_type(models.InventoryItemRole, lookups=True)
 class InventoryItemRoleFilter(OrganizationalModelFilterMixin):
-    color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field()
+    color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
 
 
 @strawberry_django.filter_type(models.Location, lookups=True)
 class LocationFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilterMixin, NestedGroupModelFilterMixin):
     site: Annotated['SiteFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
     site_id: ID | None = strawberry_django.filter_field()
-    status: Annotated['LocationStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    status: BaseFilterLookup[Annotated['LocationStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     facility: FilterLookup[str] | None = strawberry_django.filter_field()
@@ -621,7 +641,7 @@ class ModuleFilter(PrimaryModelFilterMixin, ConfigContextFilterMixin):
         strawberry_django.filter_field()
     )
     module_type_id: ID | None = strawberry_django.filter_field()
-    status: Annotated['ModuleStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    status: BaseFilterLookup[Annotated['ModuleStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     serial: FilterLookup[str] | None = strawberry_django.filter_field()
@@ -692,7 +712,7 @@ class ModuleTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, Weig
     instances: Annotated['ModuleFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
-    airflow: Annotated['ModuleAirflowEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    airflow: BaseFilterLookup[Annotated['ModuleAirflowEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     console_port_templates: (
@@ -749,16 +769,16 @@ class PowerFeedFilter(CabledObjectModelFilterMixin, TenancyFilterMixin, PrimaryM
     rack: Annotated['RackFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
     rack_id: ID | None = strawberry_django.filter_field()
     name: FilterLookup[str] | None = strawberry_django.filter_field()
-    status: Annotated['PowerFeedStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    status: BaseFilterLookup[Annotated['PowerFeedStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
-    type: Annotated['PowerFeedTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    type: BaseFilterLookup[Annotated['PowerFeedTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
-    supply: Annotated['PowerFeedSupplyEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    supply: BaseFilterLookup[Annotated['PowerFeedSupplyEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
-    phase: Annotated['PowerFeedPhaseEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    phase: BaseFilterLookup[Annotated['PowerFeedPhaseEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     voltage: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
@@ -777,29 +797,34 @@ class PowerFeedFilter(CabledObjectModelFilterMixin, TenancyFilterMixin, PrimaryM
 
 @strawberry_django.filter_type(models.PowerOutlet, lookups=True)
 class PowerOutletFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin):
-    type: Annotated['PowerOutletTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    type: BaseFilterLookup[Annotated['PowerOutletTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     power_port: Annotated['PowerPortFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
     power_port_id: ID | None = strawberry_django.filter_field()
-    feed_leg: Annotated['PowerOutletFeedLegEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    feed_leg: BaseFilterLookup[Annotated['PowerOutletFeedLegEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
+    color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
+    status: BaseFilterLookup[Annotated['PowerOutletStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
-    color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field()
 
 
 @strawberry_django.filter_type(models.PowerOutletTemplate, lookups=True)
 class PowerOutletTemplateFilter(ModularComponentModelFilterMixin):
-    type: Annotated['PowerOutletTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    type: BaseFilterLookup[Annotated['PowerOutletTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     power_port: Annotated['PowerPortTemplateFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
     power_port_id: ID | None = strawberry_django.filter_field()
-    feed_leg: Annotated['PowerOutletFeedLegEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    feed_leg: BaseFilterLookup[Annotated['PowerOutletFeedLegEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
 
@@ -819,7 +844,7 @@ class PowerPanelFilter(ContactFilterMixin, ImageAttachmentFilterMixin, PrimaryMo
 
 @strawberry_django.filter_type(models.PowerPort, lookups=True)
 class PowerPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin):
-    type: Annotated['PowerPortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    type: BaseFilterLookup[Annotated['PowerPortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     maximum_draw: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
@@ -832,7 +857,7 @@ class PowerPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterM
 
 @strawberry_django.filter_type(models.PowerPortTemplate, lookups=True)
 class PowerPortTemplateFilter(ModularComponentTemplateFilterMixin):
-    type: Annotated['PowerPortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    type: BaseFilterLookup[Annotated['PowerPortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     maximum_draw: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
@@ -845,7 +870,7 @@ class PowerPortTemplateFilter(ModularComponentTemplateFilterMixin):
 
 @strawberry_django.filter_type(models.RackType, lookups=True)
 class RackTypeFilter(RackBaseFilterMixin):
-    form_factor: Annotated['RackFormFactorEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    form_factor: BaseFilterLookup[Annotated['RackFormFactorEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     manufacturer: Annotated['ManufacturerFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
@@ -860,7 +885,7 @@ class RackTypeFilter(RackBaseFilterMixin):
 
 @strawberry_django.filter_type(models.Rack, lookups=True)
 class RackFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilterMixin, RackBaseFilterMixin):
-    form_factor: Annotated['RackFormFactorEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    form_factor: BaseFilterLookup[Annotated['RackFormFactorEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     rack_type: Annotated['RackTypeFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
@@ -877,12 +902,14 @@ class RackFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilterMi
     location_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
-    status: Annotated['RackStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field()
+    status: BaseFilterLookup[Annotated['RackStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
     role: Annotated['RackRoleFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
     role_id: ID | None = strawberry_django.filter_field()
     serial: FilterLookup[str] | None = strawberry_django.filter_field()
     asset_tag: FilterLookup[str] | None = strawberry_django.filter_field()
-    airflow: Annotated['RackAirflowEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    airflow: BaseFilterLookup[Annotated['RackAirflowEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     vlan_groups: Annotated['VLANGroupFilter', strawberry.lazy('ipam.graphql.filters')] | None = (
@@ -900,17 +927,26 @@ class RackReservationFilter(TenancyFilterMixin, PrimaryModelFilterMixin):
     user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field()
     user_id: ID | None = strawberry_django.filter_field()
     description: FilterLookup[str] | None = strawberry_django.filter_field()
+    status: BaseFilterLookup[Annotated['RackReservationStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
 
 
 @strawberry_django.filter_type(models.RackRole, lookups=True)
 class RackRoleFilter(OrganizationalModelFilterMixin):
-    color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field()
+    color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
 
 
 @strawberry_django.filter_type(models.RearPort, lookups=True)
 class RearPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin):
-    type: Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field()
-    color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field()
+    type: BaseFilterLookup[Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
+    color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
     positions: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
@@ -918,8 +954,12 @@ class RearPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMi
 
 @strawberry_django.filter_type(models.RearPortTemplate, lookups=True)
 class RearPortTemplateFilter(ModularComponentTemplateFilterMixin):
-    type: Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field()
-    color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field()
+    type: BaseFilterLookup[Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
+    color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
     positions: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
@@ -939,7 +979,9 @@ class RegionFilter(ContactFilterMixin, NestedGroupModelFilterMixin):
 class SiteFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilterMixin, PrimaryModelFilterMixin):
     name: FilterLookup[str] | None = strawberry_django.filter_field()
     slug: FilterLookup[str] | None = strawberry_django.filter_field()
-    status: Annotated['SiteStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field()
+    status: BaseFilterLookup[Annotated['SiteStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
     region: Annotated['RegionFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
     region_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
@@ -996,7 +1038,9 @@ class VirtualDeviceContextFilter(TenancyFilterMixin, PrimaryModelFilterMixin):
     device: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
     device_id: ID | None = strawberry_django.filter_field()
     name: FilterLookup[str] | None = strawberry_django.filter_field()
-    status: Annotated['VirtualDeviceContextStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = (
+    status: (
+        BaseFilterLookup[Annotated['VirtualDeviceContextStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None
+    ) = (
         strawberry_django.filter_field()
     )
     identifier: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (

+ 25 - 11
netbox/extras/graphql/filters.py

@@ -3,7 +3,7 @@ from typing import Annotated, TYPE_CHECKING
 import strawberry
 import strawberry_django
 from strawberry.scalars import ID
-from strawberry_django import FilterLookup
+from strawberry_django import BaseFilterLookup, FilterLookup
 
 from core.graphql.filter_mixins import BaseObjectTypeFilterMixin, ChangeLogFilterMixin
 from extras import models
@@ -121,7 +121,7 @@ class ConfigTemplateFilter(BaseObjectTypeFilterMixin, SyncedDataFilterMixin, Cha
 
 @strawberry_django.filter_type(models.CustomField, lookups=True)
 class CustomFieldFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin):
-    type: Annotated['CustomFieldTypeEnum', strawberry.lazy('extras.graphql.enums')] | None = (
+    type: BaseFilterLookup[Annotated['CustomFieldTypeEnum', strawberry.lazy('extras.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     object_types: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = (
@@ -139,7 +139,9 @@ class CustomFieldFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin):
     search_weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
-    filter_logic: Annotated['CustomFieldFilterLogicEnum', strawberry.lazy('extras.graphql.enums')] | None = (
+    filter_logic: (
+        BaseFilterLookup[Annotated['CustomFieldFilterLogicEnum', strawberry.lazy('extras.graphql.enums')]] | None
+    ) = (
         strawberry_django.filter_field()
     )
     default: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
@@ -162,10 +164,14 @@ class CustomFieldFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin):
         strawberry_django.filter_field()
     )
     choice_set_id: ID | None = strawberry_django.filter_field()
-    ui_visible: Annotated['CustomFieldUIVisibleEnum', strawberry.lazy('extras.graphql.enums')] | None = (
+    ui_visible: (
+        BaseFilterLookup[Annotated['CustomFieldUIVisibleEnum', strawberry.lazy('extras.graphql.enums')]] | None
+    ) = (
         strawberry_django.filter_field()
     )
-    ui_editable: Annotated['CustomFieldUIEditableEnum', strawberry.lazy('extras.graphql.enums')] | None = (
+    ui_editable: (
+        BaseFilterLookup[Annotated['CustomFieldUIEditableEnum', strawberry.lazy('extras.graphql.enums')]] | None
+    ) = (
         strawberry_django.filter_field()
     )
     is_cloneable: FilterLookup[bool] | None = strawberry_django.filter_field()
@@ -176,7 +182,9 @@ class CustomFieldFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin):
 class CustomFieldChoiceSetFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin):
     name: FilterLookup[str] | None = strawberry_django.filter_field()
     description: FilterLookup[str] | None = strawberry_django.filter_field()
-    base_choices: Annotated['CustomFieldChoiceSetBaseEnum', strawberry.lazy('extras.graphql.enums')] | None = (
+    base_choices: (
+        BaseFilterLookup[Annotated['CustomFieldChoiceSetBaseEnum', strawberry.lazy('extras.graphql.enums')]] | None
+    ) = (
         strawberry_django.filter_field()
     )
     extra_choices: Annotated['StringArrayLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
@@ -195,7 +203,9 @@ class CustomLinkFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin):
         strawberry_django.filter_field()
     )
     group_name: FilterLookup[str] | None = strawberry_django.filter_field()
-    button_class: Annotated['CustomLinkButtonClassEnum', strawberry.lazy('extras.graphql.enums')] | None = (
+    button_class: (
+        BaseFilterLookup[Annotated['CustomLinkButtonClassEnum', strawberry.lazy('extras.graphql.enums')]] | None
+    ) = (
         strawberry_django.filter_field()
     )
     new_window: FilterLookup[bool] | None = strawberry_django.filter_field()
@@ -240,7 +250,7 @@ class JournalEntryFilter(BaseObjectTypeFilterMixin, CustomFieldsFilterMixin, Tag
     created_by: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
-    kind: Annotated['JournalEntryKindEnum', strawberry.lazy('extras.graphql.enums')] | None = (
+    kind: BaseFilterLookup[Annotated['JournalEntryKindEnum', strawberry.lazy('extras.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     comments: FilterLookup[str] | None = strawberry_django.filter_field()
@@ -286,7 +296,9 @@ class TableConfigFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin):
 
 @strawberry_django.filter_type(models.Tag, lookups=True)
 class TagFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin, TagBaseFilterMixin):
-    color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field()
+    color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
     description: FilterLookup[str] | None = strawberry_django.filter_field()
 
 
@@ -295,7 +307,9 @@ class WebhookFilter(BaseObjectTypeFilterMixin, CustomFieldsFilterMixin, TagsFilt
     name: FilterLookup[str] | None = strawberry_django.filter_field()
     description: FilterLookup[str] | None = strawberry_django.filter_field()
     payload_url: FilterLookup[str] | None = strawberry_django.filter_field()
-    http_method: Annotated['WebhookHttpMethodEnum', strawberry.lazy('extras.graphql.enums')] | None = (
+    http_method: (
+        BaseFilterLookup[Annotated['WebhookHttpMethodEnum', strawberry.lazy('extras.graphql.enums')]] | None
+    ) = (
         strawberry_django.filter_field()
     )
     http_content_type: FilterLookup[str] | None = strawberry_django.filter_field()
@@ -320,7 +334,7 @@ class EventRuleFilter(BaseObjectTypeFilterMixin, CustomFieldsFilterMixin, TagsFi
     conditions: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
-    action_type: Annotated['EventRuleActionEnum', strawberry.lazy('extras.graphql.enums')] | None = (
+    action_type: BaseFilterLookup[Annotated['EventRuleActionEnum', strawberry.lazy('extras.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     action_object_type: FilterLookup[str] | None = strawberry_django.filter_field()

+ 11 - 9
netbox/ipam/graphql/filters.py

@@ -7,7 +7,7 @@ import strawberry_django
 from django.db.models import Q
 from netaddr.core import AddrFormatError
 from strawberry.scalars import ID
-from strawberry_django import FilterLookup, DateFilterLookup
+from strawberry_django import BaseFilterLookup, FilterLookup, DateFilterLookup
 
 from core.graphql.filter_mixins import BaseObjectTypeFilterMixin, ChangeLogFilterMixin
 from dcim.graphql.filter_mixins import ScopedFilterMixin
@@ -116,10 +116,10 @@ class FHRPGroupFilter(PrimaryModelFilterMixin):
         strawberry_django.filter_field()
     )
     name: FilterLookup[str] | None = strawberry_django.filter_field()
-    protocol: Annotated['FHRPGroupProtocolEnum', strawberry.lazy('ipam.graphql.enums')] | None = (
+    protocol: BaseFilterLookup[Annotated['FHRPGroupProtocolEnum', strawberry.lazy('ipam.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
-    auth_type: Annotated['FHRPGroupAuthTypeEnum', strawberry.lazy('ipam.graphql.enums')] | None = (
+    auth_type: BaseFilterLookup[Annotated['FHRPGroupAuthTypeEnum', strawberry.lazy('ipam.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     auth_key: FilterLookup[str] | None = strawberry_django.filter_field()
@@ -172,10 +172,10 @@ class IPAddressFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter
     address: FilterLookup[str] | None = strawberry_django.filter_field()
     vrf: Annotated['VRFFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field()
     vrf_id: ID | None = strawberry_django.filter_field()
-    status: Annotated['IPAddressStatusEnum', strawberry.lazy('ipam.graphql.enums')] | None = (
+    status: BaseFilterLookup[Annotated['IPAddressStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
-    role: Annotated['IPAddressRoleEnum', strawberry.lazy('ipam.graphql.enums')] | None = (
+    role: BaseFilterLookup[Annotated['IPAddressRoleEnum', strawberry.lazy('ipam.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     assigned_object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = (
@@ -227,7 +227,7 @@ class IPRangeFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilterMi
     )
     vrf: Annotated['VRFFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field()
     vrf_id: ID | None = strawberry_django.filter_field()
-    status: Annotated['IPRangeStatusEnum', strawberry.lazy('ipam.graphql.enums')] | None = (
+    status: BaseFilterLookup[Annotated['IPRangeStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     role: Annotated['RoleFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field()
@@ -279,7 +279,7 @@ class PrefixFilter(ContactFilterMixin, ScopedFilterMixin, TenancyFilterMixin, Pr
     vrf_id: ID | None = strawberry_django.filter_field()
     vlan: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field()
     vlan_id: ID | None = strawberry_django.filter_field()
-    status: Annotated['PrefixStatusEnum', strawberry.lazy('ipam.graphql.enums')] | None = (
+    status: BaseFilterLookup[Annotated['PrefixStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     role: Annotated['RoleFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field()
@@ -367,7 +367,9 @@ class VLANFilter(TenancyFilterMixin, PrimaryModelFilterMixin):
         strawberry_django.filter_field()
     )
     name: FilterLookup[str] | None = strawberry_django.filter_field()
-    status: Annotated['VLANStatusEnum', strawberry.lazy('ipam.graphql.enums')] | None = strawberry_django.filter_field()
+    status: BaseFilterLookup[Annotated['VLANStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
     role: Annotated['RoleFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field()
     role_id: ID | None = strawberry_django.filter_field()
     qinq_svlan: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = (
@@ -377,7 +379,7 @@ class VLANFilter(TenancyFilterMixin, PrimaryModelFilterMixin):
     qinq_cvlans: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
-    qinq_role: Annotated['VLANQinQRoleEnum', strawberry.lazy('ipam.graphql.enums')] | None = (
+    qinq_role: BaseFilterLookup[Annotated['VLANQinQRoleEnum', strawberry.lazy('ipam.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     l2vpn_terminations: Annotated['L2VPNFilter', strawberry.lazy('vpn.graphql.filters')] | None = (

+ 3 - 3
netbox/netbox/graphql/filter_mixins.py

@@ -5,7 +5,7 @@ from typing import TypeVar, TYPE_CHECKING, Annotated
 import strawberry
 import strawberry_django
 from strawberry import ID
-from strawberry_django import FilterLookup, DatetimeFilterLookup
+from strawberry_django import BaseFilterLookup, FilterLookup, DatetimeFilterLookup
 
 from core.graphql.filter_mixins import BaseFilterMixin, BaseObjectTypeFilterMixin, ChangeLogFilterMixin
 from extras.graphql.filter_mixins import CustomFieldsFilterMixin, JournalEntriesFilterMixin, TagsFilterMixin
@@ -76,7 +76,7 @@ class ImageAttachmentFilterMixin(BaseFilterMixin):
 @dataclass
 class WeightFilterMixin(BaseFilterMixin):
     weight: FilterLookup[float] | None = strawberry_django.filter_field()
-    weight_unit: Annotated['WeightUnitEnum', strawberry.lazy('netbox.graphql.enums')] | None = (
+    weight_unit: BaseFilterLookup[Annotated['WeightUnitEnum', strawberry.lazy('netbox.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
 
@@ -99,6 +99,6 @@ class SyncedDataFilterMixin(BaseFilterMixin):
 @dataclass
 class DistanceFilterMixin(BaseFilterMixin):
     distance: FilterLookup[float] | None = strawberry_django.filter_field()
-    distance_unit: Annotated['DistanceUnitEnum', strawberry.lazy('netbox.graphql.enums')] | None = (
+    distance_unit: BaseFilterLookup[Annotated['DistanceUnitEnum', strawberry.lazy('netbox.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )

+ 3 - 44
netbox/netbox/graphql/schema.py

@@ -16,17 +16,9 @@ from virtualization.graphql.schema import VirtualizationQuery
 from vpn.graphql.schema import VPNQuery
 from wireless.graphql.schema import WirelessQuery
 
-__all__ = (
-    'Query',
-    'QueryV1',
-    'QueryV2',
-    'schema_v1',
-    'schema_v2',
-)
-
 
 @strawberry.type
-class QueryV1(
+class Query(
     UsersQuery,
     CircuitsQuery,
     CoreQuery,
@@ -39,44 +31,11 @@ class QueryV1(
     WirelessQuery,
     *registry['plugins']['graphql_schemas'],  # Append plugin schemas
 ):
-    """Query class for GraphQL API v1"""
     pass
 
 
-@strawberry.type
-class QueryV2(
-    UsersQuery,
-    CircuitsQuery,
-    CoreQuery,
-    DCIMQuery,
-    ExtrasQuery,
-    IPAMQuery,
-    TenancyQuery,
-    VirtualizationQuery,
-    VPNQuery,
-    WirelessQuery,
-    *registry['plugins']['graphql_schemas'],  # Append plugin schemas
-):
-    """Query class for GraphQL API v2"""
-    pass
-
-
-# Expose a default Query class for the configured default GraphQL version
-class Query(QueryV2 if settings.GRAPHQL_DEFAULT_VERSION == 2 else QueryV1):
-    pass
-
-
-# Generate schemas for both versions of the GraphQL API
-schema_v1 = strawberry.Schema(
-    query=QueryV1,
-    config=StrawberryConfig(auto_camel_case=False),
-    extensions=[
-        DjangoOptimizerExtension(prefetch_custom_queryset=True),
-        MaxAliasesLimiter(max_alias_count=settings.GRAPHQL_MAX_ALIASES),
-    ]
-)
-schema_v2 = strawberry.Schema(
-    query=QueryV2,
+schema = strawberry.Schema(
+    query=Query,
     config=StrawberryConfig(auto_camel_case=False),
     extensions=[
         DjangoOptimizerExtension(prefetch_custom_queryset=True),

+ 0 - 16
netbox/netbox/graphql/utils.py

@@ -1,16 +0,0 @@
-from django.conf import settings
-
-from netbox.graphql.schema import schema_v1, schema_v2
-
-__all__ = (
-    'get_default_schema',
-)
-
-
-def get_default_schema():
-    """
-    Returns the GraphQL schema corresponding to the value of the NETBOX_GRAPHQL_DEFAULT_SCHEMA setting.
-    """
-    if settings.GRAPHQL_DEFAULT_VERSION == 2:
-        return schema_v2
-    return schema_v1

+ 17 - 3
netbox/netbox/tests/test_graphql.py

@@ -96,11 +96,25 @@ class GraphQLAPITestCase(APITestCase):
         self.assertEqual(len(data['data']['location_list']), 1)
         self.assertIsNotNone(data['data']['location_list'][0]['site'])
 
-        # Test OR logic
+        # Test OR and exact logic
         query = """{
             location_list( filters: {
-                status: STATUS_PLANNED,
-                OR: {status: STATUS_STAGING}
+                status: {exact: STATUS_PLANNED},
+                OR: {status: {exact: STATUS_STAGING}}
+            }) {
+                id site {id}
+            }
+        }"""
+        response = self.client.post(url, data={'query': query}, format="json", **self.header)
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+        data = json.loads(response.content)
+        self.assertNotIn('errors', data)
+        self.assertEqual(len(data['data']['location_list']), 2)
+
+        # Test in_list logic
+        query = """{
+            location_list( filters: {
+                status: {in_list: [STATUS_PLANNED, STATUS_STAGING]}
             }) {
                 id site {id}
             }

+ 3 - 6
netbox/netbox/urls.py

@@ -6,8 +6,7 @@ from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, Spec
 
 from account.views import LoginView, LogoutView
 from netbox.api.views import APIRootView, StatusView
-from netbox.graphql.schema import schema_v1, schema_v2
-from netbox.graphql.utils import get_default_schema
+from netbox.graphql.schema import schema
 from netbox.graphql.views import NetBoxGraphQLView
 from netbox.plugins.urls import plugin_patterns, plugin_api_patterns
 from netbox.views import HomeView, MediaView, StaticMediaFailureView, SearchView, htmx
@@ -66,10 +65,8 @@ _patterns = [
     path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='api_docs'),
     path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='api_redocs'),
 
-    # GraphQL API
-    path('graphql/', NetBoxGraphQLView.as_view(schema=get_default_schema()), name='graphql'),
-    path('graphql/v1/', NetBoxGraphQLView.as_view(schema=schema_v1), name='graphql_v1'),
-    path('graphql/v2/', NetBoxGraphQLView.as_view(schema=schema_v2), name='graphql_v2'),
+    # GraphQL
+    path('graphql/', NetBoxGraphQLView.as_view(schema=schema), name='graphql'),
 
     # Serving static media in Django to pipe it through LoginRequiredMiddleware
     path('media/<path:path>', MediaView.as_view(), name='media'),

+ 2 - 2
netbox/tenancy/graphql/filters.py

@@ -3,7 +3,7 @@ from typing import Annotated, TYPE_CHECKING
 import strawberry
 import strawberry_django
 from strawberry.scalars import ID
-from strawberry_django import FilterLookup
+from strawberry_django import BaseFilterLookup, FilterLookup
 
 from core.graphql.filter_mixins import ChangeLogFilterMixin
 from extras.graphql.filter_mixins import CustomFieldsFilterMixin, TagsFilterMixin
@@ -191,6 +191,6 @@ class ContactAssignmentFilter(CustomFieldsFilterMixin, TagsFilterMixin, ChangeLo
         strawberry_django.filter_field()
     )
     role_id: ID | None = strawberry_django.filter_field()
-    priority: Annotated['ContactPriorityEnum', strawberry.lazy('tenancy.graphql.enums')] | None = (
+    priority: BaseFilterLookup[Annotated['ContactPriorityEnum', strawberry.lazy('tenancy.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )

+ 2 - 0
netbox/virtualization/graphql/enums.py

@@ -5,7 +5,9 @@ from virtualization.choices import *
 __all__ = (
     'ClusterStatusEnum',
     'VirtualMachineStatusEnum',
+    'VirtualMachineStatusEnum',
 )
 
 ClusterStatusEnum = strawberry.enum(ClusterStatusChoices.as_enum(prefix='status'))
+VirtualMachineStartOnBootEnum = strawberry.enum(VirtualMachineStartOnBootChoices.as_enum(prefix='start_on_boot'))
 VirtualMachineStatusEnum = strawberry.enum(VirtualMachineStatusChoices.as_enum(prefix='status'))

+ 10 - 3
netbox/virtualization/graphql/filters.py

@@ -3,7 +3,7 @@ from typing import Annotated, TYPE_CHECKING
 import strawberry
 import strawberry_django
 from strawberry.scalars import ID
-from strawberry_django import FilterLookup
+from strawberry_django import BaseFilterLookup, FilterLookup
 
 from dcim.graphql.filter_mixins import InterfaceBaseFilterMixin, RenderConfigFilterMixin, ScopedFilterMixin
 from extras.graphql.filter_mixins import ConfigContextFilterMixin
@@ -50,7 +50,7 @@ class ClusterFilter(ContactFilterMixin, ScopedFilterMixin, TenancyFilterMixin, P
         strawberry_django.filter_field()
     )
     group_id: ID | None = strawberry_django.filter_field()
-    status: Annotated['ClusterStatusEnum', strawberry.lazy('virtualization.graphql.enums')] | None = (
+    status: BaseFilterLookup[Annotated['ClusterStatusEnum', strawberry.lazy('virtualization.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     vlan_groups: Annotated['VLANGroupFilter', strawberry.lazy('ipam.graphql.filters')] | None = (
@@ -92,7 +92,9 @@ class VirtualMachineFilter(
         strawberry_django.filter_field()
     )
     platform_id: ID | None = strawberry_django.filter_field()
-    status: Annotated['VirtualMachineStatusEnum', strawberry.lazy('virtualization.graphql.enums')] | None = (
+    status: (
+        BaseFilterLookup[Annotated['VirtualMachineStatusEnum', strawberry.lazy('virtualization.graphql.enums')]] | None
+    ) = (
         strawberry_django.filter_field()
     )
     role: Annotated['DeviceRoleFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
@@ -128,6 +130,11 @@ class VirtualMachineFilter(
     virtual_disks: Annotated['VirtualDiskFilter', strawberry.lazy('virtualization.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
+    start_on_boot: (
+        BaseFilterLookup[Annotated['VirtualMachineStartOnBootEnum', strawberry.lazy('virtualization.graphql.enums')]
+    ] | None) = (
+        strawberry_django.filter_field()
+    )
 
 
 @strawberry_django.filter_type(models.VMInterface, lookups=True)

+ 2 - 0
netbox/vpn/graphql/enums.py

@@ -10,6 +10,7 @@ __all__ = (
     'IKEModeEnum',
     'IKEVersionEnum',
     'IPSecModeEnum',
+    'L2VPNStatusEnum',
     'L2VPNTypeEnum',
     'TunnelEncapsulationEnum',
     'TunnelStatusEnum',
@@ -24,6 +25,7 @@ EncryptionAlgorithmEnum = strawberry.enum(EncryptionAlgorithmChoices.as_enum(pre
 IKEModeEnum = strawberry.enum(IKEModeChoices.as_enum())
 IKEVersionEnum = strawberry.enum(IKEVersionChoices.as_enum(prefix='version'))
 IPSecModeEnum = strawberry.enum(IPSecModeChoices.as_enum())
+L2VPNStatusEnum = strawberry.enum(L2VPNStatusChoices.as_enum(prefix='status'))
 L2VPNTypeEnum = strawberry.enum(L2VPNTypeChoices.as_enum(prefix='type'))
 TunnelEncapsulationEnum = strawberry.enum(TunnelEncapsulationChoices.as_enum(prefix='encap'))
 TunnelStatusEnum = strawberry.enum(TunnelStatusChoices.as_enum(prefix='status'))

+ 47 - 16
netbox/vpn/graphql/filters.py

@@ -3,7 +3,7 @@ from typing import Annotated, TYPE_CHECKING
 import strawberry
 import strawberry_django
 from strawberry.scalars import ID
-from strawberry_django import FilterLookup
+from strawberry_django import BaseFilterLookup, FilterLookup
 
 from core.graphql.filter_mixins import BaseObjectTypeFilterMixin, ChangeLogFilterMixin
 from extras.graphql.filter_mixins import CustomFieldsFilterMixin, TagsFilterMixin
@@ -42,10 +42,12 @@ class TunnelTerminationFilter(
 ):
     tunnel: Annotated['TunnelFilter', strawberry.lazy('vpn.graphql.filters')] | None = strawberry_django.filter_field()
     tunnel_id: ID | None = strawberry_django.filter_field()
-    role: Annotated['TunnelTerminationRoleEnum', strawberry.lazy('vpn.graphql.enums')] | None = (
+    role: BaseFilterLookup[Annotated['TunnelTerminationRoleEnum', strawberry.lazy('vpn.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
-    termination_type: Annotated['TunnelTerminationTypeEnum', strawberry.lazy('vpn.graphql.enums')] | None = (
+    termination_type: (
+        BaseFilterLookup[Annotated['TunnelTerminationTypeEnum', strawberry.lazy('vpn.graphql.enums')]] | None
+    ) = (
         strawberry_django.filter_field()
     )
     termination_type_id: ID | None = strawberry_django.filter_field()
@@ -59,14 +61,16 @@ class TunnelTerminationFilter(
 @strawberry_django.filter_type(models.Tunnel, lookups=True)
 class TunnelFilter(TenancyFilterMixin, PrimaryModelFilterMixin):
     name: FilterLookup[str] | None = strawberry_django.filter_field()
-    status: Annotated['TunnelStatusEnum', strawberry.lazy('vpn.graphql.enums')] | None = (
+    status: BaseFilterLookup[Annotated['TunnelStatusEnum', strawberry.lazy('vpn.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     group: Annotated['TunnelGroupFilter', strawberry.lazy('vpn.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
     group_id: ID | None = strawberry_django.filter_field()
-    encapsulation: Annotated['TunnelEncapsulationEnum', strawberry.lazy('vpn.graphql.enums')] | None = (
+    encapsulation: (
+        BaseFilterLookup[Annotated['TunnelEncapsulationEnum', strawberry.lazy('vpn.graphql.enums')]] | None
+    ) = (
         strawberry_django.filter_field()
     )
     ipsec_profile: Annotated['IPSecProfileFilter', strawberry.lazy('vpn.graphql.filters')] | None = (
@@ -83,16 +87,24 @@ class TunnelFilter(TenancyFilterMixin, PrimaryModelFilterMixin):
 @strawberry_django.filter_type(models.IKEProposal, lookups=True)
 class IKEProposalFilter(PrimaryModelFilterMixin):
     name: FilterLookup[str] | None = strawberry_django.filter_field()
-    authentication_method: Annotated['AuthenticationMethodEnum', strawberry.lazy('vpn.graphql.enums')] | None = (
+    authentication_method: (
+        BaseFilterLookup[Annotated['AuthenticationMethodEnum', strawberry.lazy('vpn.graphql.enums')]] | None
+    ) = (
         strawberry_django.filter_field()
     )
-    encryption_algorithm: Annotated['EncryptionAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')] | None = (
+    encryption_algorithm: (
+        BaseFilterLookup[Annotated['EncryptionAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')]] | None
+    ) = (
         strawberry_django.filter_field()
     )
-    authentication_algorithm: Annotated['AuthenticationAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')] | None = (
+    authentication_algorithm: (
+        BaseFilterLookup[Annotated['AuthenticationAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')]] | None
+    ) = (
+        strawberry_django.filter_field()
+    )
+    group: BaseFilterLookup[Annotated['DHGroupEnum', strawberry.lazy('vpn.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
-    group: Annotated['DHGroupEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field()
     sa_lifetime: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
@@ -104,8 +116,12 @@ class IKEProposalFilter(PrimaryModelFilterMixin):
 @strawberry_django.filter_type(models.IKEPolicy, lookups=True)
 class IKEPolicyFilter(PrimaryModelFilterMixin):
     name: FilterLookup[str] | None = strawberry_django.filter_field()
-    version: Annotated['IKEVersionEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field()
-    mode: Annotated['IKEModeEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field()
+    version: BaseFilterLookup[Annotated['IKEVersionEnum', strawberry.lazy('vpn.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
+    mode: BaseFilterLookup[Annotated['IKEModeEnum', strawberry.lazy('vpn.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
     proposals: Annotated['IKEProposalFilter', strawberry.lazy('vpn.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
@@ -115,10 +131,16 @@ class IKEPolicyFilter(PrimaryModelFilterMixin):
 @strawberry_django.filter_type(models.IPSecProposal, lookups=True)
 class IPSecProposalFilter(PrimaryModelFilterMixin):
     name: FilterLookup[str] | None = strawberry_django.filter_field()
-    encryption_algorithm: Annotated['EncryptionAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')] | None = (
+    encryption_algorithm: (
+        BaseFilterLookup[Annotated['EncryptionAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')]] | None
+    ) = (
         strawberry_django.filter_field()
     )
-    authentication_algorithm: Annotated['AuthenticationAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')] | None = (
+    authentication_algorithm: (
+        BaseFilterLookup[
+            BaseFilterLookup[Annotated['AuthenticationAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')]]
+        ] | None
+    ) = (
         strawberry_django.filter_field()
     )
     sa_lifetime_seconds: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
@@ -138,13 +160,17 @@ class IPSecPolicyFilter(PrimaryModelFilterMixin):
     proposals: Annotated['IPSecProposalFilter', strawberry.lazy('vpn.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
-    pfs_group: Annotated['DHGroupEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field()
+    pfs_group: BaseFilterLookup[Annotated['DHGroupEnum', strawberry.lazy('vpn.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
 
 
 @strawberry_django.filter_type(models.IPSecProfile, lookups=True)
 class IPSecProfileFilter(PrimaryModelFilterMixin):
     name: FilterLookup[str] | None = strawberry_django.filter_field()
-    mode: Annotated['IPSecModeEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field()
+    mode: BaseFilterLookup[Annotated['IPSecModeEnum', strawberry.lazy('vpn.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
     ike_policy: Annotated['IKEPolicyFilter', strawberry.lazy('vpn.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
@@ -159,7 +185,9 @@ class IPSecProfileFilter(PrimaryModelFilterMixin):
 class L2VPNFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilterMixin):
     name: FilterLookup[str] | None = strawberry_django.filter_field()
     slug: FilterLookup[str] | None = strawberry_django.filter_field()
-    type: Annotated['L2VPNTypeEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field()
+    type: BaseFilterLookup[Annotated['L2VPNTypeEnum', strawberry.lazy('vpn.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
     identifier: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
@@ -172,6 +200,9 @@ class L2VPNFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilterMixi
     terminations: Annotated['L2VPNTerminationFilter', strawberry.lazy('vpn.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
+    status: BaseFilterLookup[Annotated['L2VPNStatusEnum', strawberry.lazy('vpn.graphql.enums')]] | None = (
+        strawberry_django.filter_field()
+    )
 
 
 @strawberry_django.filter_type(models.L2VPNTermination, lookups=True)

+ 3 - 3
netbox/wireless/graphql/filters.py

@@ -3,7 +3,7 @@ from typing import Annotated, TYPE_CHECKING
 import strawberry
 import strawberry_django
 from strawberry.scalars import ID
-from strawberry_django import FilterLookup
+from strawberry_django import BaseFilterLookup, FilterLookup
 
 from dcim.graphql.filter_mixins import ScopedFilterMixin
 from netbox.graphql.filter_mixins import DistanceFilterMixin, PrimaryModelFilterMixin, NestedGroupModelFilterMixin
@@ -36,7 +36,7 @@ class WirelessLANFilter(
     PrimaryModelFilterMixin
 ):
     ssid: FilterLookup[str] | None = strawberry_django.filter_field()
-    status: Annotated['WirelessLANStatusEnum', strawberry.lazy('wireless.graphql.enums')] | None = (
+    status: BaseFilterLookup[Annotated['WirelessLANStatusEnum', strawberry.lazy('wireless.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     group: Annotated['WirelessLANGroupFilter', strawberry.lazy('wireless.graphql.filters')] | None = (
@@ -63,6 +63,6 @@ class WirelessLinkFilter(
     )
     interface_b_id: ID | None = strawberry_django.filter_field()
     ssid: FilterLookup[str] | None = strawberry_django.filter_field()
-    status: Annotated['WirelessLANStatusEnum', strawberry.lazy('wireless.graphql.enums')] | None = (
+    status: BaseFilterLookup[Annotated['WirelessLANStatusEnum', strawberry.lazy('wireless.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )