Django post_save() 信号的实现

95

我有一个关于Django的问题。

我这里有许多对多模型。

class Product(models.Model):
     name = models.CharField(max_length=255)
     price = models.DecimalField(default=0.0, max_digits=9, decimal_places=2)
     stock = models.IntegerField(default=0)

     def  __unicode__(self):
         return self.name

class Cart(models.Model):
    customer = models.ForeignKey(Customer)
    products = models.ManyToManyField(Product, through='TransactionDetail')
    t_date = models.DateField(default=datetime.now())
    t_sum = models.FloatField(default=0.0)

    def __unicode__(self):
         return str(self.id)

class TransactionDetail(models.Model):
    product = models.ForeignKey(Product)
    cart = models.ForeignKey(Cart)
    amount = models.IntegerField(default=0)
对于一个创建的购物车对象,我可以插入尽可能多的新的TransactionDetail对象(即产品和数量)。我的问题是,如何实现触发器?我想要的是每当创建交易详细信息时,产品库存的数量应该减去交易详细信息中的数量。
我已经了解了post_save(),但不确定如何实现它。也许像这样:
when:
post_save(TransactionDetail, 
       Cart) #Cart object where TransactionDetail.cart= Cart.id
Cart.stock -= TransactionDetail.amount

6
如果你那样做的话,很可能会遇到竞态条件。 - Thomas Orozco
5个回答

206

如果你真的想使用信号来实现这一点,以下是简要描述:

from django.db.models.signals import post_save
from django.dispatch import receiver

class TransactionDetail(models.Model):
    product = models.ForeignKey(Product)

# method for updating
@receiver(post_save, sender=TransactionDetail, dispatch_uid="update_stock_count")
def update_stock(sender, instance, **kwargs):
    instance.product.stock -= instance.amount
    instance.product.save()

1
这对我来说很好用,但不知道为什么它会进入未知长度的循环。 - Manoj Sahu
8
我遇到了“maximum recursion depth exceeded”错误,因为我在“@receiver”函数中保存了实例本身。我该如何更新self模型?我需要覆盖models的“save()”方法吗? - Dipak
2
@Dipak,由于每次更新实例时都会触发post_save,因此在每次保存时都会调用自身,导致递归深度超过最大值,从而引发异常。请告诉我您是如何克服这个问题的。 - Vamsidhar Muggulla
3
@VamsidharMuggulla,我覆盖了模型的save方法,使用updated函数更新了模型属性,而不是使用signal,这样就不会再次触发保存。 - Dipak
14
dispatch_uid 是用来干什么的? - R11G
显示剩余5条评论

22

就我个人而言,我会重写TransactionDetail的save()方法,在其中保存新的TransactionDetail并运行。

self.product.stock -= self.amount
self.product.save()

这个代码是否有效并且是否适当地处理了数据库事务/回滚? - blong
2
如果整个事务以原子模式运行,则返回“是”。 - Mikael

19

如果你想避免出现“maximum recursion depth exceeded”的错误提示,那么在信号处理程序中保存数据之前,你应该断开信号。以上示例(Kenny Shen的回答)将变为:

from django.db.models.signals import post_save
from django.dispatch import receiver

class TransactionDetail(models.Model):
    # ... fields here

# method for updating
@receiver(post_save, sender=TransactionDetail, dispatch_uid="update_stock_count")
def update_stock(sender, instance, **kwargs):
 instance.product.stock -= instance.amount

 post_save.disconnect(update_stock, sender=TransactionDetail)
 instance.product.save()
 post_save.connect(update_stock, sender=TransactionDetail)

这个问题在Disconnect signals for models and reconnect in django中得到了详细的阐述,其中还包括一个更抽象和有用的例子。

另外,还可以参考django文档中的https://docs.djangoproject.com/en/2.0/topics/signals/#disconnecting-signals


2
为使此代码生效,您需要在 disconnectconnect 方法中都包含 dispatch_uid。返回值可帮助您处理潜在的错误。 is_disconnected = post_save.disconnect(update_stock, sender=TransactionDetail, dispatch_uid="update_stock_count") if is_diconnected: instance.product.save() is_reconnected = post_save.connect(update_stock, sender=TransactionDetail, dispatch_uid="update_stock_count") - Califlower
不要这样做。断开信号可能会导致并发问题。特别是在给定代码示例所暗示的金融应用中!如果两个“TransactionDetail”对象在彼此附近的时间内保存,那怎么办?如果第一个断开信号,当第二个的save()被触发时,由于已经断开了信号,因此不会接收到信号!处理这种情况的正确方法是使用queryset.update(),它允许您“保存”对象实例而不调用save()。另请参阅:https://stackoverflow.com/a/45895528/4028977 - Rob

6

如果你真的想在Django中使用信号,请尝试以下方法:

#import inbuilt user model
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=User)
def create_profile(sender, **kwargs):
    # write you functionality
    pass
   

然后在初始化文件中添加 default_app_config

 default_app_config = "give your AppConfig path"

3
事实上,文档字符串解释了 Signalsdjango.dispatch.Signal.connect 中的作用:
def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
    Connect receiver to sender for signal.

    Arguments:

        receiver
            A function or an instance method which is to receive signals.
            Receivers must be hashable objects.

            If weak is True, then receiver must be weak referenceable.

            Receivers must be able to accept keyword arguments.

            If a receiver is connected with a dispatch_uid argument, it
            will not be added if another receiver was already connected
            with that dispatch_uid.

        sender
            The sender to which the receiver should respond. Must either be
            a Python object, or None to receive events from any sender.

        weak
            Whether to use weak references to the receiver. By default, the
            module will attempt to use weak references to the receiver
            objects. If this parameter is false, then strong references will
            be used.

        dispatch_uid
            An identifier used to uniquely identify a particular instance of
            a receiver. This will usually be a string, though it may be
            anything hashable.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接