Filtering
It is possible to define filters for Django types, which will
be converted into .filter(...) queries for the ORM:
import strawberry_djangofrom strawberry import auto
@strawberry_django.filter_type(models.Fruit)class FruitFilter: id: auto name: auto
@strawberry_django.type(models.Fruit, filters=FruitFilter)class Fruit: ... Tip
In most cases filter fields should have Optional annotations and default value strawberry.UNSET like so:
foo: Optional[SomeType] = strawberry.UNSET
Above auto annotation is wrapped in Optional automatically.
UNSET is automatically used for fields without field or with strawberry_django.filter_field .
The code above would generate following schema:
input FruitFilter { id: ID name: String AND: FruitFilter OR: FruitFilter NOT: FruitFilter DISTINCT: Boolean} Tip
If you are using the relay integration and working with types inheriting
from relay.Node and GlobalID for identifying objects, you might want to set
MAP_AUTO_ID_AS_GLOBAL_ID=True in your strawberry django settings
to make sure auto fields gets mapped to GlobalID on types and filters.
AND, OR, NOT, DISTINCT โฆ
To every filter AND , OR , NOT & DISTINCT fields are added to allow more complex filtering
{ fruits( filters: { name: "kebab" OR: { name: "raspberry" } } ) { ... }} List-based AND/OR/NOT Filters
The AND , OR , and NOT operators can also be declared as lists, allowing for more complex combinations of conditions. This is particularly useful when you need to combine multiple conditions in a single operation.
@strawberry_django.filter_type(models.Vegetable, lookups=True)class VegetableFilter: id: auto name: auto AND: Optional[list[Self]] = strawberry.UNSET OR: Optional[list[Self]] = strawberry.UNSET NOT: Optional[list[Self]] = strawberry.UNSET This enables queries like:
{ vegetables( filters: { AND: [{ name: { contains: "blue" } }, { name: { contains: "squash" } }] } ) { id }} The list-based filtering system differs from the single object filter in a few ways:
- It allows combining multiple conditions in a single
AND,OR, orNOToperation - The conditions in a list are evaluated together as a group
- When using
AND, all conditions in the list must be satisfied - When using
OR, any condition in the list can be satisfied - When using
NOT, none of the conditions in the list should be satisfied
This is particularly useful for complex queries where you need to have multiple conditions against the same field.
Lookups
Lookups can be added to all fields with lookups=True , which will
add more options to resolve each type. For example:
@strawberry_django.filter_type(models.Fruit, lookups=True)class FruitFilter: id: auto name: auto The code above would generate the following schema:
input IDBaseFilterLookup { exact: ID isNull: Boolean inList: [String!]}
input StrFilterLookup { exact: ID isNull: Boolean inList: [String!] iExact: String contains: String iContains: String startsWith: String iStartsWith: String endsWith: String iEndsWith: String regex: String iRegex: String}
input FruitFilter { id: IDFilterLookup name: StrFilterLookup AND: FruitFilter OR: FruitFilter NOT: FruitFilter DISTINCT: Boolean} Single-field lookup can be annotated with the FilterLookup generic type.
from strawberry_django import FilterLookup
@strawberry_django.filter(models.Fruit)class FruitFilter: name: FilterLookup[str] Filtering over relationships
@strawberry_django.filter(models.Color)class ColorFilter: id: auto name: auto
@strawberry_django.filter(models.Fruit)class FruitFilter: id: auto name: auto color: ColorFilter | None The code above would generate following schema:
input ColorFilter { id: ID name: String AND: ColorFilter OR: ColorFilter NOT: ColorFilter}
input FruitFilter { id: ID name: String color: ColorFilter AND: FruitFilter OR: FruitFilter NOT: FruitFilter} Custom filter methods
You can define custom filter method by defining your own resolver.
@strawberry_django.filter(models.Fruit)class FruitFilter: name: auto last_name: auto
@strawberry_django.filter_field def simple(self, value: str, prefix) -> Q: return Q(**{f"{prefix}name": value})
@strawberry_django.filter_field def full_name( self, queryset: QuerySet, value: str, prefix: str ) -> tuple[QuerySet, Q]: queryset = queryset.alias( _fullname=Concat( f"{prefix}name", Value(" "), f"{prefix}last_name" ) ) return queryset, Q(**{"_fullname": value})
@strawberry_django.filter_field def full_name_lookups( self, info: Info, queryset: QuerySet, value: strawberry_django.FilterLookup[str], prefix: str ) -> tuple[QuerySet, Q]: queryset = queryset.alias( _fullname=Concat( f"{prefix}name", Value(" "), f"{prefix}last_name" ) ) return strawberry_django.process_filters( filters=value, queryset=queryset, info=info, prefix=f"{prefix}_fullname" ) Warning
It is discouraged to use queryset.filter() directly. When using more
complex filtering via NOT , OR & AND this might lead to undesired behaviour.
[!TIP]
process_filters
As seen above
strawberry_django.process_filtersfunction is exposed and can be reused in custom methods. Above itโs used to resolve fields lookupsnull values
By default
nullvalue is ignored for all filters & lookups. This applies to custom filter methods as well. Those wonโt even be called (you donโt have to check forNone). This can be modified usingstrawberry_django.filter_field(filter_none=True)This also means that built in
exact&iExactlookups cannot be used to filter forNoneandisNullhave to be used explicitly.value resolution
valueparameter of typerelay.GlobalIDis resolved to itsnode_idattributevalueparameter of typeEnumis resolved to isโs value- above types are converted in
listsas wellresolution can modified via
strawberry_django.filter_field(resolve_value=...)
- True - always resolve
- False - never resolve
- UNSET (default) - resolves for filters without custom method only
The code above generates the following schema:
input FruitFilter { name: String lastName: String simple: str fullName: str fullNameLookups: StrFilterLookup} Resolver arguments
-
prefix- represents the current path or position- Required
- Important for nested filtering
- In code bellow custom filter
nameends up filteringFruitinstead ofColorwithout applyingprefix
@strawberry_django.filter(models.Fruit)class FruitFilter: name: auto color: ColorFilter | None
@strawberry_django.filter(models.Color)class ColorFilter: @strawberry_django.filter_field def name(self, value: str, prefix: str): # prefix is "fruit_set__" if unused root object is filtered instead if value: return Q(name=value) return Q() { fruits( filters: {color: name: "blue"} ) { ... }} -
value- represents graphql field type- Required, but forbidden for default
filtermethod - must be annotated
- used instead of fieldโs return type
- Required, but forbidden for default
-
queryset- can be used for more complex filtering- Optional, but Required for default
filtermethod - usually used to
annotateQuerySet
- Optional, but Required for default
Resolver return
For custom field methods two return values are supported
- djangoโs
Qobject - tuple with
QuerySetand djangoโsQobject ->tuple[QuerySet, Q]
For default filter method only second variant is supported.
What about nulls?
By default null values are ignored. This can be toggled as such @strawberry_django.filter_field(filter_none=True)
Overriding the default filter method
Works similar to field filter method, but:
- is responsible for resolution of filtering for entire object
- must be named
filter - argument
querysetis Required - argument
valueis Forbidden
@strawberry_django.filter(models.Fruit)class FruitFilter: def ordered( self, value: int, prefix: str, queryset: QuerySet, ): queryset = queryset.alias( _ordered_num=Count(f"{prefix}orders__id") ) return queryset, Q(**{f"{prefix}_ordered_num": value})
@strawberry_django.order_field def filter( self, info: Info, queryset: QuerySet, prefix: str, ) -> tuple[QuerySet, list[Q]]: queryset = queryset.filter( ... # Do some query modification )
return strawberry_django.process_filters( self, info=info, queryset=queryset, prefix=prefix, skip_object_order_method=True ) Tip
As seen above strawberry_django.process_filters function is exposed and can be
reused in custom methods.
For filter method filter skip_object_order_method was used to avoid endless recursion.
Adding filters to types
All fields and CUD mutations inherit filters from the underlying type by default. So, if you have a field like this:
@strawberry_django.type(models.Fruit, filters=FruitFilter)class Fruit: ...
@strawberry.typeclass Query: fruits: list[Fruit] = strawberry_django.field() The fruits field will inherit the filters of the type in the same way as
if it was passed to the field.
Adding filters directly into a field
Filters added into a field override the default filters of this type.
@strawberry.typeclass Query: fruits: list[Fruit] = strawberry_django.field(filters=FruitFilter) Generic Lookup reference
There is 7 already defined Generic Lookup strawberry.input classes importable from strawberry_django
BaseFilterLookup
- contains
exact,isNull&inList - used for
ID&boolfields
RangeLookup
- used for
rangeorBETWEENfiltering
ComparisonFilterLookup
- inherits
BaseFilterLookup - additionaly contains
gt,gte,lt,lte, &range - used for Numberical fields
FilterLookup
- inherits
BaseFilterLookup - additionally contains
iExact,contains,iContains,startsWith,iStartsWith,endsWith,iEndsWith,regex&iRegex - used for string based fields and as default
DateFilterLookup
- inherits
ComparisonFilterLookup - additionally contains
year,month,day,weekDay,isoWeekDay,week,isoYear&quarter - used for date based fields
TimeFilterLookup
- inherits
ComparisonFilterLookup - additionally contains
hour,minute,second,date&time - used for time based fields
DatetimeFilterLookup
- inherits
DateFilterLookup&TimeFilterLookup - used for timedate based fields
Legacy filtering
The previous version of filters can be enabled via USE_DEPRECATED_FILTERS
Warning
If USE_DEPRECATED_FILTERS is not set to True legacy custom filtering
methods will be not be called.
When using legacy filters it is important to use legacy
strawberry_django.filters.FilterLookup lookups as well.
The correct version is applied for auto
annotated filter field (given lookups=True being set). Mixing old and new lookups
might lead to error DuplicatedTypeName: Type StrFilterLookup is defined multiple times in the schema .
While legacy filtering is enabled new filtering custom methods are
fully functional including default filter method.
Migration process could be composed of these steps:
- enable USE_DEPRECATED_FILTERS
- gradually transform custom filter field methods to new version (do not forget to use old FilterLookup if applicable)
- gradually transform default
filtermethods - disable USE_DEPRECATED_FILTERS - This is breaking change