Django. A Custom QuerySet and Manager for Adding Extra Methods to a Call Chain

Sometimes you need to chain custom QuerySet methods. Defining them only on the model manager is not enough: the manager is the entry point to the QuerySet, but it is not the object returned by each query method.
An example of using a model manager without chaining calls:
from django.db import models
class CustomManager(models.Manager):
def custom_filter(self, **kwargs):
kwargs['status'] = 'published'
return super(CustomManager, self).get_query_set().filter(**kwargs)
def custom_order_by(self, *args):
args = ('published', ) + args
return super(CustomManager, self).get_query_set().order_by(*args)
class MyModel(models.Model):
...
custom_manager = CustomManager()
Then you call it like this:
MyModel.custom_manager.custom_filter(something=42)
# или так
MyModel.custom_manager.custom_order_by('some_field')
Chaining these methods will not work: custom_filter returns a models.query.QuerySet, which does not expose the custom methods defined on the manager:
# Так уже сделать не получится:
MyModel.custom_manager.custom_filter(something=42).custom_order_by('some_field')
Define the custom methods on a QuerySet subclass instead. Then define get_query_set on the manager so that it returns an instance of CustomQuerySet:
from django.db import models
class CustomQuerySet(models.query.QuerySet):
"""Substitution the QuerySet, and adding additional methods to QuerySet
"""
def custom_filter(self, **kwargs):
kwargs['status'] = "published"
return self.filter(**kwargs)
def custom_order_by(self, *args):
args = ('published', ) + args
return self.order_by(*args)
class CustomManager(models.Manager):
def get_query_set(self):
model = models.get_model(self.model._meta.app_label, self.model._meta.object_name)
return CustomQuerySet(model)
def __getattr__(self, attr, *args):
try:
return getattr(self.__class__, attr, *args)
except AttributeError:
return getattr(self.get_query_set(), attr, *args)
In your project code, simply use CustomManager or inherit your managers from it to add your extra methods from CustomQuerySet:
class MyModel(models.Model):
...
custom_manager = CustomManager()
MyModel.custom_manager.custom_filter(something=42).custom_order_by('some_field')
This is how I’ve implemented search in Marcus, for example. The search itself is only temporary for now (using “LIKE”); later, I want to make it possible to pass in custom search methods, such as “MATCH/AGAINST” or “SphinxSearch”.
Inspired by http://djangosnippets.org/snippets/562/
38177714.9958222.1347833143.c8f1dc4a1b410c2ad13b0ef9e4f3d2f4
Comments are closed. Read the original discussion in Russian →