Django ListView中按用户权限过滤关联Task对象的实现方案
解决方案
方案1:仅在视图层实现(无需修改模型,符合你的需求)
你不需要放弃ListView,Django的Prefetch对象正好可以解决关联模型预过滤的需求,还能避免N+1查询问题,性能更好。
视图代码修改
from django.db.models import Prefetch, Q from django.views.generic.list import ListView class TaskGroupListView(ListView): model = TaskGroup def get_queryset(self): user = self.request.user base_queryset = TaskGroup.objects.all() # 注意你之前的判断逻辑写错了,应该判断user.type属性 if user.type == "ADMIN": return base_queryset.prefetch_related("task_set") # 普通用户过滤关联任务 allowed_task_queryset = Task.objects.filter( Q(completed=False) | Q(completed_by=user) ) # 把过滤后的任务绑定到TaskGroup的自定义属性上 return base_queryset.prefetch_related( Prefetch("task_set", queryset=allowed_task_queryset, to_attr="filtered_tasks") )
模板代码修改
只需要把遍历任务的部分改成读取我们自定义的属性即可:
{% for taskgroup in object_list %} <h1>{{ taskgroup.name }}</h1> <ul> {% for task in taskgroup.filtered_tasks %} <li>{{ task.name }}</li> {% endfor %} </ul> {% endfor %}
方案2:自定义模型管理器(更符合Django惯用法)
你之前的认知存在偏差:可复用的查询逻辑封装到模型管理器/模型方法是《Two Scoops of Django》明确推荐的最佳实践,不属于反模式。如果后续其他视图也需要用到相同的任务过滤规则,这种方式可以避免重复代码。
模型修改
class TaskManager(models.Manager): def visible_for_user(self, user): if user.type == "ADMIN": return self.all() return self.filter( Q(completed=False) | Q(completed_by=user) ) class Task(models.Model): # 原有字段保持不变 name = models.CharField(max_length=256) group = models.ForeignKey(TaskGroup, on_delete=models.CASCADE) completed = models.BooleanField(default=False) completed_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True) # 替换默认管理器 objects = TaskManager()
视图修改
和方案1逻辑一致,只是查询逻辑可以复用:
def get_queryset(self): user = self.request.user base_queryset = TaskGroup.objects.all() return base_queryset.prefetch_related( Prefetch("task_set", queryset=Task.objects.visible_for_user(user), to_attr="filtered_tasks") )
模板部分的修改和方案1完全相同。
额外说明
- 完全没有必要放弃ListView,通用视图的设计初衷就是允许开发者重写部分方法实现自定义逻辑,从头编写视图反而属于重复造轮子。
- 如果你实在不想修改模板,也可以重写ListView的
get_context_data方法,手动给每个TaskGroup挂载过滤后的任务,但性能会比prefetch的方案差。
内容的提问来源于stack exchange,提问作者Dave Fol
相关产品推荐
相关产品推荐

