Supercharge your DRF APIs with Django Filters

Nikhil Maan

Software Developer, Essentia.dev

2023-10-06

Why Filtering is Essential

Filtering is a fundamental aspect of building Web APIs. It allows users to retrieve specific data from a large dataset. Effective filtering enables users to extract meaningful information and reduce data transfer overhead.

What is Django Filters?

Django Filters is a third-party library that provides a good solution for data filtering for drf APIs. It simplifies the creation of complex filters and improves the querying capabilities of your API.

How it works

Django Filters provides a declarative way to define filters. It allows you to define filters using a simple syntax and gives you a lot of options to customize the filters to your need. Django Filters also provides a set of built-in filters that can be used to filter data.

How to use it

Django Filters can be installed using pip:

pip install django-filter

Once installed, you can add it to your INSTALLED_APPS:

INSTALLED_APPS = [
    ...
    'django_filters',
]

Creating a FilterSet

A FilterSet is a class that defines a set of filters. It can be used to filter data in a queryset. Here is a simple example of a using django filters to filter add a filter to your API.

Assuming the Product model is as follows:

class Product(models.Model):
    name = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    ...

A simple FilterSet for the Product model will be as follows:

import django_filters

class ProductFilter(django_filters.FilterSet):
    name = django_filters.CharFilter(lookup_expr='icontains')
    price__gte = django_filters.NumberFilter(field_name='price', lookup_expr='gte')
    category = django_filters.ChoiceFilter(choices=Category.objects.all())

    class Meta:
        model = Product
        fields = ['name', 'price__gte', 'category']

The above code defines a FilterSet with three filters:

- name: A CharFilter that filters products by name. It uses the icontains lookup expression to perform a case-insensitive search. - price__gte: A NumberFilter that filters products by price. It uses the gte lookup expression to filter products with a price greater than or equal to the specified value. - category: A ChoiceFilter that filters products by category. It uses the choices argument to specify the available choices.

You can read more about all the available filters in detail at Django-filters Documentation.

#### Using a FilterSet

Once a FilterSet is defined, it can be added to a viewset for the corresponding model. The viewset will use the FilterSet to filter data in the queryset.

from django_filters import rest_framework as filters

class ProductViewSet(viewsets.ModelViewSet):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    filter_backends = [filters.DjangoFilterBackend]
    filterset_class = ProductFilter

#### Custom Filters

Django Filters provides a set of built-in filters that can be used to filter data. However, you can also define custom filters to filter data in a queryset, and define custom methods for filters to handle complex filtering logic.

class ProductFilter(django_filters.FilterSet):
    name = django_filters.CharFilter(lookup_expr='icontains')
    price__gte = django_filters.NumberFilter(field_name='price', lookup_expr='gte')
    category = django_filters.CharFilter(method='filter_category')

    class Meta:
        model = Product
        fields = ['name', 'price__gte', 'category']

    def filter_category(self, queryset, name, value):
        return queryset.filter(category__name=value)

## Performance Considerations

Django Filters provides a powerful API for filtering data. However, it is important to consider the performance implications of using filters. In general, filters should be used sparingly, and only when necessary. Filters should also be used in conjunction with other techniques to improve performance, such as caching and pagination.

## Filter Options

You can use your filtered queryset to generate the latest set of options for your filter. This is useful if you want to display a dynamic list of options for your filter to the user.

def get_filter_options(self, queryset, name):
    queryset = self.filter_queryset(self.get_queryset())
    return Response(
      {
        'names': queryset.values_list('name', flat=True).distinct(),
        'categories': queryset.values_list('category__name', flat=True).distinct(),
      }
    )