Django post_save() 信号实现

Posted

技术标签:

【中文标题】Django post_save() 信号实现【英文标题】:Django post_save() signal implementation 【发布时间】:2012-10-12 10:31:44 【问题描述】:

我有一个关于 django 的问题。

我这里有 ManyToMany 模型

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)

对于创建的 1 个购物车对象,我可以插入尽可能多的新 TransactionDetail 对象(产品和金额)。我的问题是。如何实现触发器?我想要的是每当创建交易详细信息时,我希望产品的库存量减去交易详细信息中的金额。

我已经阅读了关于 post_save() 的信息,但我不确定如何实现它。 也许是这样的

何时:

post_save(TransactionDetail, 
       Cart) #Cart object where TransactionDetail.cart= Cart.id
Cart.stock -= TransactionDetail.amount

【问题讨论】:

如果你这样做,你可能会遇到竞争条件。 【参考方案1】:

如果你真的想使用信号来实现这一点,这里简要介绍一下方法,

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()

【讨论】:

这对我来说很好,但不知道为什么它在未知长度的循环中 我收到maximum recursion depth exceeded 错误,因为我将实例本身保存在@receiver 函数中。如何实现更新自我模型?我是否必须覆盖模型的save() 方法? @Dipak 超出最大递归深度,因为每次更新实例时都会触发 post_save,因此它会在每次保存时调用自身,在递归深度值超过异常的状态下会出现异常。告诉我你是怎么克服的 @VamsidharMuggulla 我没有使用signal,而是覆盖了模型的save方法并使用updated函数更新了模型属性,这样就不会再次触发保存。 dispatch_uid 是干什么用的?【参考方案2】:

我个人会覆盖 TransactionDetail 的 save() 方法并在那里保存新的 TransactionDetail 然后运行

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

【讨论】:

这是否有效,它是否适当地处理数据库事务/回滚?【参考方案3】:

如果你想避免得到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。

【讨论】:

为了让它工作,你需要在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") `跨度> 不要这样做。断开信号可能会导致并发问题。特别是在给定代码示例所暗示的金融应用程序中!如果两个TransactionDetail 对象几乎同时保存会怎样?如果第一个断开信号,当第二个的 save() 被触发时,将不会收到信号,因为它断开了!处理这个问题的正确方法是使用 queryset.update(),它可以让你“保存”一个对象实例而不调用 save()。另见:***.com/a/45895528/4028977【参考方案4】:

如果你真的想在 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

然后在init文件中添加default_app_config

 default_app_config = "give your AppConfig path"

【讨论】:

【参考方案5】:

事实上,docstrstring 解释了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.

【讨论】:

以上是关于Django post_save() 信号实现的主要内容,如果未能解决你的问题,请参考以下文章

Django 从 post_save 信号访问 ManyToMany 字段

识别 django post_save 信号中更改的字段

Django中的信号简介

Django:如何在 post_save 信号中访问原始(未修改)实例

忽略 django 的 post_save 信号中对 m2m 关系的更改

Django post_save 信号和 celery 任务之间可能的竞争条件