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

如何构建Django QuerySet获取参与者的最新签到记录?

如何为参与者关联最新的签到记录?

问题描述

我需要渲染一个展示参与者(成人及未成年人)及其最新签到、签出时间的模板。系统中有两个参与者模型:AdultParticipant和MinorParticipant,其中MinorParticipant通过外键关联到AdultParticipant。签到信息(签到、签出时间)统一存储在CheckIn表中,每条记录对应一位参与者的签到信息,每位参与者可有多条签到记录,但当前代码会展示所有签到记录,我希望仅显示每个参与者的最新一条。请问如何构建QuerySet,在获取参与者信息的同时仅关联其最新的签到记录?

当前代码

模型代码

class AdultParticipant(models.Model):
    first_name = models.CharField(max_length=50)
    middle_initial = models.CharField(max_length=50, blank=True)
    # 注:模板中用到了last_name字段,原代码遗漏,建议补充
    last_name = models.CharField(max_length=50)

class MinorParticipant(models.Model):
    first_name = models.CharField(max_length=50)
    middle_initial = models.CharField(max_length=50, blank=True)
    # 注:原代码中外键指向WaiverAdult应为AdultParticipant,否则会报错
    parent = models.ForeignKey(
        AdultParticipant, on_delete=models.CASCADE, related_name='minor_of_adult')
    # 注:模板中用到了last_name字段,原代码遗漏,建议补充
    last_name = models.CharField(max_length=50)

class CheckIn(models.Model):
    adult = models.ForeignKey(
        AdultParticipant, on_delete=models.CASCADE, blank=True, null=True, related_name='adult_checkin')
    minor = models.ForeignKey(
        MinorParticipant, on_delete=models.CASCADE, blank=True, null=True, related_name='minor_checkin')
    checkin = models.DateTimeField(blank=True, null=True)
    checkout = models.DateTimeField(blank=True, null=True)

视图代码

class WaiverListView(ListView):
    participants_and_checkins = AdultParticipant.objects.all().order_by('created')
    queryset = participants_and_checkins
    context_object_name = "the_list"
    template_name = 'list.html'

模板代码

{% for adult in the_list %}
  <tr>                          
   <td class="fs-6">
     {% for c in adult.adult_checkin.all|dictsortreversed:"checkin" %} 
               In: {{ c.checkin }}<br/>
               Out: {{c.checkout}}<br/>
     {% endfor %}
                         
   </td>
   <td>{{adult.last_name}}</td>
   <td>{{adult.first_name}}</td>
  </tr>

  {% for child in adult.minor_of_adult.all %} 
    <tr>
      <td class="fs-6">
        {% for c in child.minor_checkin.all|dictsortreversed:"checkin" %} 
            In: {{ c.checkin }}<br/>
            Out: {{c.checkout}}
        {% endfor %}
      </td>
      <td>{{child.last_name}}</td>
      <td>{{child.first_name}}</td>
    </tr>
  {% endfor %}
{% endfor %}

解决方案

1. 构建关联最新签到记录的QuerySet

通过Subquery和Prefetch实现,在获取参与者时直接关联其最新的签到记录,避免模板中循环排序的低效操作:

from django.db.models import Subquery, OuterRef
from django.db.models.query import Prefetch

class WaiverListView(ListView):
    template_name = 'list.html'
    context_object_name = "the_list"

    def get_queryset(self):
        # 子查询:获取每个成人的最新签到记录ID
        latest_adult_checkin = CheckIn.objects.filter(
            adult=OuterRef('pk')
        ).order_by('-checkin').values('pk')[:1]
        
        # 子查询:获取每个未成年人的最新签到记录ID
        latest_minor_checkin = CheckIn.objects.filter(
            minor=OuterRef('pk')
        ).order_by('-checkin').values('pk')[:1]
        
        # 为成人标注最新签到ID,同时预加载未成年人及其最新签到
        return AdultParticipant.objects.annotate(
            latest_checkin_id=Subquery(latest_adult_checkin)
        ).prefetch_related(
            # 预加载未成年人,并为每个未成年人关联最新签到
            Prefetch(
                'minor_of_adult',
                queryset=MinorParticipant.objects.annotate(
                    latest_checkin_id=Subquery(latest_minor_checkin)
                ).prefetch_related(
                    Prefetch(
                        'minor_checkin',
                        queryset=CheckIn.objects.filter(pk=OuterRef('latest_checkin_id')),
                        to_attr='latest_checkin'
                    )
                ),
            ),
            # 为成人关联最新签到
            Prefetch(
                'adult_checkin',
                queryset=CheckIn.objects.filter(pk=OuterRef('latest_checkin_id')),
                to_attr='latest_checkin'
            )
        ).order_by('created')

2. 修改模板代码

直接调用预加载好的最新签到记录,无需循环排序:

{% for adult in the_list %}
  <tr>                          
   <td class="fs-6">
     {% if adult.latest_checkin %}
       In: {{ adult.latest_checkin.0.checkin|date:"Y-m-d H:i" }}<br/>
       Out: {{ adult.latest_checkin.0.checkout|date:"Y-m-d H:i" }}<br/>
     {% else %}
       暂无签到记录
     {% endif %}
   </td>
   <td>{{ adult.last_name }}</td>
   <td>{{ adult.first_name }}</td>
  </tr>

  {% for child in adult.minor_of_adult.all %} 
    <tr>
      <td class="fs-6">
        {% if child.latest_checkin %}
          In: {{ child.latest_checkin.0.checkin|date:"Y-m-d H:i" }}<br/>
          Out: {{ child.latest_checkin.0.checkout|date:"Y-m-d H:i" }}
        {% else %}
          暂无签到记录
        {% endif %}
      </td>
      <td>{{ child.last_name }}</td>
      <td>{{ child.first_name }}</td>
    </tr>
  {% endfor %}
{% endfor %}

关键说明

  • 用Subquery获取每个参与者的最新签到记录ID,确保只关联最新的一条
  • 用Prefetch预加载关联数据,避免N+1查询问题,提升性能
  • 模板中直接访问latest_checkin属性,简化代码逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 06:54:08