如何用Django ORM筛选用户拥有全部允许组权限的文档
筛选Person可访问的所有Document(需满足属于文档所有允许组)
模型定义
class Document(models.Model): allowed_groups = models.ManyToManyField(Group, related_name='allowed_documents') class Person(models.Model): permission_groups = models.ManyToManyField(Group, related_name='people') class Group(models.Model): id = models.BigIntegerField()
需求说明
需要筛选出某个Person可访问的所有Document,条件是该Person必须属于该Document的所有allowed_groups成员。
测试场景
- 场景1:Document(allowed_groups=1,2,7) 对应 Person(permission_groups=1,2,6,7,11,15) → 匹配
- 场景2:Document(allowed_groups=1,2,7) 对应 Person(permission_groups=1,7) → 不匹配
- 场景3:Document(allowed_groups=1,2,7) 对应 Person(permission_groups=1,2) → 不匹配
- 场景4:Document(allowed_groups=1,2,7) 对应 Person(permission_groups=2) → 不匹配
- 场景5:Document(allowed_groups=1,2,7) 对应 Person(permission_groups=8) → 不匹配
- 场景6:Document(allowed_groups=1,2,7) 对应 Person(permission_groups=1,2,7) → 匹配
现有代码问题
使用以下代码会匹配除场景5外的所有情况,不符合需求:
person = Person.objects.get(pk=1) Document.objects.filter(allowed_groups__in=person.permission_groups.all())
解决方案
通过Django ORM的annotate和Count结合筛选,核心逻辑是:计算每个文档的允许组总数,同时计算该文档允许组中用户拥有的数量,当两者相等时,说明用户拥有该文档所有允许组。
from django.db.models import Count, Q person = Person.objects.get(pk=1) user_group_ids = person.permission_groups.values_list('id', flat=True) # 可链式添加其他filter条件 documents = Document.objects.annotate( total_allowed_groups=Count('allowed_groups'), matched_groups=Count( 'allowed_groups', filter=Q(allowed_groups__id__in=user_group_ids) ) ).filter(total_allowed_groups=matched_groups)
逻辑说明
total_allowed_groups:统计每个文档关联的允许组总数matched_groups:统计每个文档的允许组中,用户拥有的组数量- 最后筛选
total_allowed_groups=matched_groups的文档,确保用户拥有该文档的所有允许组
这种写法可以直接和其他filter/Q对象链式调用,无需手写SQL。
内容的提问来源于stack exchange,提问作者Michaels
相关产品推荐
相关产品推荐

