如何在Django视图中通过QuerySet筛选访问Django模型的@property属性
你遇到的错误Related Field got invalid lookup: patient_number,原因很明确:Django的ORM查询是直接转换成SQL语句执行的,而patient_number是Python层面定义的@property属性,数据库里根本没有这个字段,所以ORM没办法直接对它进行过滤操作。
下面给你三个可行的解决方案,你可以根据自己的业务场景选择:
方案一:解析patient_number格式,直接过滤主键
既然你的patient_number是固定格式PN/D/XXXX(XXXX是4位的Patient主键),那我们可以从用户输入的pn里提取出主键值,然后直接用Patient的主键来筛选,这是性能最优的方案。
修改你的视图函数:
def sort_by_pt(request): if request.method=='POST': pn=request.POST.get('enter_patient_number') report = IpdReport.objects.none() # 默认返回空结果 try: # 拆分字符串获取主键部分,转成整数 pk_segment = pn.split('/')[-1] patient_pk = int(pk_segment) # 直接用主键筛选,高效且可靠 report = IpdReport.objects.filter(patient__pk=patient_pk) except (IndexError, ValueError): # 处理输入格式错误的情况,比如用户输入不符合预期格式 pass total1=report.aggregate(Sum('realization__amount_received')) total2=report.aggregate(Sum('realization__deficit_or_surplus_amount')) context={'report': report, 'total1':total1, 'total2':total2} return render(request, 'account/ipdreport.html', context) else: sort=EnterPatientNumber() return render(request, 'account/sort.html', {'sort':sort})
优点:不需要修改模型,查询效率极高(主键查询是数据库最快的操作);
缺点:依赖patient_number的固定格式,如果以后格式变更,解析逻辑也要跟着调整。
方案二:把patient_number改成数据库字段
如果希望以后能直接用patient_number做ORM查询,或者担心格式会变化,最好的办法是把这个属性变成真正的数据库字段,在保存Patient的时候自动生成值。
修改你的Patient模型:
from django.db.models.signals import post_save from django.dispatch import receiver class Patient(models.Model): name=models.CharField(max_length=100) mr_uid=models.CharField(max_length=9) # 添加数据库存储的patient_number字段,设置唯一约束避免重复 patient_number = models.CharField(max_length=20, unique=True, blank=True, editable=False) def __str__(self): return self.name+"-"+self.patient_number # 用信号在Patient保存后生成patient_number(避免save方法被重写时出问题) @receiver(post_save, sender=Patient) def generate_patient_number(sender, instance, created, **kwargs): if created and not instance.patient_number: # 生成格式为PN/D/000X的编号 instance.patient_number = f"PN/D/{instance.pk:04d}" # 再次保存(仅更新patient_number字段,避免触发信号循环) instance.save(update_fields=['patient_number'])
然后执行数据库迁移:
python manage.py makemigrations python manage.py migrate
之后你的视图函数就可以直接用原来的代码了:
report=IpdReport.objects.filter(patient__patient_number=pn)
优点:查询逻辑直观,不依赖格式解析,适合频繁用这个字段查询的场景;
缺点:需要修改模型并迁移数据库,还要处理已有数据的编号生成(可以写个脚本批量给旧Patient生成patient_number)。
方案三:Python层面过滤(不推荐大数据量)
如果只是临时测试或者数据量很小,可以先把所有数据拉到内存里,再用Python的过滤方法筛选。但这个方案性能很差,数据多的时候会严重拖慢速度,不建议在生产环境使用。
示例代码:
def sort_by_pt(request): if request.method=='POST': pn=request.POST.get('enter_patient_number') # 用select_related减少数据库查询次数,避免N+1问题 all_reports = IpdReport.objects.all().select_related('patient') # 在Python层面过滤符合条件的记录 report = [r for r in all_reports if r.patient.patient_number == pn] # 手动计算聚合值,因为现在report是列表不是QuerySet了 total1 = sum(r.realization.amount_received for r in report if hasattr(r, 'realization')) total2 = sum(r.realization.deficit_or_surplus_amount for r in report if hasattr(r, 'realization')) # 保持context和原来的结构一致,方便模板使用 context={ 'report': report, 'total1': {'realization__amount_received__sum': total1}, 'total2': {'realization__deficit_or_surplus_amount__sum': total2} } return render(request, 'account/ipdreport.html', context) else: sort=EnterPatientNumber() return render(request, 'account/sort.html', {'sort':sort})
优点:不需要修改模型,快速实现;
缺点:数据量大时性能极差,内存占用高。
内容的提问来源于stack exchange,提问作者noob87

