From 31338a28e3e25a3dfdfa539465c25f21f46d3630 Mon Sep 17 00:00:00 2001 From: Brian Tiemann Date: Tue, 19 May 2026 11:53:11 -0400 Subject: [PATCH] Closes #22059: Consolidate numeric GraphQL lookup classes via shared mixin Extract _NumericLookupMixin with shared get_filter() and filter() methods. IntegerLookup, BigIntegerLookup, and FloatLookup each inherit from it and declare only their type-specific fields, eliminating triplicated logic. Co-Authored-By: Claude Sonnet 4.6 --- netbox/netbox/graphql/filter_lookups.py | 86 ++++++++----------------- 1 file changed, 26 insertions(+), 60 deletions(-) diff --git a/netbox/netbox/graphql/filter_lookups.py b/netbox/netbox/graphql/filter_lookups.py index 583caa6d3..243dfb4e8 100644 --- a/netbox/netbox/graphql/filter_lookups.py +++ b/netbox/netbox/graphql/filter_lookups.py @@ -59,83 +59,49 @@ class JSONLookup: return None +class _NumericLookupMixin: + """Shared filter logic for numeric lookup input types (Integer, BigInteger, Float).""" + + def get_filter(self): + for field in self.__strawberry_definition__.fields: + value = getattr(self, field.name, None) + if value is not strawberry.UNSET: + return value + return None + + @strawberry_django.filter_field + def filter(self, info: Info, queryset: QuerySet, prefix: DirectiveValue[str] = '') -> tuple[QuerySet, Q]: + filters = self.get_filter() + + if not filters: + return queryset, Q() + + if isinstance(filters, RangeLookup): + prefix = f'{prefix}range__' + + return process_filters(filters=filters, queryset=queryset, info=info, prefix=prefix) + + @strawberry.input(one_of=True, description='Lookup for Integer fields. Only one of the lookup fields can be set.') -class IntegerLookup: +class IntegerLookup(_NumericLookupMixin): filter_lookup: FilterLookup[int] | None = strawberry_django.filter_field() range_lookup: RangeLookup[int] | None = strawberry_django.filter_field() comparison_lookup: ComparisonFilterLookup[int] | None = strawberry_django.filter_field() - def get_filter(self): - for field in self.__strawberry_definition__.fields: - value = getattr(self, field.name, None) - if value is not strawberry.UNSET: - return value - return None - - @strawberry_django.filter_field - def filter(self, info: Info, queryset: QuerySet, prefix: DirectiveValue[str] = '') -> tuple[QuerySet, Q]: - filters = self.get_filter() - - if not filters: - return queryset, Q() - - if isinstance(filters, RangeLookup): - prefix = f'{prefix}range__' - - return process_filters(filters=filters, queryset=queryset, info=info, prefix=prefix) - @strawberry.input(one_of=True, description='Lookup for BigInteger fields. Only one of the lookup fields can be set.') -class BigIntegerLookup: +class BigIntegerLookup(_NumericLookupMixin): filter_lookup: FilterLookup[BigInt] | None = strawberry_django.filter_field() range_lookup: RangeLookup[BigInt] | None = strawberry_django.filter_field() comparison_lookup: ComparisonFilterLookup[BigInt] | None = strawberry_django.filter_field() - def get_filter(self): - for field in self.__strawberry_definition__.fields: - value = getattr(self, field.name, None) - if value is not strawberry.UNSET: - return value - return None - - @strawberry_django.filter_field - def filter(self, info: Info, queryset: QuerySet, prefix: DirectiveValue[str] = '') -> tuple[QuerySet, Q]: - filters = self.get_filter() - - if not filters: - return queryset, Q() - - if isinstance(filters, RangeLookup): - prefix = f'{prefix}range__' - - return process_filters(filters=filters, queryset=queryset, info=info, prefix=prefix) - @strawberry.input(one_of=True, description='Lookup for Float fields. Only one of the lookup fields can be set.') -class FloatLookup: +class FloatLookup(_NumericLookupMixin): filter_lookup: FilterLookup[float] | None = strawberry_django.filter_field() range_lookup: RangeLookup[float] | None = strawberry_django.filter_field() comparison_lookup: ComparisonFilterLookup[float] | None = strawberry_django.filter_field() - def get_filter(self): - for field in self.__strawberry_definition__.fields: - value = getattr(self, field.name, None) - if value is not strawberry.UNSET: - return value - return None - - @strawberry_django.filter_field - def filter(self, info: Info, queryset: QuerySet, prefix: DirectiveValue[str] = '') -> tuple[QuerySet, Q]: - filters = self.get_filter() - - if not filters: - return queryset, Q() - - if isinstance(filters, RangeLookup): - prefix = f'{prefix}range__' - - return process_filters(filters=filters, queryset=queryset, info=info, prefix=prefix) - @strawberry.input class JSONFilter: