Reusable Filtering for Django & DRF
A generalised approach to multi-tenant systems
Jonathan Moss
The Requirement
A common requirement when building Django applications is to be able to filter data on a per user basis.
For example:
The Problem
The Plan
What I’m going to show you is one approach to solving this requirement in a generic, reusable way
Filtering QuerySet Mixin
from django.core.exceptions import ImproperlyConfigured���class UserRelatedQuerySetMixin(object):� user_filter_key = None�� def for_user(self, user):� if not self.user_filter_key:� raise ImproperlyConfigured(� "A user_filter_key is required"
)� kwargs = {self.user_filter_key: user}� try:� return self.filter(**kwargs)� except AttributeError:� return self.none()
Creating a Custom QuerySet
from django.db.models.query import QuerySet��from .mixins import UserRelatedQuerySetMixin��class CustomerQuerySet(UserRelatedQuerySetMixin, QuerySet):� user_filter_key = 'owner'
Using the Custom QuerySet
from django.db import models�from .querysets import CustomerQuerySet��class Customer(models.Model):�� name = models.CharField(max_length=128, blank=True)� ...�� objects = CustomerQuerySet.as_manager()
Reusable View Mixin
class FilterByUserMixin(object):�� def get_queryset(self):� qs = super().get_queryset()� return qs.for_user(user=self.request.user)
* Just make sure it calls super first!
Using our Mixin in a DRF View
from rest_framework import viewsets��from .mixins import FilterByUserMixin�from .models import Customer�from .serializers import CustomerSerializer��class CustomerViewSet(FilterByUserMixin, viewsets.ModelViewSet):� model = Customer� serializer_class = CustomerSerializer
A Simple Practical Example
A slide deck is not the ideal place to explain these concepts.
As such I have put together a simple example project to illustrate this approach.
You can find the project at:
https://github.com/commoncode/filtering-example
Simplifying
class FilterByUserMixin(object):� user_filter_key = 'user'�� def get_queryset(self):� qs = super().get_queryset()� if not self.user_filter_key:� raise ImproperlyConfigured(
"A user_filter_key is required"
)� kwargs = {self.user_filter_key: self.request.user}� try:� return qs.filter(**kwargs)� except AttributeError:� return qs.none()
If your views are all that matters to you then you can always use a simpler approach:
The Summary
I hope you can see from this quick overview of the approach that it provides a great deal of flexibility inside and out of Django views.
I also hope you see that by using small, discrete mixins, that isolated unit testing of the code is also made fairly straightforward.
* If you are not a fan of mixins, sub-classing can be used to elicit the same effect
Questions?
Keep in Contact