Django:过滤注解结果
我有两个模型:
class Status(models.Model):
title = models.CharField(max_length=32)
class Task(models.Model):
user = models.ForeignKey(User)
status = models.ForeignKey(Status, default=1)
title = models.CharField(max_length=128)
我想创建一个导航列表,里面包含我状态模型中的所有状态,比如:今天、明天、等待、已安排、垃圾箱。
这听起来很简单。接下来,我想显示每个状态下分配的任务数量,感谢StackOverflow,这也很简单:
Status.objects.all().annotate(Count('task'))
这样就能很好地列出我所有的状态和每个状态下的任务数量:
今天 (1) 明天 (1) 等待 (0) 已安排 (2) 垃圾箱 (7)
现在的难点是如何过滤这些值,让它们只显示当前登录用户的状态。给查询结果添加过滤器似乎会把所有零状态去掉,这很合理。但我其实想保留这些零状态。我现在的想法是用 Q()
:
Status.objects.filter(Q(task__user=1) | Q(task__user__isnull=True)).annotate(Count('task'))
但是这样不行。
有没有什么好主意?
为Yuji编辑
Status.objects.all().annotate(Count('task'))
结果是:
收件箱 (3) 今天 (0) 下一个 (1) 等待 (0) 已安排 (1) 稍后 (0) 某天 (0) 档案 (0) 垃圾箱 (0)
很好,但其中一个收件箱任务和已安排的任务是给其他用户的。好吧,那我们试试过滤。
Status.objects.filter(task__user=current_user).annotate(Count('task'))
收件箱 (2) 下一个 (1)
成功了!有点……我之前说的那些零状态不见了。应该说,任何没有与当前登录用户关联的任务的状态都不会显示出来。我希望它们能显示出来。
3 个回答
嗯……我觉得你可以通过 LEFT OUTER JOIN
或者 SUBQUERY
来实现这个功能……我对在 Django 中怎么写左外连接不太了解,所以我选择用 SUBQUERY
的方式。接下来会用 extra
和一些手写的 SQL,祝你好运!
# you should have Task and Status imported
x = Status.objects.extra(select = {
"task__count" : "SELECT COUNT(*) FROM %(task_table)s WHERE %(task_table)s.%(task_status_id)s = %(status_table)s.%(status_pk)s AND %(task_table)s.%(user_col)s = %(user_id)d" %
{
"task_table" : Task._meta.db_table,
"task_status_id" : Task._meta.get_field_by_name("status")[0].column,
"status_table" : Status._meta.db_table,
"status_pk" : Status._meta.pk.column,
"user_col" : Task._meta.get_field_by_name("user")[0].column,
"user_id" : 1
}
})
请注意,我用了一些……没有文档说明的特性(比如:Task._meta),这些可能将来会出问题(希望不会)……不过,嘿,它们能完成任务。
这段代码 Status.objects.annotate(Count('task')).filter(task__user=current_user)
能用吗?
你想要的是在数据库查询中使用 LEFT JOIN
这样的方式,把 Status
表放在左边,而不是用 INNER JOIN
。不过我不太确定这和注解(annotations)是怎么结合在一起的。
如果其他方法都不行,可以试试这种比较直接的方法:[stat.filter(task__user=current_user).count() for stat in Status.objects.all()]
,但这样会发出 N 次查询,而不是一次。
这段话的意思是,你可以通过这个方法来计算属于某个用户的所有任务对象的数量。
Status.objects.filter(task__user=current_user).annotate(Count('task'))
你说加了一个过滤条件就把所有状态为零的任务去掉,这是什么意思呢?
其实,加上用户的过滤条件后,会把所有和这个用户相关的任务对象都找出来,不管它们的状态是什么(比如状态是0或者其他的)。