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()
关键细节说明
- 实例化辅助函数:把原来的全局函数改成模型实例方法,减少数据库查询次数,代码更简洁易维护。
- 异常处理:操作
TrailerTrip时加入try-except,避免因找不到对应记录导致保存失败。 - 逻辑顺序:严格按照业务场景的先后顺序执行,比如更新场景先处理旧关联,再保存,最后更新新关联。
- 空值防护:在利润计算时加入空值判断,避免运行时报错。
辅助函数调整(可选)
如果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
相关产品推荐
相关产品推荐

