Skip to main content

Django REST Filtering

In modern web applications, efficient data retrieval is crucial. When working with APIs, clients often need to access specific subsets of data rather than entire collections. This is where filtering comes into play in Django REST Framework (DRF).

Introduction to Filtering

Filtering allows API clients to request only the data they need by specifying criteria to narrow down results. For example, instead of retrieving all products, a client might want to fetch only products in a specific category or with a price below a certain threshold.

Django REST Framework provides several approaches to implement filtering:

  1. FilterSets with django-filter
  2. Query parameter filtering
  3. Custom filtering backends

Let's explore each method in detail.

Setting Up Your Environment

Before we dive into filtering, make sure you have Django and Django REST Framework installed:

bash
pip install django djangorestframework django-filter

Add them to your INSTALLED_APPS:

python
INSTALLED_APPS = [
# ...
'rest_framework',
'django_filters',
# ...
]

Basic Model and Serializer

Let's work with a simple product model:

python
# models.py
from django.db import models

class Category(models.Model):
name = models.CharField(max_length=100)

def __str__(self):
return self.name

class Product(models.Model):
name = models.CharField(max_length=100)
description = models.TextField()
price = models.DecimalField(max_digits=10, decimal_places=2)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='products')
in_stock = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)

def __str__(self):
return self.name

And a corresponding serializer:

python
# serializers.py
from rest_framework import serializers
from .models import Product, Category

class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ['id', 'name', 'description', 'price', 'category', 'in_stock', 'created_at']

Method 1: Using django-filter

The django-filter library provides a powerful way to filter querysets based on URL parameters.

Setting Up django-filter

First, configure Django REST Framework to use django-filter:

python
# settings.py
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
}

Creating a FilterSet

Create a file named filters.py:

python
# filters.py
from django_filters import rest_framework as filters
from .models import Product

class ProductFilter(filters.FilterSet):
min_price = filters.NumberFilter(field_name="price", lookup_expr='gte')
max_price = filters.NumberFilter(field_name="price", lookup_expr='lte')
name = filters.CharFilter(field_name="name", lookup_expr='icontains')

class Meta:
model = Product
fields = ['category', 'in_stock', 'min_price', 'max_price', 'name']

Using the FilterSet in a View

python
# views.py
from rest_framework import viewsets
from .models import Product
from .serializers import ProductSerializer
from .filters import ProductFilter

class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filterset_class = ProductFilter

Example API Calls

With this setup, your API now supports these filter queries:

  1. Get all products in a specific category:

    GET /api/products/?category=1
  2. Get all in-stock products with a price between 10and10 and 50:

    GET /api/products/?in_stock=true&min_price=10&max_price=50
  3. Search products containing "phone" in their name:

    GET /api/products/?name=phone

Method 2: Simple Query Parameter Filtering

For simple filtering needs, you can implement filtering directly in your view:

python
# views.py
from rest_framework import generics
from .models import Product
from .serializers import ProductSerializer

class ProductList(generics.ListAPIView):
serializer_class = ProductSerializer

def get_queryset(self):
queryset = Product.objects.all()
category = self.request.query_params.get('category')
if category:
queryset = queryset.filter(category__id=category)

in_stock = self.request.query_params.get('in_stock')
if in_stock:
queryset = queryset.filter(in_stock=in_stock.lower() == 'true')

return queryset

Method 3: Custom Filter Backend

For reusable filtering logic, you can create custom filter backends:

python
# filters.py
from rest_framework import filters

class PriceRangeFilterBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
min_price = request.query_params.get('min_price')
max_price = request.query_params.get('max_price')

if min_price:
queryset = queryset.filter(price__gte=min_price)
if max_price:
queryset = queryset.filter(price__lte=max_price)

return queryset

Then use it in your view:

python
# views.py
from rest_framework import generics
from .models import Product
from .serializers import ProductSerializer
from .filters import PriceRangeFilterBackend

class ProductList(generics.ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = [PriceRangeFilterBackend]

Advanced Filtering Techniques

Using SearchFilter

Django REST Framework provides a built-in SearchFilter for text searches:

python
# views.py
from rest_framework import generics
from rest_framework import filters
from .models import Product
from .serializers import ProductSerializer

class ProductList(generics.ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = [filters.SearchFilter]
search_fields = ['name', 'description']

This allows queries like:

GET /api/products/?search=wireless

Using OrderingFilter

Similarly, you can add sorting capabilities:

python
# views.py
from rest_framework import generics
from rest_framework import filters
from .models import Product
from .serializers import ProductSerializer

class ProductList(generics.ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = [filters.OrderingFilter]
ordering_fields = ['price', 'created_at']
ordering = ['price'] # default ordering

This allows queries like:

GET /api/products/?ordering=-price  # descending price

Combining Multiple Filter Backends

You can combine multiple filter backends for more complex filtering:

python
# views.py
from rest_framework import viewsets
from rest_framework import filters
from django_filters.rest_framework import DjangoFilterBackend
from .models import Product
from .serializers import ProductSerializer
from .filters import ProductFilter

class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_class = ProductFilter
search_fields = ['name', 'description']
ordering_fields = ['price', 'created_at']

This setup supports filtering, searching, and ordering in a single view.

Real-World Example: E-commerce API

Let's put everything together in a real-world e-commerce API:

python
# filters.py
from django_filters import rest_framework as filters
from .models import Product

class ProductFilter(filters.FilterSet):
min_price = filters.NumberFilter(field_name="price", lookup_expr='gte')
max_price = filters.NumberFilter(field_name="price", lookup_expr='lte')
category_name = filters.CharFilter(field_name="category__name", lookup_expr='iexact')
created_after = filters.DateTimeFilter(field_name="created_at", lookup_expr='gte')

class Meta:
model = Product
fields = ['category', 'in_stock', 'min_price', 'max_price', 'category_name', 'created_after']
python
# views.py
from rest_framework import viewsets
from rest_framework import filters
from django_filters.rest_framework import DjangoFilterBackend
from .models import Product, Category
from .serializers import ProductSerializer, CategorySerializer
from .filters import ProductFilter

class CategoryViewSet(viewsets.ModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer

class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_class = ProductFilter
search_fields = ['name', 'description']
ordering_fields = ['price', 'created_at']
ordering = ['price']

def get_queryset(self):
queryset = super().get_queryset()

# Special featured products logic
featured = self.request.query_params.get('featured')
if featured and featured.lower() == 'true':
# Assume we define featured products as those with specific criteria
queryset = queryset.filter(price__gte=100, in_stock=True)

return queryset
python
# urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ProductViewSet, CategoryViewSet

router = DefaultRouter()
router.register(r'products', ProductViewSet)
router.register(r'categories', CategoryViewSet)

urlpatterns = [
path('api/', include(router.urls)),
]

With this API, clients can make various filtering requests:

GET /api/products/?min_price=20&max_price=50&in_stock=true
GET /api/products/?category=1&search=wireless
GET /api/products/?ordering=-created_at&category_name=electronics
GET /api/products/?featured=true&ordering=price

Performance Considerations

When implementing filtering, keep these performance tips in mind:

  1. Add Database Indexes: Add indexes to fields that are frequently filtered

    python
    class Product(models.Model):
    # ...
    price = models.DecimalField(max_digits=10, decimal_places=2, db_index=True)
    category = models.ForeignKey(Category, on_delete=models.CASCADE, db_index=True)
  2. Use select_related and prefetch_related: For related objects to reduce database queries

    python
    def get_queryset(self):
    return Product.objects.select_related('category').all()
  3. Limit Filtered Fields: Only allow filtering on necessary fields

Summary

In this guide, we've explored different approaches to implement filtering in Django REST Framework:

  1. Using the powerful django-filter library for complex filtering needs
  2. Simple query parameter filtering for basic scenarios
  3. Custom filter backends for reusable filtering logic
  4. Built-in filter backends for search and ordering

Filtering is essential for building performant APIs that allow clients to efficiently retrieve only the data they need. By implementing proper filtering, you can significantly improve your API's usability and performance.

Additional Resources

Exercises

  1. Implement a filter system for a blog API that allows filtering posts by author, publication date range, and tags
  2. Create a custom filter backend that filters products by their discount percentage
  3. Extend the example e-commerce API to add filters for products that have been updated recently or are on sale
  4. Implement a filter that combines multiple fields (e.g., find products in a specific category AND below a certain price)

By mastering filtering techniques in Django REST Framework, you'll be able to create APIs that are more flexible, efficient, and user-friendly.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)