【问题标题】:Order by dig-like rating in django在 django 中按 dig-like 评级排序
【发布时间】:2011-06-06 17:15:05
【问题描述】:

我有两个模型:

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

class Vote(models.Model):
    post = mdoels.ForeignKey(Post)
    value = models.IntegerField()

Votes 的值可以是 1 或 -1(用户可以投票赞成或反对)。

我如何获得按评分排序的帖子列表?

【问题讨论】:

    标签: python django rating-system


    【解决方案1】:

    你必须使用注解。 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")))
    

    然后你需要导入Sum,并可以得到一个按vote_total排序的帖子列表,比如:

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

    编辑:
    这是一个例子。创建多个帖子对象和投票对象

    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")
    

    那么posts = Post.objects.annotate(vote_total=Sum('vote__value')).order_by('-vote_total')会导致[(post.name, post.vote_total) for post in posts]存在。

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

    这有一个问题,因为最后没有帖子。由于 django 的 Sum 聚合函数将没有条目的总和视为 None,而不是 0。如果您最初给每个帖子投票,值为 0(或像 reddit 的系统一样为值 1),这可以解决,您不会得到 None :例如:

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

    然后你会得到

    >>> [(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)]
    

    编辑:根据关于不同投票类型的评论,为投票模型添加了类型(在上面完成)。 您应该能够执行以下操作(将“yourappname”替换为您的应用程序的名称以获得正确的数据库表):

    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)]
    

    现在每个帖子都将在对数据库的一次查询中包含vote_totalawesome_totaluseful_total。比 ORM 有点丑,但仍然很可读(这就是 Sum 聚合的工作原理。)。您仍然必须对每个类别的投票进行初始投票,以通过出现乱序的“无”。

    【讨论】:

    • 哇,谢谢这是一个很好的解决方案 - 我应该感到羞耻,我不知道 Sum 聚合函数存在。顺便说一句,有没有过滤总票数?我的意思是,在实际项目中,我有不同类型的投票(例如,“真棒”和“有用”),我能否以某种方式过滤仅按某种投票类型汇总排序的相关投票?
    • @DataGreed:好的,这是一个使用额外子句中的 SQL SELECT 语句仅过滤一种投票类型的示例。
    • 非常感谢,你是我的救命恩人 :) 我想,我应该学习 SQL,因为 ORM 在某些情况下是不够的。
    • @DataGreed:很高兴您的帮助得到了赞赏。这是 django 中非常罕见的情况,它有助于了解一些 SQL 以超越 ORM(并且 SQL 相当简单和可读)。它在 django 文档中也有很好的记录,请参见第二个示例(唯一的区别是它们使用 COUNT 而不是 SUM 作为聚合,并且在 where 子句中没有 AND):docs.djangoproject.com/en/1.2/ref/models/querysets/#extra
    • 仅供参考,关于Sum() 的默认值,有一个开放的Django ticket
    【解决方案2】:

    我认为这应该可行:

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

    颠倒顺序:

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

    更新

    由于帖子是ForeignKeyPost,假设您的模型是这样的:

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

    所以这应该可行,它会返回名称:

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

    注意_

    如果要对列表进行查询并出现错误,只需将其转换为列表即可:

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

    【讨论】:

    • 嗯,返回一些奇怪的东西,比如 3, 1, 1, 1, 1, 3, 3, 1, 3, 3, 1, 3, 3, 3, 3, 3, 3, 3 , 1, 3, '...(剩余元素被截断)...']
    • 我认为你误解了这个问题。您只是订购选票列表并说出该名称的投票。 DataGreed 想要的系统是按投票总和的顺序列出帖子(如我的回答所示)。
    • 显然我不明白这一点。但是如果你已经提供了一个很好的答案:) 和一个很好的答案,很好的解释!
    猜你喜欢
    • 1970-01-01
    • 2015-10-15
    • 1970-01-01
    • 1970-01-01
    • 2011-04-06
    • 1970-01-01
    • 1970-01-01
    • 2012-06-24
    • 2021-06-12
    相关资源
    最近更新 更多