如何在Django中高效为Person查询集注解Summary进度值?
解决方案:高效获取Person关联Summary的进度
首先指出原有代码的核心问题:
Grade和Evidence的related_name设置错误(%(class)ss会生成gradess/evidencess),导致模型间关联查询失效。- 无法通过
Subquery调用模型的Python方法(progress()),因为子查询仅支持数据库层面的表达式。
以下是优化后的实现方案,所有计算逻辑在数据库层面完成,查询次数控制在3-4次:
步骤1:修正模型关联名称
修改Grade和Evidence的外键关联名称,确保关联关系正确:
# models.py class Grade(models.Model): id = models.BigAutoField(primary_key=True) summary = models.ForeignKey( Summary, on_delete=models.PROTECT, related_name="grades" # 修正为grades ) level = models.ForeignKey( Level, on_delete=models.PROTECT, null=True, blank=True, related_name="grades", ) # ... 其余代码不变 class Evidence(models.Model): id = models.BigAutoField(primary_key=True) grade = models.ForeignKey( Grade, on_delete=models.PROTECT, related_name="evidences" # 修正为evidences ) comment = models.TextField()
步骤2:修改视图查询集,注入进度注解
使用Django ORM的聚合、条件注解和子查询,直接在Person查询集中计算进度值:
# views.py from django.db.models import Count, Case, When, FloatField, F, Exists, OuterRef, Q from django.views.generic import ListView from .models import Person, Summary, Evidence class PersonListView(ListView): model = Person template_name = "app/person_list.html" context_object_name = "person_list" def get_queryset(self): # 子查询:判断Grade是否关联至少一个Evidence has_evidence = Exists( Evidence.objects.filter(grade=OuterRef('pk')) ) # 构建带有进度注解的Summary查询集 annotated_summaries = Summary.objects.filter(finalized=False).annotate( # 统计当前Summary的总Grade数量 total_grades=Count('grades', distinct=True), # 统计已完成的Grade数量:有Level且关联至少一个Evidence completed_grades=Count( 'grades', filter=Q( grades__level__isnull=False, grades__in=Grade.objects.filter(has_evidence) ), distinct=True ) ).annotate( # 计算进度,处理总Grade为0的边界情况 progress=Case( When(total_grades=0, then=0.0), then=(F('completed_grades') / F('total_grades')) * 100, output_field=FloatField() ) ) # 给每个Person注入对应的未完成Summary进度 return Person.objects.annotate( summary_progress=Subquery( annotated_summaries.filter(person=OuterRef('pk')) .values('progress')[:1], # 取唯一的未完成Summary output_field=FloatField() ) )
步骤3:模板中直接使用进度值
现在可以在模板中直接访问进度字段,还可以格式化显示:
<div class="progress"> {{ person.summary_progress|floatformat:1 }}% </div>
优化亮点
- 所有计算在数据库层面完成,避免Python层面对对象的循环处理,性能更优。
- 查询次数严格控制在3-4次,符合你的性能要求。
- 处理了总Grade数为0的边界情况,避免除以0的错误。
内容的提问来源于stack exchange,提问作者UpDawg
相关产品推荐
相关产品推荐

