如何使用 Django 的 ORM 过滤所有包含多对多关系中多个元素的对象?

How to filter all objects that contain multiple elements in a many-to-many relationship using Django's ORM?

我在 Django 中有两个 classes 通过 ManyToManyField 链接(User class 是内置的 User 模型):

from django.contrib.auth.models import User

class Activity():
    participants = models.ManyToManyField(User, related_name='activity_participants')

我想找出两个用户同时参与的所有活动。

我设法使用原始查询解决了我的问题(我的应用程序名称是 "core",因此 table 名称中的 "core" 前缀:

SELECT ca.id FROM core_activity_participants AS cap, core_activity AS ca
INNER JOIN core_activity_participants AS cap2 ON cap.activity_id
WHERE cap.user_id == 1 AND cap2.user_id == 2
    AND cap.activity_id == cap2.activity_id
    AND ca.id == cap.activity_id

但是,如果可能的话,我想避免使用原始查询,因为它破坏了我应用程序其余部分的一致性。我如何使用 Django 的 ORM 进行此查询或与之等效的查询?

如果您使用的是 Django 1.11 或更高版本,intersection 查询集方法将为您提供所需的记录。

# u1 and u2 are User instances
u1_activities = Activity.objects.filter(participants=u1)
u2_activities = Activity.objects.filter(participants=u2)
common_activities = u1_activities.intersection(u2_activities)

将生成如下查询:

SELECT "core_activity"."id"
FROM "core_activity"
INNER JOIN "core_activity_participants"
ON ("core_activity"."id" = "core_activity_participants"."activity_id")
WHERE "core_activity_participants"."user_id" = 1
INTERSECT
SELECT "core_activity"."id"
FROM "core_activity"
INNER JOIN "core_activity_participants"
ON ("core_activity"."id" = "core_activity_participants"."activity_id")
WHERE "core_activity_participants"."user_id" = 2

如果您想检查超过 2 个用户之间的 activity 重叠,您还可以向交集添加额外的查询集。

更新:

另一种适用于旧版 Django 的方法是

u1_activities = u1.activity_participants.values_list('pk', flat=True)
common_activities = u2.activity_participants.filter(pk__in=u1_activities)

这会产生类似

的查询
SELECT "core_activity"."id"
FROM "core_activity"
INNER JOIN "core_activity_participants"
ON ("core_activity"."id" = "core_activity_participants"."activity_id")
WHERE (
    "core_activity_participants"."user_id" = 2
    AND "core_activity"."id" IN (
        SELECT U0."id"
        FROM "core_activity" U0
        INNER JOIN "core_activity_participants" U1
        ON (U0."id" = U1."activity_id")
        WHERE U1."user_id" = 1
    )
)