如何构建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
相关产品推荐
相关产品推荐

