Filtering¶
Filtering allows clients to request specific subsets of data based on field values and conditions. graphene-django-extras
provides powerful filtering capabilities built on top of django-filter
, making it easy to create flexible and efficient GraphQL queries.
Overview¶
The filtering system in graphene-django-extras
provides:
- :material-filter: Field-Based Filtering: Filter by any model field
- :material-tune-vertical: Multiple Lookup Types: Support for various comparison operations
- :material-link: Related Field Filtering: Filter across relationships
- :material-code-tags: Custom FilterSets: Advanced filtering with django-filter
- :material-lightning-bolt: Automatic GraphQL Integration: Seamless schema generation
Basic Field Filtering¶
The simplest way to add filtering is through the filter_fields
meta option:
from graphene_django_extras import DjangoObjectType, DjangoListObjectType
class UserType(DjangoObjectType):
class Meta:
model = User
filter_fields = ['username', 'email', 'is_active']
class UserListType(DjangoListObjectType):
class Meta:
model = User
filter_fields = ['username', 'email', 'is_active']
Lookup Types¶
Control how fields are filtered with lookup types:
Lookup | Description | Example |
---|---|---|
exact |
Exact match | username: "john" |
iexact |
Case-insensitive exact match | username_Iexact: "JOHN" |
contains |
Contains substring | username_Contains: "oh" |
icontains |
Case-insensitive contains | username_Icontains: "OH" |
startswith |
Starts with | username_Startswith: "jo" |
endswith |
Ends with | username_Endswith: "hn" |
lt |
Less than | age_Lt: 30 |
lte |
Less than or equal | age_Lte: 30 |
gt |
Greater than | age_Gt: 18 |
gte |
Greater than or equal | age_Gte: 18 |
in |
In list | id_In: [1, 2, 3] |
isnull |
Is null/not null | email_Isnull: true |
Related Field Filtering¶
Filter across model relationships using double underscores:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(blank=True)
location = models.CharField(max_length=100, blank=True)
birth_date = models.DateField(null=True, blank=True)
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
class PostType(DjangoObjectType):
class Meta:
model = Post
filter_fields = {
'title': ('icontains', 'exact'),
'content': ('icontains',),
'created_at': ('exact', 'lt', 'gt', 'lte', 'gte'),
# Filter by author fields
'author__username': ('icontains', 'exact'),
'author__email': ('exact',),
'author__is_active': ('exact',),
# Filter by author's profile
'author__profile__location': ('icontains', 'exact'),
'author__profile__birth_date': ('exact', 'lt', 'gt'),
# Filter by category
'category__name': ('icontains', 'exact'),
'category__slug': ('exact',),
}
Custom FilterSets¶
For advanced filtering needs, use django-filter FilterSets:
import django_filters
from django_filters import FilterSet, filters
from django.contrib.auth.models import User
class UserFilterSet(FilterSet):
# Custom filter for name search across multiple fields
name = filters.CharFilter(method='filter_name', label='Name')
# Date range filter
joined_after = filters.DateFilter(
field_name='date_joined',
lookup_expr='gte'
)
joined_before = filters.DateFilter(
field_name='date_joined',
lookup_expr='lte'
)
# Custom choice filter
user_type = filters.ChoiceFilter(
choices=[('staff', 'Staff'), ('regular', 'Regular')],
method='filter_user_type'
)
class Meta:
model = User
fields = {
'username': ['exact', 'icontains'],
'email': ['exact', 'icontains'],
'is_active': ['exact'],
}
def filter_name(self, queryset, name, value):
"""Search across first_name and last_name."""
return queryset.filter(
Q(first_name__icontains=value) |
Q(last_name__icontains=value)
)
def filter_user_type(self, queryset, name, value):
"""Filter by user type."""
if value == 'staff':
return queryset.filter(is_staff=True)
elif value == 'regular':
return queryset.filter(is_staff=False)
return queryset
Combining Filtering with Other Features¶
Filtering + Pagination¶
Combine filtering with pagination for efficient data access:
from graphene_django_extras import DjangoListObjectType
from graphene_django_extras.paginations import LimitOffsetGraphqlPagination
class UserListType(DjangoListObjectType):
class Meta:
model = User
pagination = LimitOffsetGraphqlPagination(default_limit=25)
filter_fields = {
'username': ('icontains', 'exact'),
'email': ('icontains', 'exact'),
'is_active': ('exact',),
'date_joined': ('exact', 'lt', 'gt', 'lte', 'gte'),
}
Filtering + Ordering¶
Field-Level Filtering¶
Use fields for more granular control:
import graphene
from graphene_django_extras import DjangoFilterListField
class Query(graphene.ObjectType):
# Basic filtering field
all_users = DjangoFilterListField(UserType)
# Filtering with custom filterset
staff_users = DjangoFilterListField(
UserType,
filterset_class=UserFilterSet
)
# Custom resolver with filtering
active_users = DjangoFilterListField(UserType)
def resolve_active_users(self, info, **kwargs):
return User.objects.filter(is_active=True)
from graphene_django_extras import DjangoFilterPaginateListField
from graphene_django_extras.paginations import PageGraphqlPagination
class Query(graphene.ObjectType):
users = DjangoFilterPaginateListField(
UserType,
pagination=PageGraphqlPagination(page_size=20),
filterset_class=UserFilterSet,
description="Paginated and filtered user list"
)
Advanced Filtering Techniques¶
Custom Filter Methods¶
class PostFilterSet(FilterSet):
# Filter by multiple categories
categories = filters.ModelMultipleChoiceFilter(
field_name='category',
queryset=Category.objects.all(),
conjoined=False # OR logic instead of AND
)
# Search across multiple text fields
search = filters.CharFilter(method='filter_search')
# Filter by author's profile data
author_location = filters.CharFilter(
field_name='author__profile__location',
lookup_expr='icontains'
)
class Meta:
model = Post
fields = ['title', 'created_at']
def filter_search(self, queryset, name, value):
"""Search across title and content."""
return queryset.filter(
Q(title__icontains=value) |
Q(content__icontains=value)
)
Range Filters¶
Performance Optimization¶
Database Indexing¶
Index Filter Fields
Add database indexes to commonly filtered fields for better performance.
class User(models.Model):
username = models.CharField(max_length=150, unique=True, db_index=True)
email = models.EmailField(db_index=True)
is_active = models.BooleanField(default=True, db_index=True)
date_joined = models.DateTimeField(auto_now_add=True, db_index=True)
Query Optimization¶
Error Handling¶
Handle filtering errors gracefully:
class CustomUserFilterSet(FilterSet):
age = filters.NumberFilter(method='filter_age')
class Meta:
model = User
fields = ['username', 'email']
def filter_age(self, queryset, name, value):
if value < 0:
raise ValueError("Age cannot be negative")
# Calculate age from birth_date
from datetime import date
cutoff_date = date.today() - timedelta(days=365 * value)
return queryset.filter(profile__birth_date__lte=cutoff_date)
Testing Filtering¶
import pytest
from graphene.test import Client
from .schema import schema
@pytest.mark.django_db
def test_user_filtering():
# Create test data
active_user = User.objects.create_user(
username='active_user',
email='active@example.com',
is_active=True
)
inactive_user = User.objects.create_user(
username='inactive_user',
email='inactive@example.com',
is_active=False
)
client = Client(schema)
query = """
query GetActiveUsers {
users(isActive: true) {
results {
id
username
isActive
}
}
}
"""
result = client.execute(query)
users = result['data']['users']['results']
assert len(users) == 1
assert users[0]['username'] == 'active_user'
assert users[0]['isActive'] is True
@pytest.mark.django_db
def test_related_field_filtering():
# Create test data with relationships
category = Category.objects.create(name='Technology')
user = User.objects.create_user(username='author', email='author@example.com')
Post.objects.create(
title='Django GraphQL',
content='Advanced GraphQL techniques',
author=user,
category=category
)
client = Client(schema)
query = """
query GetTechPosts {
posts(category_Name: "Technology", title_Icontains: "Django") {
results {
title
category {
name
}
}
}
}
"""
result = client.execute(query)
posts = result['data']['posts']['results']
assert len(posts) == 1
assert posts[0]['title'] == 'Django GraphQL'
assert posts[0]['category']['name'] == 'Technology'
Best Practices¶
Filtering Best Practices
- Index Filter Fields: Add database indexes to frequently filtered fields
- Limit Filter Options: Don't expose sensitive fields for filtering
- Use Related Queries: Optimize with
select_related
andprefetch_related
- Validate Input: Add validation for custom filter methods
- Test Edge Cases: Test filtering with edge cases and invalid input
- Document Filters: Provide clear descriptions for custom filters
Security Considerations¶
class SecureUserFilterSet(FilterSet):
class Meta:
model = User
# Don't expose sensitive fields
fields = {
'username': ['icontains', 'exact'],
'is_active': ['exact'],
# Exclude: password, last_login, user_permissions, etc.
}
The filtering system in graphene-django-extras
provides powerful and flexible ways to query your data, making it easy to build efficient GraphQL APIs with Django.