如何在双连接关系之后在Django中执行查询(或者:如何绕过Django对ManyToMany"模型"的限制?)

Gab*_*ant 5 python django django-models django-orm django-queryset

必须有办法通过ORM进行此查询,但我没有看到它.

安装程序

这就是我的建模:一个租户可以占用多个房间,一个用户可以拥有多个房间.所以房间有一个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)
Run Code Online (Sandbox Code Playgroud)

问题

给定租户,我希望用户拥有他们占据的房间.

相关的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;
Run Code Online (Sandbox Code Playgroud)

例如,可以使用相关用户对象获取任何单个值my_tenant.rooms.values_list('owner__email', flat=True),但获取用户的完整查询集会让我感到沮丧.

通常一个方式来解决这将是建立一个ManyToMany场在我Tenant的模型指向UserTenantRoom为"到"模型.这将不会在这种情况下工作,虽然,因为TenantRoom模型具有第二(无关)ForeignKeyUser(见"restictions").此外,它似乎是租户模型上的不必要的混乱.

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

问题

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


编辑

事实上,如果通过ORM在一个查询中没有办法直接执行此操作,那么最好的(大多数高性能,最惯用,最可读等的组合)方法能够实现我正在寻找的东西吗?以下是我看到的选项:

  1. 子选择

    users = User.objects.filter(id__in=my_tenant.rooms.values_list('user'))
    
    Run Code Online (Sandbox Code Playgroud)
  2. 通过Python进行子选择(有关此背后的推理,请参阅性能注意事项)

    user_ids = id__in=my_tenant.rooms.values_list('user')
    users = User.objects.filter(id__in=list(user_ids))
    
    Run Code Online (Sandbox Code Playgroud)
  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""")
    
    Run Code Online (Sandbox Code Playgroud)
  4. 其他...?

Jef*_*rry 3

执行此操作的正确方法是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)
Run Code Online (Sandbox Code Playgroud)

然后你可以这样做:

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)
Run Code Online (Sandbox Code Playgroud)