如何在双连接关系之后在 Django 中执行查询(或:如何绕过 Django 对多对多“通过”模型的限制?)
Posted
技术标签:
【中文标题】如何在双连接关系之后在 Django 中执行查询(或:如何绕过 Django 对多对多“通过”模型的限制?)【英文标题】:How to perform queries in Django following double-join relationships (or: How to get around Django's restrictions on ManyToMany "through" models?) 【发布时间】:2011-06-16 06:35:48 【问题描述】:一定有办法通过 ORM 进行此查询,但我没有看到。
设置
我的模型如下:一个租户可以占用多个房间,一个用户可以拥有多个房间。因此,Rooms 对租户有一个 FK,对用户有一个 FK。房间也由(可能不同的)用户维护。
也就是说,我有这些(简化的)模型:
class Tenant(models.Model):
name = models.CharField(max_length=100)
class Room(models.Model):
owner = models.ForeignKey(User)
maintainer = models.ForeignKey(User)
tenant = models.ForeignKey(Tenant)
问题
给定一个租户,我希望用户拥有一个他们占用的房间。
相关的 SQL 查询是:
SELECT auth_user.id, ...
FROM tenants_tenant, tenants_room, auth_user
WHERE tenants_tenant.id = tenants_room.tenant_id
AND tenants_room.owner_id = auth_user.id;
例如,可以使用my_tenant.rooms.values_list('owner__email', flat=True)
从相关的用户对象中获取任何单独的值,但是获取完整的用户查询集让我很头疼。
通常一种解决方法是在我的Tenant
模型上设置一个ManyToMany
字段,指向User
,TenantRoom
作为“直通”模型。但是,在这种情况下这不起作用,因为TenantRoom
模型有第二个(不相关的)ForeignKey
到User
(see "restictions")。此外,租户模型似乎是不必要的混乱。
执行my_tenant.rooms.values_list('user', flat=True)
让我很接近,但返回用户 ID 的 ValuesListQuerySet 而不是实际用户对象的查询集。
问题
那么:有没有办法通过 ORM 仅使用一个查询来获取实际模型实例的查询集?
编辑
事实上,如果没有办法通过 ORM 直接在一个查询中执行此操作,那么完成我正在做的事情的最佳方式是什么(最高效、最惯用、最易读等的某种组合)寻找?以下是我看到的选项:
子选择
users = User.objects.filter(id__in=my_tenant.rooms.values_list('user'))
通过 Python 进行子选择(请参阅 Performance considerations 了解其背后的原因)
user_ids = id__in=my_tenant.rooms.values_list('user')
users = User.objects.filter(id__in=list(user_ids))
原始 SQL:
User.objects.all("""SELECT auth_user.*
FROM tenants_tenant, tenants_room, auth_user
WHERE tenants_tenant.id = tenants_room.tenant_id
AND tenants_room.owner_id = auth_user.id""")
其他...?
【问题讨论】:
【参考方案1】:正确的做法是使用related_name
:
class Tenant(models.Model):
name = models.CharField(max_length=100)
class Room(models.Model):
owner = models.ForeignKey(User, related_name='owns')
maintainer = models.ForeignKey(User, related_name='maintains')
tenant = models.ForeignKey(Tenant)
那么你可以这样做:
jrb = User.objects.create(username='jrb')
bill = User.objects.create(username='bill')
bob = models.Tenant.objects.create(name="Bob")
models.Room.objects.create(owner=jrb, maintainer=bill, tenant=bob)
User.objects.filter(owns__tenant=bob)
【讨论】:
哇。脑残。我的模型上确实已经有相关名称,但将它们作为(我认为的)无关细节删除,以尽量减少示例。我下定决心要弄清楚如何向前推进关系,以至于我不知何故错过了从用户倒退的显而易见的解决方案。谢谢你让我直截了当! @kRON 我相信related_name
是必需的,因为在 Room 模型上有两个 FK 给用户。默认情况下,Django 会在 User 模型中添加一个 room_set
属性,当存在单个 FK 时该属性可以正常工作,但在添加第二个 FK 时会变得模棱两可(我怀疑第二个 FK 会破坏第一个对 @ 的分配987654326@,但我没有检查确认)
经过数小时仔细研究文档后,您的回答终于向我展示了相关名称的含义。非常感谢,杰夫!以上是关于如何在双连接关系之后在 Django 中执行查询(或:如何绕过 Django 对多对多“通过”模型的限制?)的主要内容,如果未能解决你的问题,请参考以下文章