Django выберите косвенно связанный с условием - PullRequest
0 голосов
/ 12 марта 2020

В моем проекте у меня есть Users, и они могут играть определенную роль. Однако роли ограничены диапазонами дат, то есть пользователь может иметь роль администратора с 01.01.2020 по 02.02.2020 и непривилегированную роль пользователя с 02.02.2020 по 03.03.2020.

Мне нужно чтобы иметь возможность вернуть список пользователей с их текущими ролями, и я хочу иметь возможность сделать только один запрос к базе данных. Однако я не могу понять, как это сделать.

Вот мои модели:

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_related, select_related или annotate метод вызывается так, что когда я делаю что-то вроде приведенного ниже кода, я не получаю дополнительных обращений к базе данных:

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

UPD: Чтобы быть более точным c, если я не буду делать никаких аннотаций, предварительных выборок или select_related, я получу распечатку всех (текущих) пользовательских ролей. Тем не менее, каждая итерация будет нажимать на БД, чтобы выбрать эту текущую роль. Мне нужна та же распечатка, но без дополнительных обращений к БД

1 Ответ

1 голос
/ 12 марта 2020

Чтобы получить набор объектов, вам потребуется 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)
...