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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 19:18:01