开发者

Order by dig-like rating in django

I've got two models:

class Post(models.Model):
    #some fields
    pass

class Vote(models.Model):
    post = mdoels.ForeignKe开发者_C百科y(Post)
    value = models.IntegerField()

Votes' value can be either 1 or -1 (user can vote up or down).

How can i get a list of posts, ordered by their rating?


You have to use annotation. https://docs.djangoproject.com/en/dev/topics/db/aggregation/

class Post(models.Model):         
    name = models.CharField(max_length=20)

class Vote(models.Model):
    post = models.ForeignKey(Post)
    value = models.IntegerField()
    type = models.CharField(max_length=2, choices=(("AW", "Awesomeness"), ("US", "Usefulness")))

Then you need to import Sum and can get a list of posts ordered by their vote_total like:

from django.db.models import Sum
Post.objects.annotate(vote_total=Sum('vote__value')).order_by('-vote_total')

EDIT:
Here's an example. Create several post objects and Vote Objects

Post.objects.get_or_create(id=1, name="post1")
Post.objects.get_or_create(id=2, name="post2")
Post.objects.get_or_create(id=3, name="post3")
Post.objects.get_or_create(id=4, name="post4")
Vote.objects.get_or_create(id=1, post_id=2, value=1, type="AW")
Vote.objects.get_or_create(id=2, post_id=2, value=1, type="AW")
Vote.objects.get_or_create(id=3, post_id=2, value=1, type="US")
Vote.objects.get_or_create(id=4, post_id=2, value=1, type="US")
Vote.objects.get_or_create(id=5, post_id=3, value=-1, type="AW")
Vote.objects.get_or_create(id=6, post_id=3, value=-1, type="AW")
Vote.objects.get_or_create(id=7, post_id=4, value=-1, type="AW")

Then posts = Post.objects.annotate(vote_total=Sum('vote__value')).order_by('-vote_total') will lead to [(post.name, post.vote_total) for post in posts] being.

[(u'post2', 4), (u'post4', -1), (u'post3', -2), (u'post1', None)]

This has an issue as things with no posts go at the very end. As django's Sum aggregation function takes the sum of no entries as None, not 0. This could be solved if you initially gave every post a vote with value 0 (or value 1 like reddit's system), you wouldn't get the None: e.g.:

for p in Post.objects.all():
    Vote.objects.get_or_create(post_id=p.id, value=0)

Then you'll get

>>> [(p.name, p.vote_total) for p in
     Post.objects.annotate(vote_total=Sum('vote__value')).order_by('-vote_total')]
[(u'post2', 4), (u'post1', 0), (u'post4', -1), (u'post3', -2)]

EDIT: Added type to the Vote model (done above), per comment about different vote types. You should be able to do something like (replace 'yourappname' with the name of your application to get the right db table):

select_dict = dict(vote_total = "SELECT SUM(value) FROM yourappname_vote WHERE yourappname_vote.post_id = yourappname_post.id",
                   awesome_total = "SELECT SUM(value) FROM yourappname_vote WHERE yourappname_vote.post_id = yourappname_post.id AND yourappname_vote.type = 'AW' ",
                   useful_total = "SELECT SUM(value) FROM yourappname_vote WHERE yourappname_vote.post_id = yourappname_post.id AND yourappname_vote.type = 'US' ",)

posts = Post.objects.all().extra(select = select_dict).order_by('-vote_total')

>>> [(p.name, p.vote_total, p.awesome_total, p.useful_total) for p in posts]
[(u'post2', 4, 2, 2),
 (u'post1', 0, 0, 0),
 (u'post4', -1, -1, 0),
 (u'post3', -2, -2, 0)]

Now each post now will have a vote_total, as well awesome_total and useful_total in one query to the database. A bit uglier than the ORM, but still quite readable (and this is how the Sum aggregation works. ). You still will have to give each category of votes an initial vote to get past the None appearing out of order.


I think this should work:

Vote.objects.values_list('post', flat=True).order_by('value')

To invert the order:

Vote.objects.values_list('post', flat=True).order_by('-value')

UPDATE

Since post is a ForeignKey to Post, let's assume your model is something like this:

class Post(models.Model):
    post_id = models.AutoField(primary_key=True)
    post_name = models.CharField(max_length=30, unique=True)

So this should work, it would return the names:

 Vote.objects.values_list('post__post_name', flat=True).order_by('value')

note_

If you want to make queries with the list and some error appear, just transform it to a list:

list(Vote.objects.values_list('post__post_name', flat=True).order_by('value')))
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜