This package contains advanced filters for graphene-django.
The standard filtering feature in graphene-django relies on the django-filter
library and therefore provides the flat API without the ability to use logical operators such as
and
, or
and not
. This library makes the API nested and adds logical expressions by extension
of the DjangoFilterConnectionField
field and the FilterSet
class.
Also, the library provides some other convenient filtering features.
# pip
pip install graphene-django-filter
# poetry
poetry add graphene-django-filter
To use, simply replace all DjangoFilterConnectionField
fields with
AdvancedDjangoFilterConnectionField
fields in your queries.
Also,if you create custom FilterSets, replace the inheritance from the FilterSet
class
with the inheritance from the AdvancedFilterSet
class.
For example, the following task query exposes the old flat API.
import graphene
from django_filters import FilterSet
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
class TaskFilter(FilterSet):
class Meta:
model = Task
fields = {
'name': ('exact', 'contains'),
'user__email': ('exact', 'contains'),
'user__first_name': ('exact', 'contains'),
'user__last_name': ('exact', 'contains'),
}
class UserType(DjangoObjectType):
class Meta:
model = User
interfaces = (graphene.relay.Node,)
fields = '__all__'
class TaskType(DjangoObjectType):
user = graphene.Field(UserType)
class Meta:
model = Task
interfaces = (graphene.relay.Node,)
fields = '__all__'
filterset_class = TaskFilter
class Query(graphene.ObjectType):
tasks = DjangoFilterConnectionField(TaskType)
The flat API in which all filters are applied using the and
operator looks like this.
{
tasks(
name_Contains: "important"
user_Email_Contains: "john"
user_FirstName: "John"
user_LastName: "Dou"
){
edges {
node {
id
name
}
}
}
}
After replacing the field class with the AdvancedDjangoFilterConnectionField
and the FilterSet
class with the AdvancedFilterSet
the API becomes nested with support for logical expressions.
import graphene
from graphene_django_filter import AdvancedDjangoFilterConnectionField, AdvancedFilterSet
class TaskFilter(AdvancedFilterSet):
class Meta:
model = Task
fields = {
'name': ('exact', 'contains'),
'user__email': ('exact', 'contains'),
'user__first_name': ('exact', 'contains'),
'user__last_name': ('exact', 'contains'),
}
class Query(graphene.ObjectType):
tasks = AdvancedDjangoFilterConnectionField(TaskType)
For example, the following query returns tasks which names contain the word "important"
or the user's email address contains the word "john" and the user's last name is "Dou"
and the first name is not "John".
Note that the operators are applied to lookups
such as contains
, exact
, etc. at the last level of nesting.
{
tasks(
filter: {
or: [
{name: {contains: "important"}}
{
and: [
{user: {email: {contains: "john"}}}
{user: {lastName: {exact: "Dou"}}}
]
}
]
not: {
user: {firstName: {exact: "John"}}
}
}
) {
edges {
node {
id
name
}
}
}
}
The same result can be achieved with an alternative query structure
because within the same object the and
operator is always used.
{
tasks(
filter: {
or: [
{name: {contains: "important"}}
{
user: {
email: {contains: "john"}
lastName: {exact: "Dou"}
}
}
]
not: {
user: {firstName: {exact: "John"}}
}
}
){
edges {
node {
id
name
}
}
}
}
The filter input type has the following structure.
input FilterInputType {
and: [FilterInputType]
or: [FilterInputType]
not: FilterInputType
...FieldLookups
}
For more examples, see tests.
Django provides the API
for PostgreSQL full text search. Graphene-Django-Filter inject this API into the GraphQL filter API.
To use, add full_text_search
lookup to fields for which you want to enable full text search.
For example, the following type has full text search for
first_name
and last_name
fields.
import graphene
from graphene_django import DjangoObjectType
from graphene_django_filter import AdvancedDjangoFilterConnectionField
class UserType(DjangoObjectType):
class Meta:
model = User
interfaces = (graphene.relay.Node,)
fields = '__all__'
filter_fields = {
'email': ('exact', 'startswith', 'contains'),
'first_name': ('exact', 'contains', 'full_text_search'),
'last_name': ('exact', 'contains', 'full_text_search'),
}
class Query(graphene.ObjectType):
users = AdvancedDjangoFilterConnectionField(UserType)
Since this feature belongs to the AdvancedFilterSet, it can be used in a custom FilterSet. The following example will work exactly like the previous one.
import graphene
from graphene_django import DjangoObjectType
from graphene_django_filter import AdvancedDjangoFilterConnectionField, AdvancedFilterSet
class UserFilter(AdvancedFilterSet):
class Meta:
model = User
fields = {
'email': ('exact', 'startswith', 'contains'),
'first_name': ('exact', 'contains', 'full_text_search'),
'last_name': ('exact', 'contains', 'full_text_search'),
}
class UserType(DjangoObjectType):
class Meta:
model = User
interfaces = (graphene.relay.Node,)
fields = '__all__'
filterset_class = UserFilter
class Query(graphene.ObjectType):
users = AdvancedDjangoFilterConnectionField(UserType)
Full text search API includes SearchQuery, SearchRank, and Trigram filters. SearchQuery and SearchRank filters are at the top level. If some field has been enabled for full text search then it can be included in the field array. The following queries show an example of using the SearchQuery and SearchRank filters.
{
users(
filter: {
searchQuery: {
vector: {
fields: ["first_name"]
}
query: {
or: [
{value: "Bob"}
{value: "Alice"}
]
}
}
}
){
edges {
node {
id
firstName
lastName
}
}
}
}
{
users(
filter: {
searchRank: {
vector: {fields: ["first_name", "last_name"]}
query: {value: "John Dou"}
lookups: {gte: 0.5}
}
}
){
edges {
node {
id
firstName
lastName
}
}
}
}
Trigram filter belongs to the corresponding field. The following query shows an example of using the Trigram filter.
{
users(
filter: {
firstName: {
trigram: {
value: "john"
lookups: {gte: 0.85}
}
}
}
){
edges {
node {
id
firstName
lastName
}
}
}
}
Input types have the following structure.
input SearchConfigInputType {
value: String!
isField: Boolean
}
enum SearchVectorWeight {
A
B
C
D
}
input SearchVectorInputType {
fields: [String!]!
config: SearchConfigInputType
weight: SearchVectorWeight
}
enum SearchQueryType {
PLAIN
PHRASE
RAW
WEBSEARCH
}
input SearchQueryInputType {
value: String
config: SearchConfigInputType
and: [SearchQueryInputType]
or: [SearchQueryInputType]
not: SearchQueryInputType
}
input SearchQueryFilterInputType {
vector: SearchVectorInputType!
query: SearchQueryInputType!
}
input FloatLookupsInputType {
exact: Float
gt: Float
gte: Float
lt: Float
lte: Float
}
input SearchRankWeightsInputType {
D: Float
C: Float
B: Float
A: Float
}
input SearchRankFilterInputType {
vector: SearchVectorInputType!
query: SearchQueryInputType!
lookups: FloatLookupsInputType!
weights: SearchRankWeightsInputType
coverDensity: Boolean
normalization: Int
}
enum TrigramSearchKind {
SIMILARITY
DISTANCE
}
input TrigramFilterInputType {
kind: TrigramSearchKind
lookups: FloatLookupsInputType!
value: String!
}
For more examples, see tests.
The library can be customised using settings.
To add settings, create a dictionary
with name GRAPHENE_DJANGO_FILTER
in the project’s settings.py
.
The default settings are as follows.
GRAPHENE_DJANGO_FILTER = {
'FILTER_KEY': 'filter',
'AND_KEY': 'and',
'OR_KEY': 'or',
'NOT_KEY': 'not',
}
To read the settings, import them from the conf
module.
from graphene_django_filter.conf import settings
print(settings.FILTER_KEY)
The settings
object also includes fixed settings, which depend on the user's environment.
IS_POSTGRESQL
determinate that current database is PostgreSQL
and HAS_TRIGRAM_EXTENSION
that pg_trgm
extension is installed.