Stockton Web & Cloud Services Company Articles Get Started 801-360-8331
We are an 🇺🇸American🇺🇸 small business! Help us grow! Share with a friend. We have fast response times and very reasonable prices.
Vultr Cloud for your next cloud project!
Filtering in DRF

Filtering in DRF

If you haven't using the django-filter package with the Django REST Framework (DRF), then it's time you give it a whirl!

When I saw how django-filter relates to DRF, I was thrilled. I feel like this method I'm about to show you is somewhat of a life-hack. I realize that not every situation is going to be the right situation for this, but in general I think you get a maintainable codebase by using django-filter instead of manually checking and testing request parameters.

Here is a quick example. First off, create a new directory and django project:

$ mkdir filtering
$ cd filtering
$ python3 -m venv env
$ . env/bin/activate
$ pip install "Django~=5.2" djangorestframework django-filter

Then we'll create a project and add a simple app:

$ django-admin startproject filtering .
$ ./manage.py startapp api

Next, tell Django that you're going to use DRF and filters. In the settings.py file, under INSTALLED_APPS, add these:

    ...
    'rest_framework',
    'django_filters',
    'api',
    ...

Make sure everything runs at this point:

$ ./manage.py runserver

Default Filtering

I want to apply filtering to all my viewsets, instead of one at a time. To do that, configure this setting in the settings.py file:

REST_FRAMEWORK = {
    "DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"],
}

Model & ViewSet

Lets add a model that we can filter on. In the api/models.py file:

from django.db import models
from django.utils.translation import gettext_lazy as _


class Taco(models.Model):
    name = models.CharField(max_length=15)

    TORTILLA_CORN = "corn"
    TORTILLA_FLOUR = "flour"
    TORTILLA_CHOICES = (
        (TORTILLA_CORN, _("Corn Tortilla")),
        (TORTILLA_FLOUR, _("Flour Tortilla")),
    )
    tortilla = models.CharField(
        max_length=5,
        choices=TORTILLA_CHOICES,
        default=TORTILLA_FLOUR,
    )

Then make the migration for that, and migrate:

$ ./manage.py makemigrations
$ ./manage.py migrate

Then let's add some data:

$ ./manage.py shell
>>> from api.models import Taco
>>> Taco.objects.create(name="Cheese", tortilla=Taco.TORTILLA_FLOUR)
>>> Taco.objects.create(name="Frijole", tortilla=Taco.TORTILLA_CORN)

In the api/serializers.py file:

from rest_framework import serializers
from .models import Taco


class TacoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Taco
        read_only_fields = ()
        exclude = ()

Then in the api/views.py file:

from .serializers import TacoSerializer
from .models import Taco
from rest_framework import viewsets


class TacoViewSet(viewsets.ModelViewSet):
    queryset = Taco.objects.all()
    serializer_class = TacoSerializer

Lastly, we need a URL route. In the filtering/urls.py file:

from django.contrib import admin
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from api.views import TacoViewSet

router = DefaultRouter()
router.register(r'tacos', TacoViewSet)


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

Now run the project, and check the tacos!

./manage.py runserver

Link -> http://localhost:8000/api/tacos/

But.... there is no filtering on this yet. The next steps are easy. First, lets create a filter for the tacos, using the api/filters.py file:

import django_filters
from .models import Taco


class TacoFilter(django_filters.FilterSet):
    class Meta:
        model = Taco
        fields = {
            "name": ["icontains"],
            "tortilla": ["exact"],
        }

Then we just simply add this filter into the viewset, api/views.py:

...
class TacoViewSet(viewsets.ModelViewSet):
    queryset = Taco.objects.all()
    serializer_class = TacoSerializer
    filterset_class = TacoFilter

Now if you run the dev server, and visit the API endpoint, you should notice a button that says "Filter," allowing you to easily filter the results.

Conclusion

I like this method for adding filtering to API endpoints, because it provides a simple, repeatable, programmatic way, to add filtering to an endpoint. You don't need to extract the query parameters and add a special and unique test for each one. Instead, you can tie the model directly to the filter.

Share X.com Truth Social

Written by Jon

Author Profile Picture

Hi, I'm Jon. I live in Utah with my awesome wife and children, where we enjoy hockey, basketball, soccer, and raising chickens! I have a bachelors degree in Software Development, various computer & project management certifications, and I've worked for web hosting and other dev/online companies for over a decade.