Custom managers with chainable filters

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from django.db import models

class NewsQuerySet(models.query.QuerySet):
    def live(self):
        return self.filter(state='published')

    def interesting(self):
        return self.filter(interesting=True)

class NewsManager(models.Manager):
    def get_query_set(self): 
        model = models.get_model('news', 'NewsItem')
        return NewsQuerySet(model)

    def __getattr__(self, attr, *args):
        try:
            return getattr(self.__class__, attr, *args)
        except AttributeError:
            return getattr(self.get_query_set(), attr, *args)

Comments

ep (on January 26, 2008):

Thanks, looks excellent!

I think you can even avoid repeating the Manager code for each of your classes by using something like:

class QuerySetManager(models.Manager):
    def __init__(self, qs_class):
        self.queryset_class = qs_class
    def get_query_set(self):
        return self.queryset_class(self.model)
    def __getattr__(self, attr, *args):
        try:
            return getattr(self.__class__, attr, *args)
        except AttributeError:
            return getattr(self.get_query_set(), attr, *args)

and then setting the objects attribute like this:

class NewsItem(models.Model):
    objects = QuerySetManager(NewsQuerySet)

#

itavor (on February 1, 2008):

Thanks, ep! I like your improvement. Will do it that way in my own code from now on.

#

herion (on April 20, 2008):

I tried using this. In ep's approach Manager's init is overriden and it expects an additional argument compared to Django core's Manager. However, in Django code Manager's are called without arguments. So when using object.delete() and object.someothermodel_set.* methods the system tries to call the Manager without arguments and it throws an error. I don't know however, why Django is calling Manager via the Manager model and not thourgh models Manager instance (so it doesn't help if you make argument optional as class can't find the queryset then).. I found ep's approach much too hazard although you could hack it here and there..

#

whiteinge (on July 13, 2008):

herion, just use a default for the qs_class:

def __init__(self, qs_class=models.query.QuerySet):
    self.queryset_class = qs_class

#

(Forgotten your password?)

You may use Markdown syntax here, but raw HTML will be removed.