diff --git a/strawberry_django/filters.py b/strawberry_django/filters.py index 23127cb98..d6c5bf135 100644 --- a/strawberry_django/filters.py +++ b/strawberry_django/filters.py @@ -217,6 +217,11 @@ def build_filter_kwargs( filter_methods.append(filter_method) continue + q_method = getattr(filters, f"q_{'n_' if negated else ''}{field_name}", None) + if q_method and (q_value := q_method(field_value)): + filter_kwargs &= q_value + continue + if django_model: if field_name in ("AND", "OR", "NOT"): # noqa: PLR6201 if has_object_definition(field_value): diff --git a/tests/filters/test_filters.py b/tests/filters/test_filters.py index 3c9b4544f..8fb3d1caf 100644 --- a/tests/filters/test_filters.py +++ b/tests/filters/test_filters.py @@ -4,6 +4,7 @@ import pytest import strawberry +from django.db.models import Q from strawberry import auto from strawberry.annotation import StrawberryAnnotation from strawberry.types import ExecutionResult @@ -89,6 +90,14 @@ def filter(self, queryset): return queryset.filter(name__icontains=self.name) +@strawberry_django.filters.filter(models.Fruit) +class QFilter: + search: Optional[str] + + def q_search(self, value: str) -> Q: + return Q(name=value) + + @strawberry_django.type(models.Vegetable, filters=VegetableFilter) class Vegetable: id: auto @@ -107,6 +116,7 @@ class Fruit: class Query: fruits: List[Fruit] = strawberry_django.field() field_filter: List[Fruit] = strawberry_django.field(filters=FieldFilter) + q_filter: List[Fruit] = strawberry_django.field(filters=QFilter) type_filter: List[Fruit] = strawberry_django.field(filters=TypeFilter) enum_filter: List[Fruit] = strawberry_django.field(filters=EnumFilter) enum_lookup_filter: List[Fruit] = strawberry_django.field(filters=EnumLookupFilter) @@ -247,6 +257,36 @@ def test_field_filter_method(query, fruits): ] +def test_q_filter_method(query, fruits): + result = query('{ fruits: qFilter(filters: { search: "strawberry" }) { id name } }') + assert not result.errors + assert result.data["fruits"] == [ + {"id": "1", "name": "strawberry"}, + ] + + +def test_q_filter_method_with_nested_not_filter(query, fruits): + result = query( + '{ fruits: qFilter(filters: { NOT : { search: "strawberry" }}) { id name } }' + ) + assert not result.errors + assert result.data["fruits"] == [ + {"id": "2", "name": "raspberry"}, + {"id": "3", "name": "banana"}, + ] + + +def test_q_filter_method_with_nested_or_filter(query, fruits): + result = query( + '{ fruits: qFilter(filters: { search: "strawberry", OR: { search: "raspberry" }}) { id name } }' + ) + assert not result.errors + assert result.data["fruits"] == [ + {"id": "1", "name": "strawberry"}, + {"id": "2", "name": "raspberry"}, + ] + + def test_type_filter_method(query, fruits): result = query('{ fruits: typeFilter(filters: { name: "anana" }) { id name } }') assert not result.errors