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:
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.
xilin.dingI 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!
alexHi 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!
ransommuch 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.
Jim DaltonHey 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!