Django如何在模板for循环中过滤展示两个关联模型数据
Django 患者-就诊记录关联展示方案
问题核心
原有实现同时全量查询两张表数据传入模板,手动循环匹配时未利用ORM外键关联能力,要么出现患者总数×就诊记录总数的笛卡尔积错误,要么无法正确匹配单条就诊记录对应的患者信息。
分步实现
1. 模型层修正
原模型存在命名不规范、冗余方法、外键字段定义不合理的问题,修正后代码如下:
from django.db import models class PatientProfile(models.Model): pat_Fname = models.CharField(max_length=100, verbose_name="患者名") pat_Lname = models.CharField(max_length=100, blank=True, verbose_name="患者姓") class Meta: verbose_name = "患者档案" verbose_name_plural = verbose_name class Visits(models.Model): # 定义外键,指定related_name用于反向从患者查询关联就诊记录 pat = models.ForeignKey( PatientProfile, on_delete=models.CASCADE, related_name="visits", verbose_name="所属患者" ) visit_date = models.DateField(blank=False, null=False, verbose_name="就诊日期") visit_time = models.TimeField(blank=False, null=False, verbose_name="就诊时间") class Meta: verbose_name = "就诊记录" verbose_name_plural = verbose_name
说明:原模型中自定义的
IntegerField方法无实际作用,Django默认会为模型生成自增id主键,无需额外定义;外键字段不需要手动加_ID后缀,Django会自动在数据库层生成带_id的关联字段。
2. 视图层写法
根据展示需求二选一即可,两种写法都通过ORM提供的关联查询优化接口避免N+1查询问题,不需要同时传入两张表的全量数据:
- 需求1:按就诊记录维度展示,每条记录对应一行,展示所属患者信息
from django.contrib.auth.decorators import login_required from .models import Visits @login_required def visitslist(request): # select_related通过JOIN一次性查出每条就诊记录关联的患者数据 vis = Visits.objects.select_related('pat').all() return render(request, 'pmp/visitslist.html', {'vis': vis})
- 需求2:按患者维度展示,每个患者下面列出自己的所有就诊记录,无记录则提示
from django.contrib.auth.decorators import login_required from .models import PatientProfile @login_required def visitslist(request): # prefetch_related预取所有患者关联的就诊记录,在Python层完成匹配 patpro = PatientProfile.objects.prefetch_related('visits').all() return render(request, 'pmp/visitslist.html', {'patpro': patpro})
3. 模板层写法
对应上面两种视图场景,直接通过ORM关联关系取数即可,不需要手动过滤匹配:
对应需求1(就诊记录维度)
直接通过外键字段pat访问关联的患者属性,不会出现笛卡尔积:
<table border="1" cellpadding="8" cellspacing="0"> <thead> <tr> <th>就诊时间</th> <th>就诊日期</th> <th>患者ID</th> <th>患者姓名</th> </tr> </thead> <tbody> {% for visit in vis %} <tr> <td>{{ visit.visit_time }}</td> <td>{{ visit.visit_date }}</td> <td>{{ visit.pat.id }}</td> <td>{{ visit.pat.pat_Fname }} {{ visit.pat.pat_Lname }}</td> </tr> {% empty %} <tr> <td colspan="4" align="center">暂无就诊记录</td> </tr> {% endfor %} </tbody> </table>
对应需求2(患者维度)
两层循环仅遍历当前患者自己的关联就诊记录,不会出现全量匹配的笛卡尔积问题:
{% for patient in patpro %} <div style="margin: 16px 0;"> <h4>患者信息:{{ patient.pat_Fname }} {{ patient.pat_Lname }}(ID:{{ patient.id }})</h4> <table border="1" cellpadding="8" cellspacing="0"> <thead> <tr> <th>就诊日期</th> <th>就诊时间</th> </tr> </thead> <tbody> {% for visit in patient.visits.all %} <tr> <td>{{ visit.visit_date }}</td> <td>{{ visit.visit_time }}</td> </tr> {% empty %} <tr> <td colspan="2" align="center">该患者暂无就诊记录</td> </tr> {% endfor %} </tbody> </table> </div> {% endfor %}
注意事项
- 多对一/一对一关联查询(如从就诊记录查所属患者)优先用
select_related,通过SQL JOIN一次取数,性能更高 - 一对多/多对多关联查询(如从患者查所有就诊记录)优先用
prefetch_related,分两次查询后在内存中匹配,避免大表JOIN性能问题 - 不要在模板中手动做跨表数据过滤匹配,应该在视图层通过ORM完成数据关联后再传入模板,避免出现笛卡尔积或额外的性能开销
内容的提问来源于stack exchange,提问作者Saravanan Ragavan
相关产品推荐
相关产品推荐

