模板标记中的Django用户组具有单个数据库命中

时间:2019-01-29 19:38:56

标签: django django-templates

如何避免在一个用户组的模板中进行多个数据库调用。

我在视图中有数百个重复查询,这些查询的表格式基于用户组。

我不能依赖模板中的默认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 :(得分:1)

要使用预取的组,必须避免过滤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)

我很惊讶地在非正式测试中看到,在缓存列表时,在100.000个呼叫上节省了约10%,而内联成员资格查询进一步节省了多达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)仅预取组名,例如通过在Prefetch对象的查询集上使用values_list