← All posts

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

Django logo

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 →