Django Custom Model Manager Chaining
Let's say you have a custom model manager like this:
from datetime import datetime
from django.db import models
class PostManager(models.Manager):
def by_author(self, user):
return self.filter(user=user)
def published(self):
return self.filter(published__lte=datetime.now())
class Post(models.Model):
user = models.ForeignKey(User)
published = models.DateTimeField()
objects = PostManager()
But let's say you are want to filter those results that are both published and by a certain author. There is no built-in mechanism that will allow you to chain the two together.
So for example, the following will fail since by_author returns a QuerySet:
Post.objects.by_author(user=request.user).published()
There are a few discussions on the matter:
- Django Manager Chaining
- jQuery style chaining with the Django ORM
- Extending Django Models, Managers, And QuerySets
But using Python mixins I've developed what I think to be a better way:
from django.db.models.query import QuerySet
class PostMixin(object):
def by_author(self, user):
return self.filter(user=user)
def published(self):
return self.filter(published__lte=datetime.now())
class PostQuerySet(QuerySet, PostMixin):
pass
class PostManager(models.Manager, PostMixin):
def get_query_set(self):
return PostQuerySet(self.model, using=self._db)
It keeps with DRY principles and works just like you expect Django's ORM to work.
Comments
Aidas Bendoraitis
Nice approach. I would just call the mixin as "PostQuerySetMixin" or "PostManagerMixin" and leave the name "PostMixin" for possible model mixin.
Jim Dalton
Hey Hunter, I just saw this. I actually had a waaay more complicated implementation of something similar which I wrote up last year: http://furrybrains.com/2009/06/22/named-scopes-for-django/ The way you've done it just seem so, well, easy. Since QuerySet and Manager both implement the same set of methods, all those self calls will just work... Very nice!