Django中如何实现子模型按父级关联序号生成唯一显示ID?
Django实现患者-组织样本的分组唯一标识方案
核心需求回顾
- 患者的
displayed_id由主键哈希生成,避免使用原始PK - 每个患者的样本标识格式为
[患者displayed_id]-[患者内序号],如H6F4A-1、H6F4A-2 - 删除样本后,剩余样本的编号不允许变更(适配物理条形码)
- 多人并发创建样本时,必须保证序号唯一,无竞态条件
可靠实现方案
核心思路是给TissueSample新增一个存储「患者内样本序号」的字段,通过数据库原子操作生成该序号,避免并发冲突,再组合患者的displayed_id得到最终的样本标识。
修改后的模型代码
from django.db import models, transaction # 假设h_encode是你已实现的哈希函数 class Patient(models.Model): # 其他业务字段... displayed_id = models.CharField(max_length=512, editable=False, unique=True) def save(self, *args, **kwargs): # 仅首次创建时生成displayed_id(避免重复哈希) if not self.pk: super().save(*args, **kwargs) self.displayed_id = h_encode(self.pk) super().save(*args, **kwargs) else: super().save(*args, **kwargs) class TissueSample(models.Model): # 其他业务字段... displayed_id = models.CharField(max_length=512, editable=False, unique=True) patient = models.ForeignKey(Patient, on_delete=models.RESTRICT) # 新增字段:存储该患者下的样本序号,仅内部使用 patient_sample_number = models.PositiveIntegerField(editable=False) def save(self, *args, **kwargs): # 仅创建新样本时生成序号和标识 if not self.pk: # 原子事务+行锁,避免并发竞态 with transaction.atomic(): # 锁定当前患者的所有样本记录,强制并发请求排队 patient_samples = TissueSample.objects.filter(patient=self.patient).select_for_update() # 获取当前患者的最大样本序号,无样本则从1开始 max_num = patient_samples.aggregate(max_num=models.Max('patient_sample_number'))['max_num'] or 0 self.patient_sample_number = max_num + 1 # 组合生成最终的样本标识 self.displayed_id = f"{self.patient.displayed_id}-{self.patient_sample_number}" super().save(*args, **kwargs)
方案可靠性说明
- 无竞态条件:
- 使用
transaction.atomic()包裹序号生成逻辑,保证整个操作的原子性 select_for_update()会锁定当前患者的所有样本记录,并发创建请求会等待锁释放后再执行,避免两个请求同时拿到相同的最大序号
- 使用
- 编号稳定性:
patient_sample_number存储在数据库中,删除样本时不会重新分配序号,剩余样本的displayed_id完全保留
- 全局唯一性:
displayed_id设置了unique=True,数据库层面强制保证标识唯一,需确保h_encode是单射函数(不同PK生成不同哈希)
- 性能影响可控:
- 仅针对单个患者的样本记录加锁,不会影响其他患者的样本创建操作,并发场景下性能损耗极低
迁移注意事项
如果是已有数据的系统,需要执行以下步骤:
- 新增
patient_sample_number字段(允许为空,后续填充) - 编写数据迁移脚本,按患者分组,给每个患者的样本按创建时间(或原有逻辑)分配1、2、3...的序号
- 批量更新所有样本的
displayed_id为患者displayed_id-序号的格式 - 修改字段为非空(
null=False)
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

