Question

I made a custom manager that has to randomize my query:

class RandomManager(models.Manager):

    def randomize(self):        
        count = self.aggregate(count=Count('id'))['count']
        random_index = random.randint(0, count - 1)
        return self.all()[random_index]

When I use the method defined in my manager in the first place, it's works ok:

>>> PostPages.random_objects.randomize()
>>> <PostPages: post 3>

I need to randomize the already filtered query. When I tried to use the manager and the method in chain I got an error:

PostPages.random_objects.filter(image_gallary__isnull=False).randomize()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/home/i159/workspace/shivaroot/shivablog/<ipython-input-9-98f654c77896> in <module>()
----> 1 PostPages.random_objects.filter(image_gallary__isnull=False).randomize()

AttributeError: 'QuerySet' object has no attribute 'randomize'

Result of filtering is not an instance of model class, but it's django.db.models.query.QuerySet, so that it does not have my manager and method, respectively. Is there a way to use custom manager in chain query?

Was it helpful?

Solution

Looks like this snippet provides a solution to your situation: Custom managers with chainable filters.

OTHER TIPS

This is how you chain custom methods on custom manager ie: Post.objects.by_author(user=request.user).published()

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)

Link here : django-custom-model-manager-chaining

Note :

In Django 1.7 you have this out of the box . Check out QuerySet.as_manager

Just a code example using the new as_manager() method (see update information from @zzart.

class MyQuerySet(models.query.QuerySet):
    def randomize(self):        
        count = self.aggregate(count=Count('id'))['count']
        random_index = random.randint(0, count - 1)
        return self.all()[random_index]

class MyModel(models.Model):
    .....
    .....
    objects = MyQuerySet.as_manager()
    .....
    .....

And then you will be able to use something like this in your code:

MyModel.objects.filter(age__gt=16).randomize()

As you can see, the new as_manager() is really neat:)

Given that you have an existing models.Manager and you don't want to expose some of the manager method to a chainable queryset, you can use Manager.from_queryset(QuerySet)().

So, you could still place all your chainable queryset method inside the QuerySet and your manager method independently.

Example given in the official site.

Snippet from Django Docs

class BaseManager(models.Manager):
    # Available only on Manager.
    def manager_only_method(self):
        return

class CustomQuerySet(models.QuerySet):
    # Available on both Manager and QuerySet.
    def manager_and_queryset_method(self):
        return

    # Available only on QuerySet.
    def _private_method(self):
        return

CustomManager = BaseManager.from_queryset(CustomQuerySet)

class MyModel(models.Model):
    objects = CustomManager()

How about something like below which creates the custom QuerySet dynamically and allows us to 'transplant' our custom queries onto the returned QuerySet instance:

class OfferManager(models.Manager):
    """
    Additional methods / constants to Offer's objects manager
    """
    ### Model (db table) wide constants - we put these and 
    ### not in model definition to avoid circular imports.
    ### One can access these constants through like
    <foo>.objects.STATUS_DISABLED or ImageManager.STATUS_DISABLED

    STATUS_DISABLED = 0
    ...
    STATUS_CHOICES = (
        (STATUS_DISABLED, "Disabled"),
        (STATUS_ENABLED, "Enabled"),
        (STATUS_NEGOTIATED, "Negotiated"),
        (STATUS_ARCHIVED, "Archived"),
    )
    ...

    # we keep status and filters naming a little different as
    # it is not one-to-one mapping in all situations
    QUERYSET_PUBLIC_KWARGS = {'status__gte': STATUS_ENABLED}
    QUERYSET_ACTIVE_KWARGS = {'status': STATUS_ENABLED}

    def get_query_set(self):
        """ our customized method which transpalats manager methods
        as per get_query_set.<method_name> = <method> definitions """
        CustomizedQuerySet = QuerySet
        for name, function in self.get_query_set.__dict__.items():
            setattr(CustomizedQuerySet, name, function)
        return CustomizedQuerySet(self.model, using=self._db)

    def public(self):
        """ Returns all entries accessible through front end site"""
        return self.all().filter(**OfferManager.QUERYSET_PUBLIC_KWARGS)
    get_query_set.public = public # will tranplat the function onto the 
                                  # returned QuerySet instance which 
                                  # means 'self' changes depending on context.

    def active(self):
        """ returns offers that are open to negotiation """
        return self.public().filter(**OfferManager.QUERYSET_ACTIVE_KWARGS)
    get_query_set.active = active
    ...

More polished version of this method and django ticket here: https://code.djangoproject.com/ticket/20625.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top