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')))
精彩评论