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

如何在Django中高效为Person查询集注解Summary进度值?

解决方案:高效获取Person关联Summary的进度

首先指出原有代码的核心问题:

  1. Grade和Evidence的related_name设置错误(%(class)ss会生成gradess/evidencess),导致模型间关联查询失效。
  2. 无法通过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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 22:35:22