I'm trying to find a way to implement both a custom QuerySet
and a custom Manager
without breaking DRY. This is what I have so far:
class MyInquiryManager(models.Manager):
def for_user(self, user):
return self.get_query_set().filter(
Q(assigned_to_user=user) |
Q(assigned_to_group__in=user.groups.all())
)
class Inquiry(models.Model):
ts = models.DateTimeField(auto_now_add=True)
status = models.ForeignKey(InquiryStatus)
assigned_to_user = models.ForeignKey(User, blank=True, null=True)
assigned_to_group = models.ForeignKey(Group, blank=True, null=True)
objects = MyInquiryManager()
This works fine, until I do something like this:
inquiries = Inquiry.objects.filter(status=some_status)
my_inquiry_count = inquiries.for_user(request.user).count()
This promptly breaks everything because the QuerySet
doesn't have the same methods as the Manager
. I've tried creating a custom QuerySet
class, and implementing it in MyInquiryManager
, but I end up replicating all of my method definitions.
I also found this snippet which works, but I need to pass in the extra argument to for_user
so it breaks down because it relies heavily on redefining get_query_set
.
Is there a way to do this without redefining all of my methods in both the QuerySet
and the Manager
subclasses?
The Django 1.7 released a new and simple way to create combined queryset and model manager:
See Creating Manager with QuerySet methods for more details.
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 customQuerySet
. Then, to make it work off the manager, you can simply trap the__getattr__
and return it accordinglyTo make this pattern re-usable, I've extracted out the
Manager
bits to a separate model manager:custom_queryset/models.py
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
With this pattern, any of these will work:
UPD if you are using it with custom user(
AbstractUser
), you need to changefrom
to
The following works for me.
This is on the default manager; so I used to do something like:
But there is no reason it should not work for a secondary manager.
You can provide the methods on the manager and queryset using a mixin. See the following technique:
http://hunterford.me/django-custom-model-manager-chaining/
This also avoids the use of a
__getattr__()
approach.A slightly improved version of T. Stone’s approach:
Class decorators make usage as simple as:
Update: support for nonstandard Manager and QuerySet base classes, e. g. @objects_extra(django.contrib.gis.db.models.GeoManager, django.contrib.gis.db.models.query.GeoQuerySet):