You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django中如何用prefetch_related嵌套过滤关联当前学生的Mark数据

优化Django StudentDetailView中Mark数据的数据库层面过滤方案

先基于你的描述给出模型关系假设(如果实际结构不同,只需调整过滤条件即可):

class StudentGroup(models.Model):
    name = models.CharField(max_length=100)

class Student(models.Model):
    name = models.CharField(max_length=100)
    group = models.ForeignKey(StudentGroup, on_delete=models.CASCADE, related_name='students')

class Pair(models.Model):
    group = models.ForeignKey(StudentGroup, on_delete=models.CASCADE, related_name='pairs')
    student1 = models.ForeignKey(Student, on_delete=models.CASCADE, related_name='pair_as_student1')
    student2 = models.ForeignKey(Student, on_delete=models.CASCADE, related_name='pair_as_student2')

class Mark(models.Model):
    student = models.ForeignKey(Student, on_delete=models.CASCADE, related_name='marks')
    pair = models.ForeignKey(Pair, on_delete=models.CASCADE, related_name='marks')
    score = models.IntegerField()

方案1:使用Prefetch对象实现预取时的过滤

这是最贴合需求的优化方案,通过Prefetch实例指定过滤后的Mark查询集,所有过滤逻辑在数据库层面完成,避免模板中再做条件判断:

from django.db.models import Prefetch
from django.views.generic.detail import DetailView
from .models import Student, Pair, Mark

class StudentDetailView(DetailView):
    model = Student
    template_name = 'student_detail.html'

    def get_queryset(self):
        student_id = self.kwargs['pk']
        # 定义针对当前学生的Mark过滤预取
        student_mark_prefetch = Prefetch(
            'marks',
            queryset=Mark.objects.filter(student_id=student_id),
            to_attr='current_student_marks'  # 自定义属性名,避免覆盖原有的marks集合
        )
        # 预取学生所在组的Pair,同时关联过滤后的Mark数据
        pair_prefetch = Prefetch(
            'group__pairs',
            queryset=Pair.objects.prefetch_related(student_mark_prefetch)
        )
        # 最终查询集:关联学生组,预取过滤后的Pair和Mark
        return Student.objects.select_related('group').prefetch_related(pair_prefetch)

在模板中,你可以直接调用{{ pair.current_student_marks }},这已经是当前学生对应的Mark集合,无需再用{% if %}过滤。

方案2:用Subquery给Pair标注当前学生的分数

如果每个学生在单个Pair下只有一条Mark记录,可以通过Subquery将分数直接注解到Pair对象上,进一步简化模板逻辑:

from django.db.models import Subquery, OuterRef
from django.views.generic.detail import DetailView
from .models import Student, Pair, Mark

class StudentDetailView(DetailView):
    model = Student
    template_name = 'student_detail.html'

    def get_queryset(self):
        student_id = self.kwargs['pk']
        # 子查询获取当前学生在对应Pair下的分数
        mark_subquery = Mark.objects.filter(
            pair=OuterRef('pk'),
            student_id=student_id
        ).values('score')[:1]  # 取单条记录,确保子查询返回单个值
        # 给Pair注解当前学生的分数,同时预取到学生查询集中
        pair_queryset = Pair.objects.annotate(current_student_score=Subquery(mark_subquery))
        pair_prefetch = Prefetch('group__pairs', queryset=pair_queryset)
        
        return Student.objects.select_related('group').prefetch_related(pair_prefetch)

模板中直接使用{{ pair.current_student_score }}即可获取当前学生在该Pair下的分数,完全无需处理Mark集合。

方案对比:为什么比重载get_object更优

重载get_object通常是先获取Student对象,再手动过滤关联数据,这会额外产生N次数据库查询(N为Pair的数量)。而上述两种方案都是在单次查询阶段完成所有预取和过滤,完全符合Django ORM的"减少查询次数"最佳实践,性能更优,代码结构也更清晰。

内容的提问来源于stack exchange,提问作者Zwick Vitaly

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 20:00:16