Django模型 - 如何过滤ForeignKey对象的数量
我有两个模型,A
和 B
,它们的结构是这样的:
class A(models.Model):
title = models.CharField(max_length=20)
(...)
class B(models.Model):
date = models.DateTimeField(auto_now_add=True)
(...)
a = models.ForeignKey(A)
现在我有一些 A
和 B
的对象,我想要一个查询,选出所有指向它们的 B
对象少于2个的 A
对象。
A
就像是一个池子,而用户(也就是 B
)加入这个池子。如果只有1个或者0个用户加入,这个池子就不应该显示出来。
这样的模型设计可以实现吗?还是说我需要稍微修改一下?
4 个回答
我建议你修改一下设计,在A上加一个状态字段。
问题在于“为什么?” 为什么A会有少于2个B,或者为什么A会有两个或更多的B。这是因为用户没有输入什么吗?还是因为他们尝试了但输入有错误?或者是因为少于2的规则在这种情况下不适用。
使用外键的有无只能表示“有”或“没有”,这样你就无法表示“为什么”。
另外,你还有以下选项:
[ a for a in A.objects.all() if a.b_set.count() < 2 ]
这样做可能会比较耗资源,因为它会获取所有的A,而不是让数据库来处理这个工作。
补充说明:关于评论“这需要我监控用户加入/离开池的事件”。
你并不需要“监控”任何东西——你只需要提供一个API来完成你需要的功能。这就是Django模型的主要好处。这里有一种方法,可以在A
类中使用明确的方法。
class A( models.Model ):
....
def addB( self, b ):
self.b_set.add( b )
self.changeFlags()
def removeB( self, b ):
self.b_set.remove( b )
self.changeFlags()
def changeFlags( self ):
if self.b_set.count() < 2: self.show= NotYet
else: self.show= ShowNow
你还可以为此定义一个特殊的Manager
,并用你的管理器替换默认的b_set
管理器,这样它可以计算引用并更新A
。
这个问题和选中的回答是2008年的内容,从那以后,这个功能已经被整合进了django框架。因为这是关于“django过滤外键计数”的热门搜索结果,我想分享一个更简单的解决方案,适用于最近的django版本,使用了聚合功能。
from django.db.models import Count
cats = A.objects.annotate(num_b=Count('b')).filter(num_b__lt=2)
在我的情况下,我需要把这个概念再往前推进一步。我的“B”对象有一个布尔字段叫做is_available,我只想返回那些有超过0个is_available为True的B对象的A对象。
A.objects.filter(B__is_available=True).annotate(num_b=Count('b')).filter(num_b__gt=0).order_by('-num_items')
这听起来像是可以用extra
来解决的问题。
A.objects.extra(
select={
'b_count': 'SELECT COUNT(*) FROM yourapp_b WHERE yourapp_b.a_id = yourapp_a.id',
},
where=['b_count < 2']
)
如果你经常需要B的数量来进行筛选或排序,或者需要在列表视图中显示这个数量,你可以考虑对A模型进行“反规范化”,也就是在A模型里添加一个b_count字段,并使用信号来在添加或删除B的时候更新这个字段:
from django.db import connection, transaction
from django.db.models.signals import post_delete, post_save
def update_b_count(instance, **kwargs):
"""
Updates the B count for the A related to the given B.
"""
if not kwargs.get('created', True) or kwargs.get('raw', False):
return
cursor = connection.cursor()
cursor.execute(
'UPDATE yourapp_a SET b_count = ('
'SELECT COUNT(*) FROM yourapp_b '
'WHERE yourapp_b.a_id = yourapp_a.id'
') '
'WHERE id = %s', [instance.a_id])
transaction.commit_unless_managed()
post_save.connect(update_b_count, sender=B)
post_delete.connect(update_b_count, sender=B)
另一种解决方案是在你添加或删除相关的B时,管理A对象上的一个状态标志。
B.objects.create(a=some_a)
if some_a.hidden and some_a.b_set.count() > 1:
A.objects.filter(id=some_a.id).update(hidden=False)
...
some_a = b.a
some_b.delete()
if not some_a.hidden and some_a.b_set.count() < 2:
A.objects.filter(id=some_a.id).update(hidden=True)