Django如何在模型管理器中获取请求用户或登录用户
Django 模型层采用请求上下文解耦的设计,request 对象仅在视图、中间件等处理 HTTP 请求的流程中存在,自定义管理器初始化、执行查询时默认没有请求上下文的访问权限。直接在 get_queryset 方法里引用 request.user 属于作用域不匹配,会直接抛出变量未定义错误,不可能正常运行。
方案1:给管理器增加显式传参的过滤方法(官方推荐,无副作用)
不要把用户过滤逻辑硬编码在默认 get_queryset 中,而是在自定义管理器里新增接收用户参数的查询方法,在视图层调用时主动传入当前请求的用户对象。该方案完全符合 Django 分层设计原则,无线程安全问题,兼容同步、异步所有部署场景,也不会影响管理后台、定时任务、命令行等非HTTP场景下的模型使用。
代码实现示例:
from django.db import models from django.contrib.auth import get_user_model class Course(models.Model): name = models.CharField(max_length=100) instructors = models.ManyToManyField(get_user_model()) # 其余原有字段定义 class InstructorCourseManager(models.Manager): def for_instructor(self, user): # 接收外部传入的用户对象,返回过滤后的查询集 return super().get_queryset().filter(instructors=user) class ProxyInstructorCourse(Course): objects = InstructorCourseManager() class Meta: proxy = True
视图层调用示例:
def my_courses(request): # 调用时显式传入当前登录用户 my_course_list = ProxyInstructorCourse.objects.for_instructor(request.user) return render(request, 'course_list.html', {'courses': my_course_list})
如果需要复用基础过滤逻辑,可以在管理器内部封装公共过滤方法,按需组合查询条件即可。
方案2:通过线程本地变量存储当前请求用户(非必要不推荐)
如果项目全为同步部署,且确实需要所有默认查询自动带上用户过滤、无需每次手动传参,可以通过「中间件+线程本地存储」的方式,把当前请求的用户对象存在独立线程变量中,管理器执行查询时直接从该变量取值。
该方案存在明显缺陷:
- 有线程安全风险,如果中间件未及时清理变量,可能出现用户数据串号问题
- 完全不兼容ASGI异步视图、异步任务场景
- 隐式传递依赖会提升代码排查难度
- Shell、管理后台、定时脚本等非HTTP请求场景下,会因为取不到用户返回空查询集,容易引发非预期bug
简单实现示例:
# 新建context.py存储线程本地变量 import threading _thread_locals = threading.local() def set_current_user(user): _thread_locals.user = user def get_current_user(): return getattr(_thread_locals, 'user', None) def clear_current_user(): if hasattr(_thread_locals, 'user'): del _thread_locals.user
中间件实现:
# 新建middlewares.py from .context import set_current_user, clear_current_user class CurrentUserMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): # 请求进入时绑定当前用户 if request.user.is_authenticated: set_current_user(request.user) response = self.get_response(request) # 请求结束必须清理变量,避免线程复用导致数据串号 clear_current_user() return response
将中间件添加到settings.py的MIDDLEWARE列表中,放在django.contrib.auth.middleware.AuthenticationMiddleware之后即可。
最后修改管理器的get_queryset方法:
from .context import get_current_user class InstructorCourseManager(models.Manager): def get_queryset(self): current_user = get_current_user() qs = super().get_queryset() # 处理未登录/取不到用户的边界情况 if not current_user or not current_user.is_authenticated: return qs.none() return qs.filter(instructors=current_user)
方案3:视图层封装公共查询混入类
如果不想修改管理器逻辑,也可以在视图层做公共封装,编写查询混入类重写获取查询集的方法,自动过滤当前登录用户的课程,所有需要返回讲师课程的视图直接继承该混入类即可,逻辑更直观,不会破坏模型层独立性。
示例:
class InstructorCourseQueryMixin: def get_queryset(self): return super().get_queryset().filter(instructors=self.request.user) # 类视图中直接继承使用 from django.views.generic import ListView class MyCourseListView(InstructorCourseQueryMixin, ListView): model = Course template_name = 'course_list.html'
内容的提问来源于stack exchange,提问作者fdas

