Django:将计算应用于查询集

4

我有一个QuerySet,希望将其传递给通用视图进行分页:

links = Link.objects.annotate(votes=Count('vote')).order_by('-created')[:300]

这是我的“热门”页面,列出了我最新的300个提交(每页30个链接,共10页)。现在我想按照HackerNews使用的算法对此QuerySet进行排序:

(p - 1) / (t + 2)^1.5
p = votes minus submitter's initial vote
t = age of submission in hours

现在,由于在整个数据库上应用此算法可能非常昂贵,因此我只满足于最近的300次提交。我的网站不太可能成为下一个digg/reddit,因此虽然可扩展性是一个优点,但并非必需。

我的问题是如何迭代查询集并按上述算法进行排序?

更多信息,请参见适用的模型:

class Link(models.Model):
    category = models.ForeignKey(Category, blank=False, default=1)
    user = models.ForeignKey(User)
    created = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)
    url = models.URLField(max_length=1024, unique=True, verify_exists=True)
    name = models.CharField(max_length=512)

    def __unicode__(self):
        return u'%s (%s)' % (self.name, self.url)

class Vote(models.Model):
    link = models.ForeignKey(Link)
    user = models.ForeignKey(User)
    created = models.DateTimeField(auto_now_add=True)

    def __unicode__(self):
        return u'%s vote for %s' % (self.user, self.link)

注:

  1. 我没有“踩”的选项,所以投票行的存在就是特定用户对特定链接进行了投票的指示。

编辑

我认为我一直在过度复杂化事情,并发现了一个巧妙的小代码片段:

links = Link.objects.annotate(votes=Count('vote')).order_by('-created')[:300]
for link in links:
    link.popularity = ((link.votes - 1) / (2 + 2)**1.5)

但是我却无法将它翻译成我的模板:
{% for link in object_list %}
    Popularity: {{ link.popularity }}
{% endfor %}

为什么它没有显示出来?我知道流行度是有效的,因为:
print 'LinkID: %s - Votes: %s - Popularity: %s' % (link.id, link.votes, link.popularity)

在控制台中返回了我预期的结果。

3个回答

2
如果可能的话,从您的QuerySet中创建一个值字典或值列表,并将您的排序算法应用于获得的字典(列表)。 参见:http://docs.djangoproject.com/en/dev/ref/models/querysets/#values-fieldshttp://docs.djangoproject.com/en/dev/ref/models/querysets/#values-list-fields 示例
# select links
links = Link.objects.annotate(votes=Count('vote')).order_by('-created')[:300]
# make a values list:
links = links.values_list('id', 'votes', 'created')
# now sort 
# TODO: you need to properly format your created date (x[2]) here
list(links).sort(key = lambda x: (x[1] - 1) / (x[2] + 2)^1.5)

能麻烦您提供一个例子吗?比如将每个“votes”值加1。我很难理解这个概念。 - TheLizardKing
谢谢提供示例!但我遇到了一个问题,报错显示:'ValuesListQuerySet' object has no attribute 'sort'。是我漏掉了哪个导入吗? - TheLizardKing
嗯,我想因为它不完全是一个列表,所以排序不是可用选项。 - TheLizardKing
越来越接近了,我想:KeyError at /links/ 1。嗯。 - TheLizardKing
但我会说我需要将我的QS传递给一个通用视图,所以我不确定将其转换为列表是否是最佳选项。 - TheLizardKing
那么也许你应该添加冗余并在额外的列中计算链接排名? - dragoon

1
qs = [obj1, obj2, obj3] # queryset
s = [] # will hold the sorted items
for obj in qs:
    s.append(((obj.votes-1)/pow((obj.submision_age+2), 1.5), obj))
s.sort()
s.reverse()

在编程中,s 应该按照从最高计算重要性到最低排序,并且看起来像:

[(计算重要性,对象), (计算重要性,对象), ...]


由于我通过通用视图将计算出的列表传递给模板,所以我认为它需要保持为 QS。 - TheLizardKing
不要使用通用视图作为解决方案。编写自己的视图返回排序后的列表很容易,并且我认为在5个项目上不需要分页。也许还可以通过raw()在数据库级别上完成此操作,从而使数据库计算受欢迎程度并按其进行排序。 - Davor Lucic
在数据库层面上实现这个功能被证明是困难的。虽然有人通过使用.extra提供了答案,但它们似乎并不起作用。 - TheLizardKing
raw() 的工作方式与 extra() 有些不同,它返回纯 SQL 的 QuerySet。我认为使用 SQL 可以更快地完成。http://docs.djangoproject.com/en/dev/topics/db/sql/#topics-db-sql - Davor Lucic
哈哈,唉,我想我得拿到开发版本。 - TheLizardKing
可能吧,我想Django 1.2已经进入RC1阶段了,很快就会发布。 - Davor Lucic

0

虽然无法在QuerySet上进行计算,但我不得不将其转换为列表

links = Link.objects.select_related().annotate(votes=Count('vote'))
for link in links:
    delta_in_hours = (int(datetime.now().strftime("%s")) - int(link.created.strftime("%s"))) / 3600
    link.popularity = ((link.votes - 1) / (delta_in_hours + 2)**1.5)

links = sorted(links, key=lambda x: x.popularity, reverse=True)

虽然不是最优解,但它能够工作。我不能使用我喜欢的 object_list 通用视图及其自动分页功能,只能手动实现,但这是一个公平的妥协,以获得一个可用的视图...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接