优化Django模板中django-guardian对象权限查询,减少数据库请求
优化Django Guardian对象级权限查询(解决N+1数据库查询问题)
你的当前实现会在循环中为每个对象单独发起权限查询,导致数据库查询量随对象数量线性增长(N+1问题)。可以通过单次批量查询获取所有对象的权限状态,下面提供两种可行方案:
方案一:在视图中构建可编辑对象ID集合
1. 修改ListView视图类
在视图中一次性查询当前用户拥有编辑权限的所有对象ID,存入集合后传递给模板:
from django.contrib.auth.models import Permission from guardian.models import UserObjectPermission from django.views.generic import ListView from .models import Item class ItemListView(ListView): model = Item template_name = "your_template.html" context_object_name = "items" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) items = context["items"] # 获取对应模型的"编辑"权限对象 change_permission = Permission.objects.get( codename="change_item", content_type__model=Item._meta.model_name ) # 批量查询用户拥有编辑权限的对象ID editable_ids = UserObjectPermission.objects.filter( user=self.request.user, permission=change_permission, content_object__in=items ).values_list("content_object_id", flat=True) # 转成集合提升模板中判断的效率 context["editable_item_ids"] = set(editable_ids) return context
2. 修改模板代码
移除循环内的权限查询标签,直接通过ID集合判断:
{% extends "base.html" %} {% block content %} <table> <thead>...</thead> <tbody> {% for i in items %} <tr> <td>{{ i.name }}</td> <td> {% if i.id in editable_item_ids %} <a href="{% url 'edit_item' i.id %}">Edit</a> {% endif %} </td> </tr> {% endfor %} </tbody> </table> {% endblock %}
方案二:用QuerySet Annotate直接标记权限状态
这种方式将权限判断整合到对象查询中,通过annotate为每个对象添加权限状态字段,更简洁直观:
1. 修改ListView的查询集
from django.db.models import Exists, OuterRef from django.contrib.auth.models import Permission from guardian.models import UserObjectPermission from django.views.generic import ListView from .models import Item class ItemListView(ListView): template_name = "your_template.html" context_object_name = "items" def get_queryset(self): change_permission = Permission.objects.get( codename="change_item", content_type__model=Item._meta.model_name ) # 为每个Item对象添加has_edit_perm字段,标记是否拥有编辑权限 return Item.objects.annotate( has_edit_perm=Exists( UserObjectPermission.objects.filter( user=self.request.user, permission=change_permission, content_object_id=OuterRef("pk") ) ) )
2. 修改模板代码
直接判断对象的annotate字段:
{% extends "base.html" %} {% block content %} <table> <thead>...</thead> <tbody> {% for i in items %} <tr> <td>{{ i.name }}</td> <td> {% if i.has_edit_perm %} <a href="{% url 'edit_item' i.id %}">Edit</a> {% endif %} </td> </tr> {% endfor %} </tbody> </table> {% endblock %}
两种方案的优势
- 均只触发两次数据库查询:一次获取所有对象,一次批量查询权限状态,彻底解决N+1问题
- 方案二更贴近Django ORM的使用习惯,模板代码更简洁;方案一在权限逻辑复杂时更灵活
内容的提问来源于stack exchange,提问作者Yasser Mohsen
相关产品推荐
相关产品推荐

