Custom QuerySet and Manager without breaking DRY? Custom QuerySet and Manager without breaking DRY? django django

Custom QuerySet and Manager without breaking DRY?


Django has changed! Before using the code in this answer, which was written in 2009, be sure to check out the rest of the answers and the Django documentation to see if there is a more appropriate solution.


The way I've implemented this is by adding the actual get_active_for_account as a method of a custom QuerySet. Then, to make it work off the manager, you can simply trap the __getattr__ and return it accordingly

To make this pattern re-usable, I've extracted out the Manager bits to a separate model manager:

custom_queryset/models.py

from django.db import modelsfrom django.db.models.query import QuerySetclass CustomQuerySetManager(models.Manager):    """A re-usable Manager to access a custom QuerySet"""    def __getattr__(self, attr, *args):        try:            return getattr(self.__class__, attr, *args)        except AttributeError:            # don't delegate internal methods to the queryset            if attr.startswith('__') and attr.endswith('__'):                raise            return getattr(self.get_query_set(), attr, *args)    def get_query_set(self):        return self.model.QuerySet(self.model, using=self._db)

Once you've got that, on your models all you need to do is define a QuerySet as a custom inner class and set the manager to your custom manager:

your_app/models.py

from custom_queryset.models import CustomQuerySetManagerfrom django.db.models.query import QuerySetclass Inquiry(models.Model):    objects = CustomQuerySetManager()    class QuerySet(QuerySet):        def active_for_account(self, account, *args, **kwargs):            return self.filter(account=account, deleted=False, *args, **kwargs)

With this pattern, any of these will work:

>>> Inquiry.objects.active_for_account(user)>>> Inquiry.objects.all().active_for_account(user)>>> Inquiry.objects.filter(first_name='John').active_for_account(user)

UPD if you are using it with custom user(AbstractUser), you need to change
from

class CustomQuerySetManager(models.Manager):

to

from django.contrib.auth.models import UserManagerclass CustomQuerySetManager(UserManager):    ***


The Django 1.7 released a new and simple way to create combined queryset and model manager:

class InquiryQuerySet(models.QuerySet):    def for_user(self, user):        return self.filter(            Q(assigned_to_user=user) |            Q(assigned_to_group__in=user.groups.all())        )class Inquiry(models.Model):    objects = InqueryQuerySet.as_manager()

See Creating Manager with QuerySet methods for more details.


You can provide the methods on the manager and queryset using a mixin.

This also avoids the use of a __getattr__() approach.

from django.db.models.query import QuerySetclass 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):    passclass PostManager(models.Manager, PostMixin):    def get_query_set(self):        return PostQuerySet(self.model, using=self._db)