As you know, model managers can be overriden in Django. It's convenient to add custom filtration method there:
Article.objects.published()
Article.objects.old()
But these custom methods cannot be chained:
Article.objects.published().old()
Overriding manager doesn't allow it, because after first manager method is invoked (published
in this case), we receive queryset as a result, which knows nothing about custom manager methods. So we have to add custom methods to queryset. This can look like this:
class ArticleQuerySet(models.query.QuerySet):
def published(self):
return self.filter("...")
def old(self):
return self.filter("...")
Now we need to make manager user this class. In order to do this, let's override manager's get_query_set
method:
class ArticleManager(models.Manager):
def get_query_set(self):
return ArticleQuerySet(self.model, using=self._db)
class Article(models.Model):
# ...
objects = ArticleManager()
This allows to make queties like:
Article.objects.all().published().old()
Here we invoke all
to get queryset instance, because this time manager doesn't have custom methods - published
and old
. To avoid this, we can make manager look for undefined methods in queryset:
class ArticleManager(models.Manager):
def get_query_set(self):
return ArticleQuerySet(self.model, using=self._db)
def getattr(self, key):
return getattr(self.get_query_set(), key)
Note that getattr
is only invoked when attribute is not found by usual means.
Manager can be further improved to be more generic:
class QuerySetManager(models.Manager):
def init(self, queryset_class, args, **kwargs):
self.queryset_class = queryset_class
super(QuerySetManager, self).init(args, **kwargs)
def get_query_set(self):
return self.queryset_class(self.model, using=self._db)
def getattr(self, key):
return getattr(self.get_query_set(), key)
# Now you can simply write:
# objects = QuerySetManager(ArticleQuerySet)
After article was published, I found out that this idea is not all that new and unique :)
It's funny that only after I implemented my own solution, I've managed to make correct search query.