Django select 与条件间接相关

Django select indirectly related with a condition

在我的项目中,我有Users,他们可以起到一定的作用。但是,角色受日期范围限制,即用户可以在 2020 年 1 月 1 日到 2020 年 2 月 2 日期间拥有管理员角色,在 2020 年 2 月 2 日到 2020 年 3 月 3 日期间可以拥有非特权用户角色。

我需要能够 return 具有当前角色的用户列表,并且我希望能够只对数据库进行一次查询。但是,我不知道该怎么做。

这是我的模型:

class User(models.Model):
    ...

class Role(models.Model):
    name = models.CharField(...)

class UserRoleQuerySet(models.QuerySet):
    def current():
        return self.filter(period__contains=date.today())

class UserRoles(models.Model):
    user = models.ForeignKey(User, related_name='roles')
    role = models.ForeignKey(Role)
    period = DateTimeRangeField()

    objects = UserRoleQuerySet.as_manager()

我需要一个调用了 prefetch_relatedselect_relatedannotate 方法的查询集,这样当我执行类似下面的代码时,我就不会得到额外的数据库行程:

users = User.objects.(some_magic_here).all()
for user in users:
    print(user.role)

UPD: 更具体地说,如果我不做任何注释、预取或 select_related,我将得到所有(当前)用户的打印输出角色。但是,每次迭代都会将数据库命中 select 当前角色。我想要的是相同的打印输出,但不需要额外往返 db

要获得一组对象,您将需要 prefetch_related,但您不能在预取后更改(过滤)查询,因为 Django 将无法进行连接。所以你需要在 Django 进行预取时提供过滤器:

users = Users.objects.prefetch_related(
    Prefetch(
        'roles',
        queryset=(
            UserRoles.objects
                .filter(period__contains=date.today())
                .select_related('role')
        )
    )
)

当您遍历 users 时,如果您再次更改查询,每次迭代都会对数据库进行一次额外的访问,因此不要这样做:

for user in users:
    print(user.roles.first().role)

您需要执行以下操作以避免进一步查询数据库,知道 user.roles.all() 始终只会给您一个结果:

for user in users:
    for user_role in user.roles.all():
        print(user_role.role)