Преглед изворни кода

refactor(graphql): Update filter lookups for strawberry-django 0.86

Update strawberry-graphql-django to 0.86.1 and remove redundant type
parameters from StrFilterLookup, DateFilterLookup, TimeFilterLookup, and
DatetimeFilterLookup annotations across model-backed GraphQL filters.

Add NetBox-local JSON date, time, and datetime lookup input types to
preserve the previous string-backed JSON filter schema without relying
on deprecated upstream generic lookup annotations. These local types
keep the legacy GraphQL type names and date/time sub-lookup fields
intact.

Fixes #22353
Martin Hauser пре 2 недеља
родитељ
комит
f46090076d

+ 14 - 15
netbox/circuits/graphql/filters.py

@@ -1,4 +1,3 @@
-from datetime import date
 from typing import TYPE_CHECKING, Annotated
 
 import strawberry
@@ -62,9 +61,9 @@ class CircuitTerminationFilter(
     upstream_speed: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
-    xconnect_id: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    pp_info: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    xconnect_id: StrFilterLookup | None = strawberry_django.filter_field()
+    pp_info: StrFilterLookup | None = strawberry_django.filter_field()
+    description: StrFilterLookup | None = strawberry_django.filter_field()
 
     # Cached relations
     _provider_network: Annotated['ProviderNetworkFilter', strawberry.lazy('circuits.graphql.filters')] | None = (
@@ -92,7 +91,7 @@ class CircuitFilter(
     TenancyFilterMixin,
     PrimaryModelFilter
 ):
-    cid: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    cid: StrFilterLookup | None = strawberry_django.filter_field()
     provider: Annotated['ProviderFilter', strawberry.lazy('circuits.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
@@ -108,8 +107,8 @@ class CircuitFilter(
     status: BaseFilterLookup[Annotated['CircuitStatusEnum', strawberry.lazy('circuits.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
-    install_date: DateFilterLookup[date] | None = strawberry_django.filter_field()
-    termination_date: DateFilterLookup[date] | None = strawberry_django.filter_field()
+    install_date: DateFilterLookup | None = strawberry_django.filter_field()
+    termination_date: DateFilterLookup | None = strawberry_django.filter_field()
     commit_rate: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
@@ -145,8 +144,8 @@ class CircuitGroupAssignmentFilter(CustomFieldsFilterMixin, TagsFilterMixin, Cha
 
 @strawberry_django.filter_type(models.Provider, lookups=True)
 class ProviderFilter(ContactFilterMixin, PrimaryModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    slug: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    slug: StrFilterLookup | None = strawberry_django.filter_field()
     asns: Annotated['ASNFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field()
     circuits: Annotated['CircuitFilter', strawberry.lazy('circuits.graphql.filters')] | None = (
         strawberry_django.filter_field()
@@ -159,18 +158,18 @@ class ProviderAccountFilter(ContactFilterMixin, PrimaryModelFilter):
         strawberry_django.filter_field()
     )
     provider_id: ID | None = strawberry_django.filter_field()
-    account: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    account: StrFilterLookup | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
 
 
 @strawberry_django.filter_type(models.ProviderNetwork, lookups=True)
 class ProviderNetworkFilter(PrimaryModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
     provider: Annotated['ProviderFilter', strawberry.lazy('circuits.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
     provider_id: ID | None = strawberry_django.filter_field()
-    service_id: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    service_id: StrFilterLookup | None = strawberry_django.filter_field()
 
 
 @strawberry_django.filter_type(models.VirtualCircuitType, lookups=True)
@@ -180,7 +179,7 @@ class VirtualCircuitTypeFilter(CircuitTypeFilterMixin, OrganizationalModelFilter
 
 @strawberry_django.filter_type(models.VirtualCircuit, lookups=True)
 class VirtualCircuitFilter(TenancyFilterMixin, PrimaryModelFilter):
-    cid: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    cid: StrFilterLookup | None = strawberry_django.filter_field()
     provider_network: Annotated['ProviderNetworkFilter', strawberry.lazy('circuits.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
@@ -218,4 +217,4 @@ class VirtualCircuitTerminationFilter(CustomFieldsFilterMixin, TagsFilterMixin,
         strawberry_django.filter_field()
     )
     interface_id: ID | None = strawberry_django.filter_field()
-    description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    description: StrFilterLookup | None = strawberry_django.filter_field()

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

@@ -1,5 +1,4 @@
 from dataclasses import dataclass
-from datetime import datetime
 from typing import TYPE_CHECKING, Annotated
 
 import strawberry
@@ -20,5 +19,5 @@ class ChangeLoggingMixin:
     changelog: Annotated['ObjectChangeFilter', strawberry.lazy('core.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
-    created: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field()
-    last_updated: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field()
+    created: DatetimeFilterLookup | None = strawberry_django.filter_field()
+    last_updated: DatetimeFilterLookup | None = strawberry_django.filter_field()

+ 15 - 16
netbox/core/graphql/filters.py

@@ -1,4 +1,3 @@
-from datetime import datetime
 from typing import TYPE_CHECKING, Annotated
 
 import strawberry
@@ -26,33 +25,33 @@ __all__ = (
 
 @strawberry_django.filter_type(models.DataFile, lookups=True)
 class DataFileFilter(BaseModelFilter):
-    created: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field()
-    last_updated: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field()
+    created: DatetimeFilterLookup | None = strawberry_django.filter_field()
+    last_updated: DatetimeFilterLookup | None = strawberry_django.filter_field()
     source: Annotated['DataSourceFilter', strawberry.lazy('core.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
     source_id: ID | None = strawberry_django.filter_field()
-    path: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    path: StrFilterLookup | None = strawberry_django.filter_field()
     size: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
-    hash: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    hash: StrFilterLookup | None = strawberry_django.filter_field()
 
 
 @strawberry_django.filter_type(models.DataSource, lookups=True)
 class DataSourceFilter(PrimaryModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    type: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    source_url: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    type: StrFilterLookup | None = strawberry_django.filter_field()
+    source_url: StrFilterLookup | 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: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    ignore_rules: StrFilterLookup | None = strawberry_django.filter_field()
     parameters: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
-    last_synced: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field()
+    last_synced: DatetimeFilterLookup | None = strawberry_django.filter_field()
     datafiles: Annotated['DataFileFilter', strawberry.lazy('core.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
@@ -60,10 +59,10 @@ class DataSourceFilter(PrimaryModelFilter):
 
 @strawberry_django.filter_type(models.ObjectChange, lookups=True)
 class ObjectChangeFilter(BaseModelFilter):
-    time: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field()
+    time: DatetimeFilterLookup | None = strawberry_django.filter_field()
     user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field()
-    user_name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    request_id: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    user_name: StrFilterLookup | None = strawberry_django.filter_field()
+    request_id: StrFilterLookup | None = strawberry_django.filter_field()
     action: (
         BaseFilterLookup[Annotated['ObjectChangeActionEnum', strawberry.lazy('core.graphql.enums')]] | None
     ) = strawberry_django.filter_field()
@@ -76,7 +75,7 @@ class ObjectChangeFilter(BaseModelFilter):
         strawberry_django.filter_field()
     )
     related_object_id: ID | None = strawberry_django.filter_field()
-    object_repr: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    object_repr: StrFilterLookup | None = strawberry_django.filter_field()
     prechange_data: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
@@ -87,5 +86,5 @@ class ObjectChangeFilter(BaseModelFilter):
 
 @strawberry_django.filter_type(DjangoContentType, lookups=True)
 class ContentTypeFilter(BaseModelFilter):
-    app_label: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    model: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    app_label: StrFilterLookup | None = strawberry_django.filter_field()
+    model: StrFilterLookup | None = strawberry_django.filter_field()

+ 44 - 2
netbox/core/tests/test_api.py

@@ -12,7 +12,7 @@ from rq.registry import FailedJobRegistry, StartedJobRegistry
 
 from users.constants import TOKEN_PREFIX
 from users.models import Token
-from utilities.testing import APITestCase, APIViewTestCases, TestCase
+from utilities.testing import APITestCase, APIViewTestCases, GraphQLQueryTest, TestCase
 from utilities.testing.mixins import RQQueueTestMixin
 from utilities.testing.utils import disable_logging
 
@@ -39,12 +39,49 @@ class DataSourceTestCase(APIViewTestCases.APIViewTestCase):
     @classmethod
     def setUpTestData(cls):
         data_sources = (
-            DataSource(name='Data Source 1', type='local', source_url='file:///var/tmp/source1/'),
+            DataSource(
+                name='Data Source 1', type='local', source_url='file:///var/tmp/source1/',
+                parameters={
+                    'sync_date': '2024-01-01',
+                    'sync_datetime': '2024-01-01T12:30:00+00:00',
+                    'sync_time': '12:30:00',
+                },
+            ),
             DataSource(name='Data Source 2', type='local', source_url='file:///var/tmp/source2/'),
             DataSource(name='Data Source 3', type='local', source_url='file:///var/tmp/source3/'),
         )
         DataSource.objects.bulk_create(data_sources)
 
+        cls.graphql_query_tests = (
+            GraphQLQueryTest(
+                name='parameters_json_date_lookup',
+                query=(
+                    '{ data_source_list(filters: {parameters: '
+                    '{path: "sync_date", lookup: {date_lookup: {exact: "2024-01-01"}}}}) '
+                    '{ id } }'
+                ),
+                assert_result=cls.assert_only_source_1,
+            ),
+            GraphQLQueryTest(
+                name='parameters_json_datetime_lookup',
+                query=(
+                    '{ data_source_list(filters: {parameters: '
+                    '{path: "sync_datetime", lookup: {datetime_lookup: {exact: "2024-01-01T12:30:00+00:00"}}}}) '
+                    '{ id } }'
+                ),
+                assert_result=cls.assert_only_source_1,
+            ),
+            GraphQLQueryTest(
+                name='parameters_json_time_lookup',
+                query=(
+                    '{ data_source_list(filters: {parameters: '
+                    '{path: "sync_time", lookup: {time_lookup: {exact: "12:30:00"}}}}) '
+                    '{ id } }'
+                ),
+                assert_result=cls.assert_only_source_1,
+            ),
+        )
+
         cls.create_data = [
             {
                 'name': 'Data Source 4',
@@ -63,6 +100,11 @@ class DataSourceTestCase(APIViewTestCases.APIViewTestCase):
             },
         ]
 
+    def assert_only_source_1(self, data):
+        """The JSON lookup returns exactly the source carrying the matching value."""
+        ids = sorted(result['id'] for result in data['data_source_list'])
+        self.assertEqual(ids, [str(DataSource.objects.get(name='Data Source 1').pk)])
+
 
 class DataFileTestCase(
     APIViewTestCases.GetObjectViewTestCase,

+ 6 - 6
netbox/dcim/graphql/filter_mixins.py

@@ -66,9 +66,9 @@ class ComponentModelFilterMixin:
     )
     device: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
     device_id: ID | None = strawberry_django.filter_field()
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    label: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    label: StrFilterLookup | None = strawberry_django.filter_field()
+    description: StrFilterLookup | None = strawberry_django.filter_field()
 
 
 @dataclass
@@ -96,9 +96,9 @@ class ComponentTemplateFilterMixin:
         strawberry_django.filter_field()
     )
     device_type_id: ID | None = strawberry_django.filter_field()
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    label: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    label: StrFilterLookup | None = strawberry_django.filter_field()
+    description: StrFilterLookup | None = strawberry_django.filter_field()
 
 
 @dataclass

+ 41 - 41
netbox/dcim/graphql/filters.py

@@ -116,7 +116,7 @@ __all__ = (
 
 @strawberry_django.filter_type(models.CableBundle, lookups=True)
 class CableBundleFilter(PrimaryModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
 
 
 @strawberry_django.filter_type(models.Cable, lookups=True)
@@ -127,7 +127,7 @@ class CableFilter(TenancyFilterMixin, PrimaryModelFilter):
     status: BaseFilterLookup[Annotated['LinkStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
-    label: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    label: StrFilterLookup | None = strawberry_django.filter_field()
     color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
@@ -223,9 +223,9 @@ class DeviceFilter(
     platform: Annotated['PlatformFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    serial: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    asset_tag: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    serial: StrFilterLookup | None = strawberry_django.filter_field()
+    asset_tag: StrFilterLookup | None = strawberry_django.filter_field()
     site: Annotated['SiteFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
     site_id: ID | None = strawberry_django.filter_field()
     location: Annotated['LocationFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
@@ -353,7 +353,7 @@ class InventoryItemTemplateFilter(ComponentTemplateFilterMixin, ChangeLoggedMode
         strawberry_django.filter_field()
     )
     manufacturer_id: ID | None = strawberry_django.filter_field()
-    part_id: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    part_id: StrFilterLookup | None = strawberry_django.filter_field()
 
 
 @strawberry_django.filter_type(models.DeviceRole, lookups=True)
@@ -370,13 +370,13 @@ class DeviceTypeFilter(ImageAttachmentFilterMixin, WeightFilterMixin, PrimaryMod
         strawberry_django.filter_field()
     )
     manufacturer_id: ID | None = strawberry_django.filter_field()
-    model: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    slug: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    model: StrFilterLookup | None = strawberry_django.filter_field()
+    slug: StrFilterLookup | None = strawberry_django.filter_field()
     default_platform: Annotated['PlatformFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
     default_platform_id: ID | None = strawberry_django.filter_field()
-    part_number: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    part_number: StrFilterLookup | None = strawberry_django.filter_field()
     instances: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
@@ -493,7 +493,7 @@ class PortTemplateMappingFilter(BaseModelFilter):
 
 @strawberry_django.filter_type(models.MACAddress, lookups=True)
 class MACAddressFilter(PrimaryModelFilter):
-    mac_address: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    mac_address: StrFilterLookup | None = strawberry_django.filter_field()
     assigned_object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
@@ -539,7 +539,7 @@ class InterfaceFilter(
     duplex: BaseFilterLookup[Annotated['InterfaceDuplexEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
-    wwn: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    wwn: StrFilterLookup | None = strawberry_django.filter_field()
     parent: Annotated['InterfaceFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
@@ -659,9 +659,9 @@ class InventoryItemFilter(ComponentModelFilterMixin, NetBoxModelFilter):
         strawberry_django.filter_field()
     )
     manufacturer_id: ID | None = strawberry_django.filter_field()
-    part_id: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    serial: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    asset_tag: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    part_id: StrFilterLookup | None = strawberry_django.filter_field()
+    serial: StrFilterLookup | None = strawberry_django.filter_field()
+    asset_tag: StrFilterLookup | None = strawberry_django.filter_field()
     discovered: FilterLookup[bool] | None = strawberry_django.filter_field()
 
 
@@ -679,7 +679,7 @@ class LocationFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilt
     status: BaseFilterLookup[Annotated['LocationStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
-    facility: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    facility: StrFilterLookup | None = strawberry_django.filter_field()
     prefixes: Annotated['PrefixFilter', strawberry.lazy('ipam.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
@@ -708,8 +708,8 @@ class ModuleFilter(ConfigContextFilterMixin, PrimaryModelFilter):
     status: BaseFilterLookup[Annotated['ModuleStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
-    serial: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    asset_tag: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    serial: StrFilterLookup | None = strawberry_django.filter_field()
+    asset_tag: StrFilterLookup | None = strawberry_django.filter_field()
     consoleports: Annotated['ConsolePortFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
         strawberry_django.filter_field(name='console_ports')
     )
@@ -748,19 +748,19 @@ class ModuleBayFilter(ModularComponentFilterMixin, NetBoxModelFilter):
         strawberry_django.filter_field()
     )
     parent_id: ID | None = strawberry_django.filter_field()
-    position: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    position: StrFilterLookup | None = strawberry_django.filter_field()
     enabled: FilterLookup[bool] | None = strawberry_django.filter_field()
 
 
 @strawberry_django.filter_type(models.ModuleBayTemplate, lookups=True)
 class ModuleBayTemplateFilter(ModularComponentTemplateFilterMixin, ChangeLoggedModelFilter):
-    position: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    position: StrFilterLookup | None = strawberry_django.filter_field()
     enabled: FilterLookup[bool] | None = strawberry_django.filter_field()
 
 
 @strawberry_django.filter_type(models.ModuleTypeProfile, lookups=True)
 class ModuleTypeProfileFilter(PrimaryModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
 
 
 @strawberry_django.filter_type(models.ModuleType, lookups=True)
@@ -773,8 +773,8 @@ class ModuleTypeFilter(ImageAttachmentFilterMixin, WeightFilterMixin, PrimaryMod
         strawberry_django.filter_field()
     )
     profile_id: ID | None = strawberry_django.filter_field()
-    model: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    part_number: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    model: StrFilterLookup | None = strawberry_django.filter_field()
+    part_number: StrFilterLookup | None = strawberry_django.filter_field()
     instances: Annotated['ModuleFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
@@ -831,7 +831,7 @@ class PowerFeedFilter(CabledObjectModelFilterMixin, TenancyFilterMixin, PrimaryM
     power_panel_id: ID | None = strawberry_django.filter_field()
     rack: Annotated['RackFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
     rack_id: ID | None = strawberry_django.filter_field()
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
     status: BaseFilterLookup[Annotated['PowerFeedStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
@@ -902,7 +902,7 @@ class PowerPanelFilter(ContactFilterMixin, ImageAttachmentFilterMixin, PrimaryMo
     location_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
 
 
 @strawberry_django.filter_type(models.PowerPort, lookups=True)
@@ -940,8 +940,8 @@ class RackTypeFilter(ImageAttachmentFilterMixin, RackFilterMixin, WeightFilterMi
         strawberry_django.filter_field()
     )
     manufacturer_id: ID | None = strawberry_django.filter_field()
-    model: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    slug: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    model: StrFilterLookup | None = strawberry_django.filter_field()
+    slug: StrFilterLookup | None = strawberry_django.filter_field()
     racks: Annotated['RackFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
     rack_count: ComparisonFilterLookup[int] | None = strawberry_django.filter_field()
 
@@ -962,8 +962,8 @@ class RackFilter(
         strawberry_django.filter_field()
     )
     rack_type_id: ID | None = strawberry_django.filter_field()
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    facility_id: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    facility_id: StrFilterLookup | None = strawberry_django.filter_field()
     site: Annotated['SiteFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
     site_id: ID | None = strawberry_django.filter_field()
     location: Annotated['LocationFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
@@ -981,8 +981,8 @@ class RackFilter(
     )
     role: Annotated['RackRoleFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
     role_id: ID | None = strawberry_django.filter_field()
-    serial: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    asset_tag: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    serial: StrFilterLookup | None = strawberry_django.filter_field()
+    asset_tag: StrFilterLookup | None = strawberry_django.filter_field()
     airflow: BaseFilterLookup[Annotated['RackAirflowEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
@@ -1006,7 +1006,7 @@ class RackReservationFilter(TenancyFilterMixin, PrimaryModelFilter):
     unit_count: ComparisonFilterLookup[int] | None = strawberry_django.filter_field()
     user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field()
     user_id: ID | None = strawberry_django.filter_field()
-    description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    description: StrFilterLookup | None = strawberry_django.filter_field()
     status: BaseFilterLookup[Annotated['RackReservationStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
@@ -1057,8 +1057,8 @@ class RegionFilter(ContactFilterMixin, NestedGroupModelFilter):
 
 @strawberry_django.filter_type(models.Site, lookups=True)
 class SiteFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilterMixin, PrimaryModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    slug: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    slug: StrFilterLookup | None = strawberry_django.filter_field()
     status: BaseFilterLookup[Annotated['SiteStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
@@ -1072,11 +1072,11 @@ class SiteFilter(ContactFilterMixin, ImageAttachmentFilterMixin, TenancyFilterMi
     group_id: Annotated['TreeNodeFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
-    facility: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    facility: StrFilterLookup | None = strawberry_django.filter_field()
     asns: Annotated['ASNFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field()
-    time_zone: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    physical_address: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    shipping_address: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    time_zone: StrFilterLookup | None = strawberry_django.filter_field()
+    physical_address: StrFilterLookup | None = strawberry_django.filter_field()
+    shipping_address: StrFilterLookup | None = strawberry_django.filter_field()
     latitude: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
@@ -1105,8 +1105,8 @@ class SiteGroupFilter(ContactFilterMixin, NestedGroupModelFilter):
 class VirtualChassisFilter(PrimaryModelFilter):
     master: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
     master_id: ID | None = strawberry_django.filter_field()
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    domain: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    domain: StrFilterLookup | None = strawberry_django.filter_field()
     members: (
         Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None
     ) = strawberry_django.filter_field()
@@ -1117,7 +1117,7 @@ class VirtualChassisFilter(PrimaryModelFilter):
 class VirtualDeviceContextFilter(TenancyFilterMixin, PrimaryModelFilter):
     device: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
     device_id: ID | None = strawberry_django.filter_field()
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
     status: (
         BaseFilterLookup[Annotated['VirtualDeviceContextStatusEnum', strawberry.lazy('dcim.graphql.enums')]] | None
     ) = (
@@ -1134,7 +1134,7 @@ class VirtualDeviceContextFilter(TenancyFilterMixin, PrimaryModelFilter):
         strawberry_django.filter_field()
     )
     primary_ip6_id: ID | None = strawberry_django.filter_field()
-    comments: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    comments: StrFilterLookup | None = strawberry_django.filter_field()
     interfaces: (
         Annotated['InterfaceFilter', strawberry.lazy('dcim.graphql.filters')] | None
     ) = strawberry_django.filter_field()

+ 56 - 57
netbox/extras/graphql/filters.py

@@ -1,4 +1,3 @@
-from datetime import datetime
 from typing import TYPE_CHECKING, Annotated
 
 import strawberry
@@ -55,11 +54,11 @@ __all__ = (
 
 @strawberry_django.filter_type(models.ConfigContext, lookups=True)
 class ConfigContextFilter(SyncedDataFilterMixin, ChangeLoggedModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
     weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
-    description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    description: StrFilterLookup | None = strawberry_django.filter_field()
     is_active: FilterLookup[bool] | None = strawberry_django.filter_field()
     regions: Annotated['RegionFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
         strawberry_django.filter_field()
@@ -112,22 +111,22 @@ class ConfigContextFilter(SyncedDataFilterMixin, ChangeLoggedModelFilter):
 
 @strawberry_django.filter_type(models.ConfigContextProfile, lookups=True)
 class ConfigContextProfileFilter(SyncedDataFilterMixin, PrimaryModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    description: StrFilterLookup | None = strawberry_django.filter_field()
     tags: Annotated['TagFilter', strawberry.lazy('extras.graphql.filters')] | None = strawberry_django.filter_field()
 
 
 @strawberry_django.filter_type(models.ConfigTemplate, lookups=True)
 class ConfigTemplateFilter(SyncedDataFilterMixin, ChangeLoggedModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    description: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    template_code: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    description: StrFilterLookup | None = strawberry_django.filter_field()
+    template_code: StrFilterLookup | None = strawberry_django.filter_field()
     environment_params: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
-    mime_type: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    file_name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    file_extension: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    mime_type: StrFilterLookup | None = strawberry_django.filter_field()
+    file_name: StrFilterLookup | None = strawberry_django.filter_field()
+    file_extension: StrFilterLookup | None = strawberry_django.filter_field()
     as_attachment: FilterLookup[bool] | None = strawberry_django.filter_field()
 
 
@@ -142,10 +141,10 @@ class CustomFieldFilter(ChangeLoggedModelFilter):
     related_object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    label: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    group_name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    label: StrFilterLookup | None = strawberry_django.filter_field()
+    group_name: StrFilterLookup | None = strawberry_django.filter_field()
+    description: StrFilterLookup | None = strawberry_django.filter_field()
     required: FilterLookup[bool] | None = strawberry_django.filter_field()
     unique: FilterLookup[bool] | None = strawberry_django.filter_field()
     search_weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
@@ -171,7 +170,7 @@ class CustomFieldFilter(ChangeLoggedModelFilter):
     validation_maximum: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
-    validation_regex: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    validation_regex: StrFilterLookup | None = strawberry_django.filter_field()
     choice_set: Annotated['CustomFieldChoiceSetFilter', strawberry.lazy('extras.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
@@ -187,13 +186,13 @@ class CustomFieldFilter(ChangeLoggedModelFilter):
         strawberry_django.filter_field()
     )
     is_cloneable: FilterLookup[bool] | None = strawberry_django.filter_field()
-    comments: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    comments: StrFilterLookup | None = strawberry_django.filter_field()
 
 
 @strawberry_django.filter_type(models.CustomFieldChoiceSet, lookups=True)
 class CustomFieldChoiceSetFilter(ChangeLoggedModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    description: StrFilterLookup | None = strawberry_django.filter_field()
     base_choices: (
         BaseFilterLookup[Annotated['CustomFieldChoiceSetBaseEnum', strawberry.lazy('extras.graphql.enums')]] | None
     ) = (
@@ -234,14 +233,14 @@ class CustomFieldChoiceSetFilter(ChangeLoggedModelFilter):
 
 @strawberry_django.filter_type(models.CustomLink, lookups=True)
 class CustomLinkFilter(ChangeLoggedModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
     enabled: FilterLookup[bool] | None = strawberry_django.filter_field()
-    link_text: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    link_url: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    link_text: StrFilterLookup | None = strawberry_django.filter_field()
+    link_url: StrFilterLookup | None = strawberry_django.filter_field()
     weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
-    group_name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    group_name: StrFilterLookup | None = strawberry_django.filter_field()
     button_class: (
         BaseFilterLookup[Annotated['CustomLinkButtonClassEnum', strawberry.lazy('extras.graphql.enums')]] | None
     ) = (
@@ -252,15 +251,15 @@ class CustomLinkFilter(ChangeLoggedModelFilter):
 
 @strawberry_django.filter_type(models.ExportTemplate, lookups=True)
 class ExportTemplateFilter(SyncedDataFilterMixin, ChangeLoggedModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    description: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    template_code: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    description: StrFilterLookup | None = strawberry_django.filter_field()
+    template_code: StrFilterLookup | None = strawberry_django.filter_field()
     environment_params: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
-    mime_type: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    file_name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    file_extension: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    mime_type: StrFilterLookup | None = strawberry_django.filter_field()
+    file_name: StrFilterLookup | None = strawberry_django.filter_field()
+    file_extension: StrFilterLookup | None = strawberry_django.filter_field()
     as_attachment: FilterLookup[bool] | None = strawberry_django.filter_field()
 
 
@@ -276,7 +275,7 @@ class ImageAttachmentFilter(ChangeLoggedModelFilter):
     image_width: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
 
 
 @strawberry_django.filter_type(models.JournalEntry, lookups=True)
@@ -292,13 +291,13 @@ class JournalEntryFilter(CustomFieldsFilterMixin, TagsFilterMixin, ChangeLoggedM
     kind: BaseFilterLookup[Annotated['JournalEntryKindEnum', strawberry.lazy('extras.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
-    comments: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    comments: StrFilterLookup | None = strawberry_django.filter_field()
 
 
 @strawberry_django.filter_type(models.Notification, lookups=True)
 class NotificationFilter(BaseModelFilter):
-    created: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field()
-    read: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field()
+    created: DatetimeFilterLookup | None = strawberry_django.filter_field()
+    read: DatetimeFilterLookup | None = strawberry_django.filter_field()
     user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field()
     user_id: ID | None = strawberry_django.filter_field()
     object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = (
@@ -306,23 +305,23 @@ class NotificationFilter(BaseModelFilter):
     )
     object_type_id: ID | None = strawberry_django.filter_field()
     object_id: ID | None = strawberry_django.filter_field()
-    object_repr: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    event_type: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    object_repr: StrFilterLookup | None = strawberry_django.filter_field()
+    event_type: StrFilterLookup | None = strawberry_django.filter_field()
 
 
 @strawberry_django.filter_type(models.NotificationGroup, lookups=True)
 class NotificationGroupFilter(ChangeLoggedModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    description: StrFilterLookup | None = strawberry_django.filter_field()
     groups: Annotated['GroupFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field()
     users: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field()
 
 
 @strawberry_django.filter_type(models.SavedFilter, lookups=True)
 class SavedFilterFilter(ChangeLoggedModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    slug: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    slug: StrFilterLookup | None = strawberry_django.filter_field()
+    description: StrFilterLookup | None = strawberry_django.filter_field()
     user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field()
     user_id: ID | None = strawberry_django.filter_field()
     weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
@@ -337,7 +336,7 @@ class SavedFilterFilter(ChangeLoggedModelFilter):
 
 @strawberry_django.filter_type(models.Subscription, lookups=True)
 class SubscriptionFilter(BaseModelFilter):
-    created: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field()
+    created: DatetimeFilterLookup | None = strawberry_django.filter_field()
     user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field()
     user_id: ID | None = strawberry_django.filter_field()
     object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = (
@@ -349,8 +348,8 @@ class SubscriptionFilter(BaseModelFilter):
 
 @strawberry_django.filter_type(models.TableConfig, lookups=True)
 class TableConfigFilter(ChangeLoggedModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    description: StrFilterLookup | None = strawberry_django.filter_field()
     user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field()
     user_id: ID | None = strawberry_django.filter_field()
     weight: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
@@ -362,30 +361,30 @@ class TableConfigFilter(ChangeLoggedModelFilter):
 
 @strawberry_django.filter_type(models.Tag, lookups=True)
 class TagFilter(ChangeLoggedModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    slug: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    slug: StrFilterLookup | None = strawberry_django.filter_field()
     color: BaseFilterLookup[Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
-    description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    description: StrFilterLookup | None = strawberry_django.filter_field()
 
 
 @strawberry_django.filter_type(models.Webhook, lookups=True)
 class WebhookFilter(CustomFieldsFilterMixin, TagsFilterMixin, ChangeLoggedModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    description: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    payload_url: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    description: StrFilterLookup | None = strawberry_django.filter_field()
+    payload_url: StrFilterLookup | None = strawberry_django.filter_field()
     http_method: (
         BaseFilterLookup[Annotated['WebhookHttpMethodEnum', strawberry.lazy('extras.graphql.enums')]] | None
     ) = (
         strawberry_django.filter_field()
     )
-    http_content_type: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    additional_headers: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    body_template: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    secret: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    http_content_type: StrFilterLookup | None = strawberry_django.filter_field()
+    additional_headers: StrFilterLookup | None = strawberry_django.filter_field()
+    body_template: StrFilterLookup | None = strawberry_django.filter_field()
+    secret: StrFilterLookup | None = strawberry_django.filter_field()
     ssl_verification: FilterLookup[bool] | None = strawberry_django.filter_field()
-    ca_file_path: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    ca_file_path: StrFilterLookup | None = strawberry_django.filter_field()
     events: Annotated['EventRuleFilter', strawberry.lazy('extras.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
@@ -393,8 +392,8 @@ class WebhookFilter(CustomFieldsFilterMixin, TagsFilterMixin, ChangeLoggedModelF
 
 @strawberry_django.filter_type(models.EventRule, lookups=True)
 class EventRuleFilter(CustomFieldsFilterMixin, TagsFilterMixin, ChangeLoggedModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    description: StrFilterLookup | None = strawberry_django.filter_field()
     event_types: Annotated['StringArrayLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
@@ -413,4 +412,4 @@ class EventRuleFilter(CustomFieldsFilterMixin, TagsFilterMixin, ChangeLoggedMode
     action_data: Annotated['JSONFilter', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
-    comments: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    comments: StrFilterLookup | None = strawberry_django.filter_field()

+ 20 - 21
netbox/ipam/graphql/filters.py

@@ -1,4 +1,3 @@
-from datetime import date
 from typing import TYPE_CHECKING, Annotated
 
 import netaddr
@@ -72,8 +71,8 @@ class ASNFilter(TenancyFilterMixin, PrimaryModelFilter):
 
 @strawberry_django.filter_type(models.ASNRange, lookups=True)
 class ASNRangeFilter(TenancyFilterMixin, OrganizationalModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    slug: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    slug: StrFilterLookup | None = strawberry_django.filter_field()
     rir: Annotated['RIRFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field()
     rir_id: ID | None = strawberry_django.filter_field()
     start: Annotated['BigIntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
@@ -86,10 +85,10 @@ class ASNRangeFilter(TenancyFilterMixin, OrganizationalModelFilter):
 
 @strawberry_django.filter_type(models.Aggregate, lookups=True)
 class AggregateFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter):
-    prefix: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    prefix: StrFilterLookup | None = strawberry_django.filter_field()
     rir: Annotated['RIRFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field()
     rir_id: ID | None = strawberry_django.filter_field()
-    date_added: DateFilterLookup[date] | None = strawberry_django.filter_field()
+    date_added: DateFilterLookup | None = strawberry_django.filter_field()
 
     @strawberry_django.filter_field()
     def contains(self, value: list[str], prefix) -> Q:
@@ -122,14 +121,14 @@ class FHRPGroupFilter(PrimaryModelFilter):
     group_id: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
     protocol: BaseFilterLookup[Annotated['FHRPGroupProtocolEnum', strawberry.lazy('ipam.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
     auth_type: BaseFilterLookup[Annotated['FHRPGroupAuthTypeEnum', strawberry.lazy('ipam.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
-    auth_key: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    auth_key: StrFilterLookup | None = strawberry_django.filter_field()
     ip_addresses: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
@@ -140,7 +139,7 @@ class FHRPGroupAssignmentFilter(ChangeLoggedModelFilter):
     interface_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
-    interface_id: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    interface_id: StrFilterLookup | None = strawberry_django.filter_field()
     group: Annotated['FHRPGroupFilter', strawberry.lazy('ipam.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
@@ -176,7 +175,7 @@ class FHRPGroupAssignmentFilter(ChangeLoggedModelFilter):
 
 @strawberry_django.filter_type(models.IPAddress, lookups=True)
 class IPAddressFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter):
-    address: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    address: StrFilterLookup | 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: BaseFilterLookup[Annotated['IPAddressStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = (
@@ -197,7 +196,7 @@ class IPAddressFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter
         strawberry_django.filter_field()
     )
     nat_outside_id: ID | None = strawberry_django.filter_field()
-    dns_name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    dns_name: StrFilterLookup | None = strawberry_django.filter_field()
 
     @strawberry_django.filter_field()
     def assigned(self, value: bool, prefix) -> Q:
@@ -227,8 +226,8 @@ class IPAddressFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter
 
 @strawberry_django.filter_type(models.IPRange, lookups=True)
 class IPRangeFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter):
-    start_address: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    end_address: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    start_address: StrFilterLookup | None = strawberry_django.filter_field()
+    end_address: StrFilterLookup | None = strawberry_django.filter_field()
     size: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
@@ -281,7 +280,7 @@ class IPRangeFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter):
 
 @strawberry_django.filter_type(models.Prefix, lookups=True)
 class PrefixFilter(ContactFilterMixin, ScopedFilterMixin, TenancyFilterMixin, PrimaryModelFilter):
-    prefix: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    prefix: StrFilterLookup | 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()
     vlan: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field()
@@ -330,7 +329,7 @@ class RoleFilter(OrganizationalModelFilter):
 
 @strawberry_django.filter_type(models.RouteTarget, lookups=True)
 class RouteTargetFilter(TenancyFilterMixin, PrimaryModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
     importing_vrfs: Annotated['VRFFilter', strawberry.lazy('ipam.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
@@ -347,7 +346,7 @@ class RouteTargetFilter(TenancyFilterMixin, PrimaryModelFilter):
 
 @strawberry_django.filter_type(models.Service, lookups=True)
 class ServiceFilter(ContactFilterMixin, ServiceFilterMixin, PrimaryModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
     ip_addresses: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
@@ -359,7 +358,7 @@ class ServiceFilter(ContactFilterMixin, ServiceFilterMixin, PrimaryModelFilter):
 
 @strawberry_django.filter_type(models.ServiceTemplate, lookups=True)
 class ServiceTemplateFilter(ServiceFilterMixin, PrimaryModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
 
 
 @strawberry_django.filter_type(models.VLAN, lookups=True)
@@ -373,7 +372,7 @@ class VLANFilter(TenancyFilterMixin, PrimaryModelFilter):
     vid: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
     status: BaseFilterLookup[Annotated['VLANStatusEnum', strawberry.lazy('ipam.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
@@ -404,7 +403,7 @@ class VLANGroupFilter(ScopedFilterMixin, OrganizationalModelFilter):
 
 @strawberry_django.filter_type(models.VLANTranslationPolicy, lookups=True)
 class VLANTranslationPolicyFilter(PrimaryModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
 
 
 @strawberry_django.filter_type(models.VLANTranslationRule, lookups=True)
@@ -413,7 +412,7 @@ class VLANTranslationRuleFilter(NetBoxModelFilter):
         strawberry_django.filter_field()
     )
     policy_id: ID | None = strawberry_django.filter_field()
-    description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    description: StrFilterLookup | None = strawberry_django.filter_field()
     local_vid: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
@@ -424,8 +423,8 @@ class VLANTranslationRuleFilter(NetBoxModelFilter):
 
 @strawberry_django.filter_type(models.VRF, lookups=True)
 class VRFFilter(TenancyFilterMixin, PrimaryModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    rd: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    rd: StrFilterLookup | None = strawberry_django.filter_field()
     enforce_unique: FilterLookup[bool] | None = strawberry_django.filter_field()
     import_targets: Annotated['RouteTargetFilter', strawberry.lazy('ipam.graphql.filters')] | None = (
         strawberry_django.filter_field()

+ 46 - 7
netbox/netbox/graphql/filter_lookups.py

@@ -11,12 +11,9 @@ from strawberry.directive import DirectiveValue
 from strawberry.types import Info
 from strawberry_django import (
     ComparisonFilterLookup,
-    DateFilterLookup,
-    DatetimeFilterLookup,
     FilterLookup,
     RangeLookup,
     StrFilterLookup,
-    TimeFilterLookup,
     process_filters,
 )
 
@@ -39,16 +36,58 @@ T = TypeVar('T')
 SKIP_MSG = 'Filter will be skipped on `null` value'
 
 
+# These JSON lookup types intentionally mirror the legacy DateFilterLookup[str],
+# TimeFilterLookup[str], and DatetimeFilterLookup[str] schema. JSON values are
+# string-backed, so the concrete strawberry-django date/time lookup classes
+# (which now ignore type parameters and warn) are deliberately not used here.
+@strawberry.input(name='StrDateFilterLookup')
+class JSONDateFilterLookup(ComparisonFilterLookup[str]):
+    year: ComparisonFilterLookup[int] | None = strawberry.UNSET
+    month: ComparisonFilterLookup[int] | None = strawberry.UNSET
+    day: ComparisonFilterLookup[int] | None = strawberry.UNSET
+    week_day: ComparisonFilterLookup[int] | None = strawberry.UNSET
+    iso_week_day: ComparisonFilterLookup[int] | None = strawberry.UNSET
+    week: ComparisonFilterLookup[int] | None = strawberry.UNSET
+    iso_year: ComparisonFilterLookup[int] | None = strawberry.UNSET
+    quarter: ComparisonFilterLookup[int] | None = strawberry.UNSET
+
+
+@strawberry.input(name='StrTimeFilterLookup')
+class JSONTimeFilterLookup(ComparisonFilterLookup[str]):
+    hour: ComparisonFilterLookup[int] | None = strawberry.UNSET
+    minute: ComparisonFilterLookup[int] | None = strawberry.UNSET
+    second: ComparisonFilterLookup[int] | None = strawberry.UNSET
+    date: ComparisonFilterLookup[int] | None = strawberry.UNSET
+    time: ComparisonFilterLookup[int] | None = strawberry.UNSET
+
+
+@strawberry.input(name='StrDatetimeFilterLookup')
+class JSONDatetimeFilterLookup(ComparisonFilterLookup[str]):
+    year: ComparisonFilterLookup[int] | None = strawberry.UNSET
+    month: ComparisonFilterLookup[int] | None = strawberry.UNSET
+    day: ComparisonFilterLookup[int] | None = strawberry.UNSET
+    week_day: ComparisonFilterLookup[int] | None = strawberry.UNSET
+    iso_week_day: ComparisonFilterLookup[int] | None = strawberry.UNSET
+    week: ComparisonFilterLookup[int] | None = strawberry.UNSET
+    iso_year: ComparisonFilterLookup[int] | None = strawberry.UNSET
+    quarter: ComparisonFilterLookup[int] | None = strawberry.UNSET
+    hour: ComparisonFilterLookup[int] | None = strawberry.UNSET
+    minute: ComparisonFilterLookup[int] | None = strawberry.UNSET
+    second: ComparisonFilterLookup[int] | None = strawberry.UNSET
+    date: ComparisonFilterLookup[int] | None = strawberry.UNSET
+    time: ComparisonFilterLookup[int] | None = strawberry.UNSET
+
+
 @strawberry.input(one_of=True, description='Lookup for JSON field. Only one of the lookup fields can be set.')
 class JSONLookup:
-    string_lookup: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    string_lookup: StrFilterLookup | None = strawberry_django.filter_field()
     int_range_lookup: RangeLookup[int] | None = strawberry_django.filter_field()
     int_comparison_lookup: ComparisonFilterLookup[int] | None = strawberry_django.filter_field()
     float_range_lookup: RangeLookup[float] | None = strawberry_django.filter_field()
     float_comparison_lookup: ComparisonFilterLookup[float] | None = strawberry_django.filter_field()
-    date_lookup: DateFilterLookup[str] | None = strawberry_django.filter_field()
-    datetime_lookup: DatetimeFilterLookup[str] | None = strawberry_django.filter_field()
-    time_lookup: TimeFilterLookup[str] | None = strawberry_django.filter_field()
+    date_lookup: JSONDateFilterLookup | None = strawberry_django.filter_field()
+    datetime_lookup: JSONDatetimeFilterLookup | None = strawberry_django.filter_field()
+    time_lookup: JSONTimeFilterLookup | None = strawberry_django.filter_field()
     boolean_lookup: FilterLookup[bool] | None = strawberry_django.filter_field()
 
     def get_filter(self):

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

@@ -1,5 +1,4 @@
 from dataclasses import dataclass
-from datetime import datetime
 from typing import TYPE_CHECKING, Annotated, TypeVar
 
 import strawberry
@@ -48,9 +47,9 @@ class SyncedDataFilterMixin:
         strawberry_django.filter_field()
     )
     data_file_id: FilterLookup[int] | None = strawberry_django.filter_field()
-    data_path: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    data_path: StrFilterLookup | None = strawberry_django.filter_field()
     auto_sync_enabled: FilterLookup[bool] | None = strawberry_django.filter_field()
-    data_synced: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field()
+    data_synced: DatetimeFilterLookup | None = strawberry_django.filter_field()
 
 
 @dataclass

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

@@ -42,21 +42,21 @@ class NetBoxModelFilter(
 
 @dataclass
 class NestedGroupModelFilter(NetBoxModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    slug: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    slug: StrFilterLookup | None = strawberry_django.filter_field()
+    description: StrFilterLookup | None = strawberry_django.filter_field()
     parent_id: ID | None = strawberry_django.filter_field()
 
 
 @dataclass
 class OrganizationalModelFilter(NetBoxModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    slug: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    description: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    comments: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    slug: StrFilterLookup | None = strawberry_django.filter_field()
+    description: StrFilterLookup | None = strawberry_django.filter_field()
+    comments: StrFilterLookup | None = strawberry_django.filter_field()
 
 
 @dataclass
 class PrimaryModelFilter(NetBoxModelFilter):
-    description: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    comments: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    description: StrFilterLookup | None = strawberry_django.filter_field()
+    comments: StrFilterLookup | None = strawberry_django.filter_field()

+ 26 - 1
netbox/netbox/tests/test_graphql.py

@@ -1,4 +1,5 @@
 import json
+import re
 
 import strawberry
 from django.contrib.contenttypes.models import ContentType
@@ -12,7 +13,7 @@ from dcim.choices import LocationStatusChoices
 from dcim.models import Device, DeviceRole, DeviceType, Location, Manufacturer, Site, VirtualChassis
 from extras.models import TableConfig, Tag
 from netbox.graphql.scalars import BigInt, BigIntScalar
-from netbox.graphql.schema import Query, get_schema_extensions
+from netbox.graphql.schema import Query, get_schema_extensions, schema
 from utilities.tables import get_table_for_model
 from utilities.testing import APITestCase, TestCase, disable_warnings
 
@@ -90,6 +91,30 @@ class GraphQLTestCase(TestCase):
         with disable_warnings('django.request'):
             self.assertHttpStatus(response, 302)  # Redirect to login page
 
+    def test_json_lookup_schema_is_string_backed(self):
+        """JSONLookup date/time lookups keep the legacy string-backed input types and fields."""
+        sdl = schema.as_str()
+
+        def input_block(name):
+            match = re.search(rf'^input {re.escape(name)}\b.*?^\}}', sdl, re.DOTALL | re.MULTILINE)
+            self.assertIsNotNone(match, f'{name} not found in schema')
+            return match.group(0)
+
+        # JSONLookup points at the legacy string-backed lookup type names
+        json_lookup = input_block('JSONLookup')
+        self.assertIn('date_lookup: StrDateFilterLookup', json_lookup)
+        self.assertIn('datetime_lookup: StrDatetimeFilterLookup', json_lookup)
+        self.assertIn('time_lookup: StrTimeFilterLookup', json_lookup)
+
+        # Value fields are string-backed, not Date/DateTime/Time scalars
+        self.assertIn('exact: String', input_block('StrDateFilterLookup'))
+
+        # Legacy date/time sub-lookups remain integer comparison lookups
+        for name in ('StrTimeFilterLookup', 'StrDatetimeFilterLookup'):
+            block = input_block(name)
+            self.assertIn('date: IntComparisonFilterLookup', block)
+            self.assertIn('time: IntComparisonFilterLookup', block)
+
 
 class GraphQLAPITestCase(APITestCase):
 

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

@@ -60,8 +60,8 @@ __all__ = (
 
 @strawberry_django.filter_type(models.Tenant, lookups=True)
 class TenantFilter(ContactFilterMixin, PrimaryModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    slug: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    slug: StrFilterLookup | None = strawberry_django.filter_field()
     group: Annotated['TenantGroupFilter', strawberry.lazy('tenancy.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
@@ -153,12 +153,12 @@ class TenantGroupFilter(OrganizationalModelFilter):
 
 @strawberry_django.filter_type(models.Contact, lookups=True)
 class ContactFilter(PrimaryModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    title: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    phone: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    email: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    address: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    link: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    title: StrFilterLookup | None = strawberry_django.filter_field()
+    phone: StrFilterLookup | None = strawberry_django.filter_field()
+    email: StrFilterLookup | None = strawberry_django.filter_field()
+    address: StrFilterLookup | None = strawberry_django.filter_field()
+    link: StrFilterLookup | None = strawberry_django.filter_field()
     groups: Annotated['ContactGroupFilter', strawberry.lazy('tenancy.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )

+ 12 - 13
netbox/users/graphql/filters.py

@@ -1,4 +1,3 @@
-from datetime import datetime
 from typing import Annotated
 
 import strawberry
@@ -18,27 +17,27 @@ __all__ = (
 
 @strawberry_django.filter_type(models.Group, lookups=True)
 class GroupFilter(BaseModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    description: StrFilterLookup | None = strawberry_django.filter_field()
 
 
 @strawberry_django.filter_type(models.User, lookups=True)
 class UserFilter(BaseModelFilter):
-    username: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    first_name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    last_name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    email: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    username: StrFilterLookup | None = strawberry_django.filter_field()
+    first_name: StrFilterLookup | None = strawberry_django.filter_field()
+    last_name: StrFilterLookup | None = strawberry_django.filter_field()
+    email: StrFilterLookup | None = strawberry_django.filter_field()
     is_superuser: FilterLookup[bool] | None = strawberry_django.filter_field()
     is_active: FilterLookup[bool] | None = strawberry_django.filter_field()
-    date_joined: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field()
-    last_login: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field()
+    date_joined: DatetimeFilterLookup | None = strawberry_django.filter_field()
+    last_login: DatetimeFilterLookup | None = strawberry_django.filter_field()
     groups: Annotated['GroupFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field()
 
 
 @strawberry_django.filter_type(models.Owner, lookups=True)
 class OwnerFilter(BaseModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    description: StrFilterLookup | None = strawberry_django.filter_field()
     group: Annotated['OwnerGroupFilter', strawberry.lazy('users.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
@@ -50,5 +49,5 @@ class OwnerFilter(BaseModelFilter):
 
 @strawberry_django.filter_type(models.OwnerGroup, lookups=True)
 class OwnerGroupFilter(BaseModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    description: StrFilterLookup | None = strawberry_django.filter_field()

+ 2 - 2
netbox/virtualization/graphql/filter_mixins.py

@@ -20,5 +20,5 @@ class VMComponentFilterMixin:
         strawberry_django.filter_field()
     )
     virtual_machine_id: ID | None = strawberry_django.filter_field()
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    description: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    description: StrFilterLookup | None = strawberry_django.filter_field()

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

@@ -40,7 +40,7 @@ __all__ = (
 
 @strawberry_django.filter_type(models.Cluster, lookups=True)
 class ClusterFilter(ContactFilterMixin, ScopedFilterMixin, TenancyFilterMixin, PrimaryModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
     type: Annotated['ClusterTypeFilter', strawberry.lazy('virtualization.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
@@ -96,7 +96,7 @@ class VirtualMachineFilter(
     TenancyFilterMixin,
     PrimaryModelFilter,
 ):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
     virtual_machine_type: (
         Annotated['VirtualMachineTypeFilter', strawberry.lazy('virtualization.graphql.filters')] | None
     ) = strawberry_django.filter_field()
@@ -138,7 +138,7 @@ class VirtualMachineFilter(
     disk: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
         strawberry_django.filter_field()
     )
-    serial: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    serial: StrFilterLookup | None = strawberry_django.filter_field()
     interface_count: FilterLookup[int] | None = strawberry_django.filter_field()
     virtual_disk_count: FilterLookup[int] | None = strawberry_django.filter_field()
     interfaces: Annotated['VMInterfaceFilter', strawberry.lazy('virtualization.graphql.filters')] | None = (

+ 9 - 9
netbox/vpn/graphql/filters.py

@@ -63,7 +63,7 @@ class TunnelTerminationFilter(CustomFieldsFilterMixin, TagsFilterMixin, ChangeLo
 
 @strawberry_django.filter_type(models.Tunnel, lookups=True)
 class TunnelFilter(TenancyFilterMixin, PrimaryModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
     status: BaseFilterLookup[Annotated['TunnelStatusEnum', strawberry.lazy('vpn.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
@@ -89,7 +89,7 @@ class TunnelFilter(TenancyFilterMixin, PrimaryModelFilter):
 
 @strawberry_django.filter_type(models.IKEProposal, lookups=True)
 class IKEProposalFilter(PrimaryModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
     authentication_method: (
         BaseFilterLookup[Annotated['AuthenticationMethodEnum', strawberry.lazy('vpn.graphql.enums')]] | None
     ) = (
@@ -118,7 +118,7 @@ class IKEProposalFilter(PrimaryModelFilter):
 
 @strawberry_django.filter_type(models.IKEPolicy, lookups=True)
 class IKEPolicyFilter(PrimaryModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
     version: BaseFilterLookup[Annotated['IKEVersionEnum', strawberry.lazy('vpn.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
@@ -128,12 +128,12 @@ class IKEPolicyFilter(PrimaryModelFilter):
     proposals: Annotated['IKEProposalFilter', strawberry.lazy('vpn.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
-    preshared_key: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    preshared_key: StrFilterLookup | None = strawberry_django.filter_field()
 
 
 @strawberry_django.filter_type(models.IPSecProposal, lookups=True)
 class IPSecProposalFilter(PrimaryModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
     encryption_algorithm: (
         BaseFilterLookup[Annotated['EncryptionAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')]] | None
     ) = (
@@ -159,7 +159,7 @@ class IPSecProposalFilter(PrimaryModelFilter):
 
 @strawberry_django.filter_type(models.IPSecPolicy, lookups=True)
 class IPSecPolicyFilter(PrimaryModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
     proposals: Annotated['IPSecProposalFilter', strawberry.lazy('vpn.graphql.filters')] | None = (
         strawberry_django.filter_field()
     )
@@ -170,7 +170,7 @@ class IPSecPolicyFilter(PrimaryModelFilter):
 
 @strawberry_django.filter_type(models.IPSecProfile, lookups=True)
 class IPSecProfileFilter(PrimaryModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
     mode: BaseFilterLookup[Annotated['IPSecModeEnum', strawberry.lazy('vpn.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
@@ -186,8 +186,8 @@ class IPSecProfileFilter(PrimaryModelFilter):
 
 @strawberry_django.filter_type(models.L2VPN, lookups=True)
 class L2VPNFilter(ContactFilterMixin, TenancyFilterMixin, PrimaryModelFilter):
-    name: StrFilterLookup[str] | None = strawberry_django.filter_field()
-    slug: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    name: StrFilterLookup | None = strawberry_django.filter_field()
+    slug: StrFilterLookup | None = strawberry_django.filter_field()
     type: BaseFilterLookup[Annotated['L2VPNTypeEnum', strawberry.lazy('vpn.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )

+ 1 - 1
netbox/wireless/graphql/filter_mixins.py

@@ -21,4 +21,4 @@ class WirelessAuthenticationFilterMixin:
     auth_cipher: Annotated['WirelessAuthCipherEnum', strawberry.lazy('wireless.graphql.enums')] | None = (
         strawberry_django.filter_field()
     )
-    auth_psk: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    auth_psk: StrFilterLookup | None = strawberry_django.filter_field()

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

@@ -38,7 +38,7 @@ class WirelessLANFilter(
     TenancyFilterMixin,
     PrimaryModelFilter
 ):
-    ssid: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    ssid: StrFilterLookup | None = strawberry_django.filter_field()
     status: BaseFilterLookup[Annotated['WirelessLANStatusEnum', strawberry.lazy('wireless.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )
@@ -65,7 +65,7 @@ class WirelessLinkFilter(
         strawberry_django.filter_field()
     )
     interface_b_id: ID | None = strawberry_django.filter_field()
-    ssid: StrFilterLookup[str] | None = strawberry_django.filter_field()
+    ssid: StrFilterLookup | None = strawberry_django.filter_field()
     status: BaseFilterLookup[Annotated['WirelessLANStatusEnum', strawberry.lazy('wireless.graphql.enums')]] | None = (
         strawberry_django.filter_field()
     )

+ 1 - 1
requirements.txt

@@ -39,7 +39,7 @@ social-auth-app-django==5.9.0
 social-auth-core==4.8.7
 sorl-thumbnail==13.0.0
 strawberry-graphql==0.316.0
-strawberry-graphql-django==0.85.0
+strawberry-graphql-django==0.86.1
 svgwrite==1.4.3
 tablib==3.9.0
 tzdata==2026.2