Hunter Ford

Senior Systems/Software Engineer

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:

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!