You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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(),导致同一对象被重复保存,加剧主键冲突问题。

解决方案

  1. 修正模型save()方法的super调用,移除多余的self参数
  2. 优化视图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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 04:07:15