Django:判断用户是否投票
我有一长串链接,使用下面的代码输出,包括总投票数、提交者等常规信息,但我不太确定当前登录的用户是否对某个链接投过票。我知道如何在我的视图中做到这一点,但我需要修改下面的视图代码吗,还是可以利用模板的工作方式来判断呢?
我看过Django投票上下的方法,但我不太明白里面的内容(也不需要任何复杂的JavaScript)。
模型(代码片段):
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)
视图(代码片段):
def hot(request):
links = Link.objects.select_related().annotate(votes=Count('vote')).order_by('-created')
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)
if request.user.is_authenticated():
try:
link.voted = Vote.objects.get(link=link, user=request.user)
except Vote.DoesNotExist:
link.voted = None
links = sorted(links, key=lambda x: x.popularity, reverse=True)
links = paginate(request, links, 15)
return direct_to_template(
request,
template = 'links/link_list.html',
extra_context = {
'links': links,
})
上面的视图实际上达到了我的需求,但我觉得这种方式非常低效。这导致了令人头疼的n+1查询,当前的情况是,对于一个包含29个链接的页面,我需要33个查询,而我最开始只需要4个查询。我更希望使用Django的ORM,或者至少使用.extra()方法来实现。
有什么建议吗?
编辑
@Gabriel Hurley
我正在尝试重现你的答案,但结果有些混乱,让我给你看看我做的。
views.py
links = Link.objects.select_related().extra(
select={
'votes': 'COUNT(links_vote.id)',
'voted': 'SELECT COUNT(links_vote.id) FROM links_vote WHERE links_vote.user_id = 1 AND links_vote.link_id = links_link.id',
},
tables = ['links_vote']
)
models.py
class Vote(models.Model):
link = models.ForeignKey(Link)
user = models.ForeignKey(User)
created = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = ('link', 'user')
def __unicode__(self):
return u'%s vote for %s' % (self.user, self.link)
但是它返回了一个错误:
subquery uses ungrouped column "links_link.id" from outer query
第1行:...E links_vote.user_id = 1 AND links_vote.link_id = links_link...
生成的查询看起来大概是这样的(完全一样):
SELECT (SELECT COUNT(links_vote.id) FROM links_vote WHERE links_vote.user_id = 1 AND links_vote.link_id = links_link.id) AS "voted", "links_link"."id", "links_link"."category_id", "links_link"."user_id", "links_link"."created", "links_link"."modified", "links_link"."url", "links_link"."name", "links_category"."id", "links_category"."name", "auth_user"."id", "auth_user"."username", "auth_user"."first_name", "auth_user"."last_name", "auth_user"."email", "auth_user"."password", "auth_user"."is_staff", "auth_user"."is_active", "auth_user"."is_superuser", "auth_user"."last_login", "auth_user"."date_joined" FROM "links_link" INNER JOIN "links_category" ON ("links_link"."category_id" = "links_category"."id") INNER JOIN "auth_user" ON ("links_link"."user_id" = "auth_user"."id") , "links_vote"
我使用的是PostgreSQL,我知道它喜欢使用GROUP BY,但我不太确定该如何修正这个问题。
编辑2(重大进展)
links = Link.objects.select_related().annotate(votes=Count('vote')).extra(
select={
#'voted': 'SELECT COUNT() FROM links_vote WHERE links_vote.user_id = %s AND links_vote.link_id = links_link.id' % (request.user.id),
#'voted': '' % (request.user.id),
#'voted': 'SELECT CASE WHEN links_vote.user_id = %s THEN 1 ELSE 0 END' % (request.user.id),
#'voted': 'SELECT COUNT() FROM links_vote WHERE links_vote.link_id = links_link.id AND links_vote.user_id = %s' % (request.user.id),
},
where=['links_link.id = links_vote.link_id'],
).order_by('-created')
*这只有在应用了这里的一个补丁后才能工作(http://code.djangoproject.com/ticket/11916)
我离找到最后一块判断用户是否投过票的拼图已经很近了……
1 个回答
我之前遇到过这个问题,并且大致是通过 extra
解决的,方法如下:
# annotate whether you've already voted on this item
table = Vote._meta.db_table
select = 'SELECT COUNT(id) FROM %s' %table
where1 = 'WHERE ' + table + '.user_id = %s'
where2 = 'AND ' + table + '.item_id = appname_item.id'
items = items.extra(
select={'votes':" ".join((select, where1, where2,))},
select_params=(request.user.id,)
)
简单来说,这段代码会处理一组项目,并给每个项目加上一个标记,要么是0,要么是某个投票数。在我的系统中,我在投票(Vote)上使用了 unique_together = ('link', 'user')
,这样可以确保每个用户只能投一次票,所以加上的数据要么是0,要么是1(实际上就是布尔值)。这个方法效果很好,避免了n+1的问题。