Django Pharmcare应用重写save()触发UNIQUE约束失败求助
问题:重写Django模型save()方法引发UNIQUE约束错误
问题背景
开发名为pharmcare的Django应用时,为实现药师创建/更新患者记录时自动计算总费用,重写了Patient模型的save()方法来自动计算并保存total字段。但出现以下问题:
- 不重写
save()时,CreateView表单正常工作,但admin面板无法显示计算后的total值 - 重写
save()后,触发错误:UNIQUE constraint failed: pharmcare_patient.id - 移除
save()方法错误消失,但无法自动保存计算结果 - 尝试过
get_or_create无效,且不想对外键使用SET_NULL(希望站点所有者可直接删除药师/组织者及其关联记录),数据库使用PostgreSQL
错误原因分析
- 模型save()方法调用错误:重写的
save()方法中,调用super().save(self, *args, **kwargs)时错误地传递了self参数。Django模型的save()是实例方法,通过super()调用父类方法时不需要显式传递self,多余的self会被当作force_insert参数传入,导致Django尝试强制插入一条已存在主键的记录,触发UNIQUE约束冲突。 - 视图form_valid()重复保存:在
PatientCreateView的form_valid()方法中,先执行了form.save(),之后又调用super().form_valid(form),而CreateView的默认form_valid()逻辑会再次调用form.save(),导致同一对象被重复保存,加剧主键冲突问题。
解决方案
- 修正模型
save()方法的super调用,移除多余的self参数 - 优化视图
form_valid()逻辑,避免重复保存对象
修正后的代码
修正后的Patient模型
class Patient(models.Model): medical_charge = models.PositiveBigIntegerField(blank=True, null=True, verbose_name="amount paid (medical charge if any)") notes = models.TextField(null=True, blank=True) pharmacist = models.ForeignKey( "Pharmacist", on_delete=models.SET_NULL, null=True, blank=True) organization = models.ForeignKey( 'leads.UserProfile', on_delete=models.CASCADE) user = models.ForeignKey( 'songs.User', on_delete=models.CASCADE) patient = models.ForeignKey( 'PatientDetail', on_delete=models.CASCADE, verbose_name='Patient-detail') medical_history = models.ForeignKey( 'MedicationHistory', on_delete=models.CASCADE) total = models.PositiveBigIntegerField(editable=True, blank=True, null=True, verbose_name="Total (auto-add)") slug = models.SlugField(null=True, blank=True) date_created = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['id', '-date_created'] def __str__(self): return self.patient.first_name def get_total_charge(self) -> int: total = self.patient.consultation # 检查是否有额外医疗费用,有的话累加 if self.medical_charge: total += self.medical_charge return total def save(self, *args, **kwargs): """重写save方法,自动计算total并生成slug""" self.total = self.get_total_charge() self.slug = slug_modifier() # 假设slug_modifier是已实现的生成slug的函数 # 移除多余的self参数,正确调用父类save方法 super().save(*args, **kwargs)
修正后的PatientCreateView视图
class PatientCreateView(OrganizerPharmacistLoginRequiredMixin, CreateView): """处理管理员/药师创建患者的请求响应""" template_name = 'pharmcare/patients/patient-info-create.html' form_class = PatientModelForm def get_queryset(self): organization = self.request.user.userprofile user = self.request.user if user.is_organizer or user.is_pharmacist: return Patient.objects.filter(organization=organization) else: return Patient.objects.filter( pharmacist__organization=user.pharmacist.organization, pharmacist__user=user ) def form_valid(self, form: BaseModelForm) -> HttpResponse: user = self.request.user # 设置关联字段,不立即保存 form.instance.user = user form.instance.organization = user.userprofile # 调用父类form_valid,由其完成保存逻辑,避免重复保存 return super().form_valid(form) def get_success_url(self) -> str: return reverse('pharmcare:patient-info')
额外说明
- 简化了
get_total_charge()方法的逻辑,去除冗余代码 - 优化了
get_queryset()的写法,合并Filter条件更简洁 - 确保
slug_modifier()函数能生成唯一slug,避免slug重复问题
内容的提问来源于stack exchange,提问作者Blaisemart
相关产品推荐
相关产品推荐

