Django如何正确使用filter()查询用户已报课程对应的测验
问题说明
需要实现权限控制逻辑:仅向报名了对应课程的用户展示该课程的测验,例如用户报名了Frontend Crash Course,就只能看到这门课关联的quiz,无法查看数据库内所有测验。
项目用UserCourse模型存储用户的课程报名记录,开发过程中两次查询写法都触发了报错:
- 第一次使用
.get()查询用户报名记录再过滤测验,代码如下:
user_course = UserCourse.objects.get(user=request.user) quizzes = Quiz.objects.filter(course__usercourse=user_course).annotate(questions_count=Count('questions'))
触发报错:get() returned more than one UserCourse -- it returned 3!
- 第二次将
.get()替换为.filter(),修改后代码:
user_course = UserCourse.objects.filter(user=request.user) quizzes = Quiz.objects.filter(course__usercourse=user_course).annotate(questions_count=Count('questions'))
触发报错:The QuerySet value for an exact lookup must be limited to one result using slicing.
涉及的模型定义如下:
class UserCourse(models.Model): user = models.ForeignKey(User , null = False , on_delete=models.CASCADE) course = models.ForeignKey(Course , null = False , on_delete=models.CASCADE, related_name="usercourse") class Quiz(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="quizzes") title = models.CharField(max_length=255) course = models.ForeignKey(Course, on_delete=models.SET_NULL, null=True, related_name="quizzes") date = models.DateTimeField(auto_now_add=True) slug = models.SlugField(unique=True) user_course = models.ForeignKey(UserCourse, on_delete=models.SET_NULL, null=True) def __str__(self): return self.title
报错原因
.get()方法仅能返回单条匹配的记录,单个用户通常会报名多门课程,UserCourse表中对应该用户的记录有多条,自然触发“返回结果超过1条”的错误。- 直接将
.filter()返回的多结果QuerySet传入字段做精确匹配时,Django无法处理这类查询:精确匹配要求对比值为单个实例,不能是未切片的多结果集合。
正确查询写法
两种方案都可以实现需求,根据代码习惯选择即可:
- 直接跨关联表查询,不需要提前查询用户报名记录
from django.db.models import Count quizzes = Quiz.objects.filter( # 顺着Quiz -> Course -> UserCourse的关联路径,筛选属于当前用户报名课程的测验 course__usercourse__user=request.user ).annotate( questions_count=Count('questions') )
这种写法是Django ORM支持的跨关系链式查询,会自动处理表关联逻辑,不需要手动处理中间查询结果,代码更简洁。
- 提前查询用户报名记录时,用
__in匹配集合内的所有值
from django.db.models import Count # 变量名用复数更符合语义,这里拿到的是当前用户所有的报名记录集合 user_courses = UserCourse.objects.filter(user=request.user) quizzes = Quiz.objects.filter( # 用__in声明匹配集合内所有关联记录,而不是做单值精确匹配 course__usercourse__in=user_courses ).annotate( questions_count=Count('questions') )
额外提示:你的
Quiz模型本身已经设置了指向UserCourse的外键字段user_course,如果创建测验的业务逻辑已经正确给这个字段赋值,还可以直接用更短的查询逻辑:Quiz.objects.filter(user_course__user=request.user),查询效率更高。使用前请先确认user_course字段没有遗漏赋值的情况,避免出现查询结果缺失的问题。
内容的提问来源于stack exchange,提问作者Destiny Franks
相关产品推荐
相关产品推荐

