Hunter Ford Hunter Ford

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

  • xilin.ding

    I have a question, about: class PostMixin(QuerySet): def by_author(self, user): return self.filter(user=user) def published(self): return self.filter(published__lte=datetime.now()) class PostManager(models.Manager, PostMixin): def get_query_set(self): return PostMixin(self.model, using=self._db). this style result is I have some model is OK, but others don't work!
  • Julien

    Great tip thanks
  • alex

    Hi Hunter! I'm kind a new to django and just feeding my self some bunch of django stuff and got in here for manager chaning method. I just wanted to know how this approach works and how can I used it. Thanks in advance!
  • ransom

    much yes. the __getattr__ trick is apparently slow when used with querysets returned by things other than all(). as written, this method has some boilerplate, which i'm currently removing with something like def custom_query_manager(mixin): class CustomManager(models.Manager, mixin): class AvailabilityQuerySet(models.query.QuerySet, mixin): pass def get_query_set(self): return self.AvailabilityQuerySet(self.model, using=self._db) return CustomManager() although that's pretty fragile if you need a custom manager that does more than keep a bunch of queries in one place. one way or another, django ought to have a preferred way to add functionality to all querysets for a certain object, whether by adding decorators to custom model methods or otherwise, this is obv a common problem without a standardized solution.
  • Matt Stevens

    Beautiful, thanks!
  • Diederik van der Boor

    Ohh how nice! Sweet! :-D so easy to implement, and no need for magic like the QuerySetManager, or __getattr__ hack.
  • 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!