Django模型字段设置一种折扣后如何禁止另一折扣字段赋值
最优方案选择
优先选择方案1(保存时互斥清空另一折扣)+ 数据库级互斥约束 + 基础计算逻辑校验的组合方案,比单独使用方案2更稳妥,原因如下:
- 仅用方案2的话,数据库会长期存在冗余的折扣数据,运营后台、统计脚本或者其他业务逻辑如果没遵循你
final_price里的优先级规则,直接读取折扣字段计算就会出现数据错误,排查成本极高 - 仅用方案1的话,如果遇到
bulk_update、管理员直接在数据库操作、或者其他绕开model save方法的修改操作,还是可能出现两种折扣同时存在的异常情况,需要加数据库层兜底
具体实现优化
你当前的代码已经实现了方案1的save逻辑,只需要补充数据库约束和边界处理即可,优化后代码如下:
from django.db import models from django.core.validators import MinValueValidator class Product(models.Model): class Meta: verbose_name = '商品' verbose_name_plural = '商品' ordering = ('created_at',) # 新增数据库层面的互斥约束,兜底保证两种折扣不会同时存在 constraints = [ models.CheckConstraint( check= models.Q(cash_code__isnull=True) | models.Q(percentage_code__isnull=True), name='discount_mutually_exclusive' ) ] # 其余原有字段保持不变 name = models.CharField(max_length=50) category = models.ForeignKey('product.Category', on_delete=models.CASCADE, related_name='products') brand = models.ForeignKey('product.Brand', on_delete=models.CASCADE, related_name='products') label = models.CharField(choices=LABEL, max_length=1) image = models.ImageField(upload_to='product/static/product/images/', null=True, blank=True) description = models.TextField(max_length=1000, null=True, blank=True) is_original = models.BooleanField() inventory = models.PositiveIntegerField(default=0) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True, null=True, blank=True) # 单位价格加最小值校验,避免出现负单价 unit_price = models.DecimalField(max_digits=8, decimal_places=2, validators=[MinValueValidator(0)]) number_sold = models.PositiveIntegerField() cash_code = models.ForeignKey('cart.CashDiscount', null=True, on_delete=models.CASCADE, blank=True, related_name='related_products') percentage_code = models.ForeignKey('cart.PercentageDiscount', null=True, on_delete=models.CASCADE, related_name='related_products', blank=True) expiration_date = models.DateField(null=True, blank=True) slug = AutoSlugField(populate_from=['name', 'brand'], allow_unicode=True, unique=True) @property def final_price(self): sale_price = self.get_sale() return sale_price if sale_price is not None else self.unit_price def get_sale(self): # 优先计算现金折扣,如需调整优先级修改判断顺序即可 if self.cash_code: # 避免折扣后价格为负 discount_amount = self.cash_code.amount return max(self.unit_price - discount_amount, 0) elif self.percentage_code: # 避免百分比超过100%导致负价格 discount_percent = min(self.percentage_code.percentage, 100) discount_amount = (discount_percent * self.unit_price) / 100 return max(self.unit_price - discount_amount, 0) return None def save(self, *args, **kwargs): # 简化原有逻辑,无需额外处理都为None的情况 if self.cash_code: self.percentage_code = None elif self.percentage_code: self.cash_code = None super().save(*args, **kwargs)
内容的提问来源于stack exchange,提问作者Sarah Ghanei
相关产品推荐
相关产品推荐

