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

Django模型:如何区分数据条目是更新操作还是新增插入操作?

嘿,在Django里区分模型的新增和更新操作其实非常直观,核心就是利用模型实例自带的状态标志。结合你给出的Shipment模型和业务需求,我帮你整理了一套完整的实现方案,直接就能用:

区分Django Shipment模型新增与更新操作的实现方案

核心判断逻辑

Django为模型实例提供了两种可靠的判断方式:

  • 检查_state.adding属性:这是Django内部维护的状态标记,instance._state.adding为True表示当前是新增操作,False则是更新操作(推荐用这个,能覆盖手动设置主键但未保存的边缘场景)。
  • 检查主键值:新增实例未保存时,主键(你的模型里是id)为None;已存在的实例主键有具体值。

最优雅的实现方式是重写模型的save()方法,在方法内部加入判断逻辑,根据新增/更新场景执行对应的业务逻辑。

修改后的Shipment模型代码

import uuid
from decimal import Decimal
from django.db import models
from django.utils import timezone

# 假设Trailer和TrailerTrip模型已定义,这里补全基础结构
class Trailer(models.Model):
    number = models.CharField(max_length=50, unique=True)
    # 其他业务字段...

class TrailerTrip(models.Model):
    trailer = models.ForeignKey(Trailer, on_delete=models.PROTECT)
    shipment = models.ForeignKey('Shipment', on_delete=models.SET_NULL, null=True, blank=True)
    # 其他业务字段...

class Shipment(models.Model):
    CARRIER_CHOICES = [
        ('GW', 'Greatwide'),
        ('SM', 'Sample'),
    ]
    dateTendered = models.DateField(default=timezone.now)
    loadNumber = models.CharField(max_length=50)
    masterBolNumber = models.CharField(max_length=50)
    carrier = models.CharField(max_length=100, blank=True, choices=CARRIER_CHOICES, default='GW')
    destinationCity = models.CharField(max_length=70)
    destinationState = models.CharField(max_length=50)
    rateLineHaul = models.DecimalField(max_digits=15, decimal_places=2)
    rateFSC = models.DecimalField(max_digits=15, decimal_places=2)
    rateExtras = models.DecimalField(max_digits=15, decimal_places=2, default=0.00)
    rateTotal = models.DecimalField(max_digits=15, decimal_places=2, blank=True, null=True, default=0.00)
    loadDelivered = models.BooleanField(default=False)
    customCarrierRate = models.BooleanField(default=False)
    deliveryDate = models.DateField(null=True, blank=True)
    deliveryTime = models.TimeField(null=True, blank=True)
    driverName = models.CharField(max_length=70, null=True, blank=True)
    driverCell = models.CharField(max_length=70, null=True, blank=True)
    rateTotalCarrier = models.DecimalField(max_digits=15, decimal_places=2, null=True, default=0)
    shipmentMargin = models.DecimalField(max_digits=15, decimal_places=2, null=True, blank=True)
    shipmentMarginPercentage = models.DecimalField(max_digits=15, decimal_places=3, null=True, blank=True)
    trailer = models.ForeignKey(Trailer, on_delete=models.PROTECT, blank=True, null=True)
    id = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False)

    def calcRate(self):
        # 将全局函数改为实例方法,直接操作当前实例属性
        self.rateTotal = self.rateLineHaul + self.rateFSC + self.rateExtras
        if not self.customCarrierRate:
            rate_check_result = checkRate(self.destinationState)
            if not rate_check_result:
                self.rateTotalCarrier = ((self.rateLineHaul * Decimal(0.8)) + self.rateFSC + self.rateExtras) - Decimal(250)
            else:
                rateList = rate_check_result
                miles = self.rateLineHaul / Decimal(rateList[1])
                linehaul = Decimal(miles) * Decimal(rateList[2])
                self.rateTotalCarrier = ((linehaul * Decimal(0.8)) + self.rateFSC + self.rateExtras) - Decimal(250)

    def calcShipmentMargin(self):
        # 新增空值判断,避免除以零或空值报错
        if self.rateTotal and self.rateTotalCarrier is not None and self.rateTotal != 0:
            self.shipmentMargin = self.rateTotal - self.rateTotalCarrier
            self.shipmentMarginPercentage = (self.shipmentMargin / self.rateTotal) * 100

    def updateTrailerTrip(self):
        if self.trailer:
            try:
                trailerTrip = TrailerTrip.objects.get(trailer=self.trailer)
                trailerTrip.shipment = self
                trailerTrip.save()
            except TrailerTrip.DoesNotExist:
                # 可根据业务需求添加创建新TrailerTrip的逻辑,或跳过
                pass

    def save(self, *args, **kwargs):
        is_new = self._state.adding
        old_trailer = None

        # 更新场景:先获取旧实例状态,处理前置逻辑
        if not is_new:
            old_instance = Shipment.objects.get(pk=self.pk)
            old_trailer = old_instance.trailer

            # 场景a/c:修改承运人费率或基础费率时
            if (self.rateTotalCarrier != old_instance.rateTotalCarrier) or \
               (self.rateLineHaul != old_instance.rateLineHaul) or \
               (self.rateFSC != old_instance.rateFSC) or \
               (self.rateExtras != old_instance.rateExtras):
                self.customCarrierRate = True
                self.calcRate()
                self.calcShipmentMargin()

            # 场景b:修改挂车时,先清空旧挂车的关联
            if self.trailer != old_trailer and old_trailer:
                try:
                    old_trip = TrailerTrip.objects.get(trailer=old_trailer)
                    old_trip.shipment = None
                    old_trip.save()
                except TrailerTrip.DoesNotExist:
                    pass

        # 新增场景:处理前置计算逻辑
        else:
            self.calcRate()

            # 场景b:勾选customCarrierRate时
            if self.customCarrierRate:
                self.rateTotalCarrier = Decimal(0.00)
            else:
                # 场景c:手动输入rateTotalCarrier时
                if self.rateTotalCarrier != 0:
                    self.customCarrierRate = True
                self.calcShipmentMargin()

        # 先保存实例,确保新增场景能获取到主键
        super().save(*args, **kwargs)

        # 保存后处理关联逻辑
        if is_new:
            # 场景d:新增时选择挂车,更新TrailerTrip
            if self.trailer:
                self.updateTrailerTrip()
        else:
            # 更新场景:修改挂车后,更新新挂车的关联
            if self.trailer != old_trailer and self.trailer:
                self.updateTrailerTrip()

关键细节说明

  1. 实例化辅助函数:把原来的全局函数改成模型实例方法,减少数据库查询次数,代码更简洁易维护。
  2. 异常处理:操作TrailerTrip时加入try-except,避免因找不到对应记录导致保存失败。
  3. 逻辑顺序:严格按照业务场景的先后顺序执行,比如更新场景先处理旧关联,再保存,最后更新新关联。
  4. 空值防护:在利润计算时加入空值判断,避免运行时报错。

辅助函数调整(可选)

如果checkRate函数和Shipment业务紧密相关,可以改成模型的静态方法:

@staticmethod
def checkRate(destination_state):
    # 原checkRate的业务逻辑,返回rateList或False
    # 示例:
    # if destination_state == 'CA':
    #     return ['CA', 2.5, 1.8]
    # return False

内容的提问来源于stack exchange,提问作者Nicholas Tallarico

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.01 02:17:43