【问题标题】:How to perform queries in Django following double-join relationships (or: How to get around Django's restrictions on ManyToMany "through" models?)如何在双连接关系之后在 Django 中执行查询(或:如何绕过 Django 对多对多“通过”模型的限制?)
【发布时间】: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 字段,指向UserTenantRoom 作为“直通”模型。但是,在这种情况下这不起作用,因为TenantRoom 模型有第二个(不相关的)ForeignKeyUsersee "restictions")。此外,租户模型似乎是不必要的混乱。

执行my_tenant.rooms.values_list('user', flat=True) 让我很接近,但返回用户 ID 的 ValuesListQuerySet 而不是实际用户对象的查询集。

问题

那么:有没有办法通过 ORM 仅使用一个查询来获取实际模型实例的查询集?


编辑

事实上,如果没有办法通过 ORM 直接在一个查询中执行此操作,那么完成我正在做的事情的最佳方式是什么(最高效、最惯用、最易读等的某种组合)寻找?以下是我看到的选项:

  1. 子选择

    users = User.objects.filter(id__in=my_tenant.rooms.values_list('user'))
    
  2. 通过 Python 进行子选择(请参阅 Performance considerations 了解其背后的原因)

    user_ids = id__in=my_tenant.rooms.values_list('user')
    users = User.objects.filter(id__in=list(user_ids))
    
  3. 原始 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""")
    
  4. 其他...?

【问题讨论】:

    标签: python django django-models django-queryset django-orm


    【解决方案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@,但我没有检查确认)
    • 经过数小时仔细研究文档后,您的回答终于向我展示了相关名称的含义。非常感谢,杰夫!
    猜你喜欢
    • 2013-07-04
    • 2011-10-09
    • 1970-01-01
    • 2014-05-27
    • 2011-06-21
    • 2013-11-22
    • 2011-11-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多