Группы пользователей Django в теге шаблона с одним попаданием в базу данных - PullRequest
0 голосов
/ 29 января 2019

Как избежать нескольких вызовов базы данных в шаблоне для группы (групп) пользователя.

У меня есть сотни повторяющихся запросов в представлениях с таблицами, которые форматируются на основе группы (групп) пользователя.

Я не могу положиться на значение по умолчанию user в шаблоне, так как он не (или я не знаю, как) добавить prefetch_related для получения группы (групп) пользователя.Поэтому я добавляю к виду:

user = User.objects.all().select_related(
).prefetch_related(
    'groups',
).get(pk=request.user.pk)

В шаблоне, если я хочу проверить, является ли пользователь частью группы, я бы сделал что-то вроде:

 {% if user|has_group:"Mechanic" %}

исделать тег шаблона (я пробовал несколько версий, но в конечном итоге тег шаблона не кэшируется, и поэтому всегда будет снова попадать в базу данных, даже если он использует пользователя и его атрибуты prefetch_related)

@register.filter(name='has_group')
def has_group(user, group_name):
    lis = list(user.groups.all().values_list('name', flat=True))
    grp = get_object_or_404(Group, name=group_name)
    return grp in lis

Если я передам все объекты группы в шаблон, я могу избежать вызовов, проверив тип группы по атрибутам prefetch_related пользовательской модели, но теперь я должен передать каждую модель группы по отдельности каждому представлению

в представлении

  mechGroup = Group.objects.filter(name='Mechanic')
  managerGroup = Group.objects.filter(name='Manager')
  …

в шаблоне

  {% if mechGroup in user.groups.all %}

Есть ли лучший / более простой способ сделать это?

1 Ответ

0 голосов
/ 01 февраля 2019

Чтобы использовать предварительно выбранные группы, вы должны избегать фильтрации набора запросов groups.all(), так как это создаст новый запрос.Итак:

def has_group(user, group_name):
    groups = user.groups.all()
    return group_name in [g.name for g in groups]

Обратите внимание, что это не обязательно повышает производительность по сравнению с отдельным запросом для пользователя.Обязательно выполните некоторое профилирование.

Если у вас есть тысячи вызовов на has_group на отчет, вы можете сохранить список имен групп в атрибуте пользователя, сэкономив еще несколько мс на отчет:

def has_group(user, group_name):
    groups = user.groups.all()
    if not hasattr(user, 'group_names'):
        user.group_names = [g.name for g in groups]
    return group_name in user.group_names

И, наконец, если вы знаете, что для большинства пользователей набора запросов вы будете вызывать has_user, вы можете предварительно инициализировать атрибут group_names для всех пользователей, чтобы сохранить вызов функции has_group для каждого запроса ииспользовать if group_name in user.group_names напрямую вместо has_group(user, group_name).

Я был удивлен, увидев в неформальном тестовом прогоне, что при кэшировании списка сэкономлено около 10% на 100 000 вызовов, а добавление запроса на членство еще больше сэкономило колоссальные 80%.довести общую экономию на порядок.Но если у вас всего несколько сотен вызовов в отчете, эффект, вероятно, незначителен.

Еще один способ сэкономить время - сделать ваши предварительные выборки проще, поскольку prefetch_related - довольно тяжелая операция (вот почемучасто имеет смысл делать целевые запросы к базе данных, а не перетаскивать все эти данные из базы данных в объекты Python).Поэтому, если у вас много пользователей (или групп), и вы знаете, что вам нужны только имена групп, а не полноценные объекты групп, не используйте

users = User.objects.prefetch_related('groups')

Используйте это вместочтобы получить список Group объектов, а не QuerySet, только с предварительно выбранным полем name:

users = (User.objects
    .prefetch_related(
        Prefetch('groups',
                  queryset=Group.objects.only('name'), 
                  to_attr='group_list'
        )
    )
)

Затем создайте список имен групп, как раньше:

for u in users:
    u.group_names = [g.name for g in u.group_list]

К сожалению, пока (v2.1.5) невозможно предварительно выбрать только имена групп, например, с помощью values_list в наборе запросов объекта предварительной выборки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...