Custom QuerySet and Manager without breaking DRY?
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 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)
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 models
from django.db.models.query import QuerySet
class 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 CustomQuerySetManager
from django.db.models.query import QuerySet
class 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 UserManager
class 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.