Managers and Voting and Subqueries, Oh My!

Recently I launched Peevalizer, a website for talking about your pet peeves, which of course was written in Python using the Django web framework. In fact, it was the culmination of my efforts to teach myself design, and while I made some progress, it's clear that I'll never be a designer. Anyway, part of Peevalizer is that users can vote on different pet peeves, and view the peeves with the highest score. I used django-voting as the application to enable this functionality, and it provides a manager on the Vote object with methods for getting the top N results, where N is a positive integer.

One of the reasons for custom manager on Vote is because aggregate support has not yet been finished. However with Django's built-in Pagination support, it's necessary to retrieve not only a list of the top N voted pet peeves, but a list of all of the pet peeves, ordered by score. How is this possible? Specifically, how is this possible without forking django-voting? Here is the solution that I came up with:

class VoteAwareManager(models.Manager):
    def _get_score_annotation(self):
        model_type = ContentType.objects.get_for_model(self.model)
        table_name = self.model._meta.db_table
        return self.extra(select={
            'score': 'SELECT COALESCE(SUM(vote),0) FROM %s WHERE content_type_id=%d AND object_id=%s.id' %
                (Vote._meta.db_table, int(model_type.id), table_name)}
        )

    def most_hated(self):
        return self._get_score_annotation().order_by('-score')

    def most_loved(self):
        return self._get_score_annotation().order_by('score')

Then I assigned that manager onto all of the objects that could be voted on. What that's doing is literally issuing a subquery for every row, doing an aggregate on all of the votes for that row, and assigning it to an attribute named score.

However, we also wanted to allow for voting on User objects, which is built in to Django and cannot be easily changed. How do we add this manager to user? I spent a while thinking about that before realizing that it's not the right question to ask. The right question to ask is, how can we associate the User model with this manager? A quick look through some Django source code revealed this to be an absolutely trivial task. Here's how it goes in our code:

from django.contrib.auth.models import User
manager = VoteAwareManager()
manager.model = User

for user in manager.most_hated():
    # Do something with user's score

There are a few things to note about this implementation. Firstly, it can be much more computationally expensive to use this method instead of using django-voting's method (which executes some custom SQL), so either be aware of that or use aggressive caching strategies to overcome this shortcoming. The other thing is if you're not using a manager like this on multiple models, and since managers mostly just proxy to QuerySet anyway, it might be simpler to just acquire a QuerySet on the model that you would like to get, and run the extra() method in the calling function.

14 Comments So Far...

By Justin at 8:13 p.m. on May 24, 2008

Hey Eric, thanks for the tips on voting. We just launched <a href="http://www.myyardourmessage.com/">My Yard Our Message</a>, also built on django and will soon incorporate voting (though not there yet). I've been planning on django voting as well, though was kind of wondering how getting a score out of it would work. This is helpful.

Btw.. on design stuff as it pertains to Peevalizer, more line-spacing, more padding, and justify the sidebar to the left. Right justified text doesn't make visual sense on the right side of the page.

 

By Dev Mem at 9:11 a.m. on May 27, 2008

Cool.

BTW, how do you colorize your HTML Python output?

 

By jaren at 4:13 a.m. on June 23, 2008

PzcfTX dfv078fnw8f934ndvkg2l

 

By ben 10 oyunları at 3:09 a.m. on May 25, 2009

I've been planning on django voting as well, though was kind of wondering how getting a score out of it would work. This is helpful.

 

By Halloween Costumes at 11:12 a.m. on June 4, 2009

the above recipe assumes there will be no captures in the patterns in your and that any flags to be used are embedded in the patterns. I don't think that's a problem in practice, though.

 

By Batones tony at 1:15 p.m. on June 8, 2009

ya, Btw.. on design stuff as it pertains to Peevalizer, more line-spacing, more padding, and justify the sidebar to the left. Right justified text doesn't make visual sense on the right side of the page.

 

By wow gold at 4:52 a.m. on June 17, 2009

ooops, a good post offering us lots of useful info, with to read more about such articles, thanks.

 

By jordan shoes at 2:44 a.m. on June 25, 2009

I've been planning on django voting as well, though was kind of wondering how getting a score out of it would work. This is helpful.

 

By jordan shoes at 2:45 a.m. on June 25, 2009

Cool.

BTW, how do you colorize your HTML Python output?

 

By ugg boots at 2:46 a.m. on June 25, 2009

Btw.. on design stuff as it pertains to Peevalizer, more line-spacing, more padding, and justify the sidebar to the left. Right justified text doesn't make visual sense on the right side of the page.

 

By nike shoes at 2:47 a.m. on June 25, 2009

ya, Btw.. on design stuff as it pertains to Peevalizer, more line-spacing, more padding, and justify the sidebar to the left. Right justified text doesn't make visual sense on the right side of the page.

 

By tiffany jewellery at 2:48 a.m. on June 25, 2009

the above recipe assumes there will be no captures in the patterns in your and that any flags to be used are embedded in the patterns. I don't think that's a problem in practice, though.

 

By lingerie wholesale at 9:59 a.m. on June 28, 2009

I think you will make these projects into a success also!

 

By Sun at 1:30 p.m. on July 2, 2009

Just want to say i`m glad i found this site.
I am from Republic and too poorly know English, give true I wrote the following sentence: "Here are some safe and natural ways to get rid of fleas on your pets."

;) Thanks in advance. Sun.

 

Voice your opinion...