Using mixins to add fluid filters to Django model managers.
There are plenty of samples on the web showing how to create custom Django
QuerySet and Manager classes to create fluid manager interfaces; here's
We have a lot of shared fields across models, that we filter on in similar
ways, irrespective of the model. A good example is
state - we use the django-fsm app, and several of our models have a state attribute that is used to manage workflow. Our codebase is then littered with
.filter(state='xyz') model manager method calls.
In order to consolidate some of these common filters, we use custom mixins that encapsulate the specifics. The key (and the thing you will see in all of the posts on the web - search for 'custom django model manager'), is how to combine the Manager and QuerySet in such a way that they are both chainable. What does this mean?
Django QuerySets are chainable - you can do the following:
>>> objs = MyModel.objects.all().filter(x=y).filter(a=b).exclude(k=j)
If you create a custom QuerySet, you can add your own filters and give them more explicit names (yes, it's a fatuous example, but it's just an example):
>>> objs = MyModel.objects.all().x_is_y().a_is_b()
The problem is that pesky
all() - the filters are on the queryset, not the manager, and so if we want to have the
x_is_y() method available on the
manager, we need to replicate the method on both Manager and QuerySet.
In order to get around this, we have a base mixin class that is used to return a reference to the underlying QuerySet when implemented by either a Manager or a QuerySet:
"Base class used by Filter mixins."
"Returns the underlying queryset from Manager or QuerySet object."
if issubclass(type(self), models.query.QuerySet):
elif issubclass(type(self), models.Manager):
u"Object %s of type <%s> does not contain queryset."
% (self, type(self)))
This mixin is then implemented by the specific filter class, in this case a StateFilterMixin that provides a neater
Mixin for custom QuerySet and Manager classes to filter on state.
This mixin should be used to provide the `in_state` and `in_states`
fluid filters to models that have a state attribute.
def in_state(self, state):
"Filter requests by a state."
def in_states(self, states):
"Filter requests by a list of states."
if states is None or states == :
This StateFilterMixin can be implemented by both Manager and QuerySet classes to add the
in_states methods. The only remaining task is to declare the custom classes, and to bind the Manager to the correct QuerySet:
from django.db import models
class MyModelQuerySet(StateFilterMixin, models.query.QuerySet):
class MyModelManager(StateFilterMixin, models.Manager):
return MyModelQuerySet(self, using=self._db)
state = models.CharField(default='new')
objects = MyModelManager()
We can now use the custom methods without the
>>> models = MyModel.objects.all()
>>> new_models = MyModel.objects.in_state('new')
>>> old_models = MyModel.objects.in_state('old')
We've rolled this across a number of models and it's proving very helpful (assuming you think fluid interfaces are a good thing) - here's a slightly more complex example that we used to filter on the date a model was created, which for various reasons is modelled as either
created_at attributes (implementation details removed for brevity - there's nothing very complicated about them):
Mixin used to filter according to 'added' or 'created_at' attr.
"Filter entities added today."
"Filter entities added yesterday."
"Filter entities added this week."
def added_on_date(self, date):
"Filter entities added on a given date."
def added_after(self, date, inclusive=True):
"Filter entities added since given date."
def added_before(self, date, inclusive=True):
"Filter entities before a given date."
If we now have to filter objects created between two dates in a particular state, we have something that looks like:
>>> matches = (
And we can added these filters to any new (or existing) models that have the requisite attributes in just a few lines of code. Simples.