37

有许多关于使用post_save信号进行递归的 Stack Overflow 帖子,其评论和答案绝大多数是:“为什么不覆盖 save()”或仅触发的保存created == True

好吧,我相信有一个不使用的好案例save()——例如,我正在添加一个临时应用程序,它处理与我们的 Order 模型完全分开的订单履行数据。

框架的其余部分完全不知道履行应用程序,并且使用 post_save 挂钩将所有与履行相关的代码与我们的订单模型隔离开来。

如果我们放弃履行服务,我们的核心代码无需更改。我们删除履行应用程序,仅此而已。

那么,是否有任何体面的方法来确保 post_save 信号不会触发同一个处理程序两次?

4

10 回答 10

91

您可以在信号处理程序中使用更新而不是保存

queryset.filter(pk=instance.pk).update(....)
于 2012-05-31T19:51:21.307 回答
39

不要断开信号。如果在信号断开时生成任何相同类型的新模型,则不会触发处理函数。信号在 Django 中是全局的,多个请求可以同时运行,导致一些请求失败,而另一些则运行它们的 post_save 处理程序。

于 2013-11-12T17:47:05.297 回答
37

你觉得这个解决方案怎么样?

@receiver(post_save, sender=Article)
def generate_thumbnails(sender, instance=None, created=False, **kwargs):

    if not instance:
        return

    if hasattr(instance, '_dirty'):
        return

    do_something()

    try:
        instance._dirty = True
        instance.save()
    finally:
        del instance._dirty

您还可以创建装饰器

def prevent_recursion(func):

    @wraps(func)
    def no_recursion(sender, instance=None, **kwargs):

        if not instance:
            return

        if hasattr(instance, '_dirty'):
            return

        func(sender, instance=instance, **kwargs)

        try:
            instance._dirty = True
            instance.save()
        finally:
            del instance._dirty

    return no_recursion


@receiver(post_save, sender=Article)
@prevent_recursion
def generate_thumbnails(sender, instance=None, created=False, **kwargs):

    do_something()
于 2015-02-06T16:07:09.073 回答
27

我认为save_without_signals()在模型上创建一个方法更明确:

class MyModel()
    def __init__():
        # Call super here.
        self._disable_signals = False

    def save_without_signals(self):
        """
        This allows for updating the model from code running inside post_save()
        signals without going into an infinite loop:
        """
        self._disable_signals = True
        self.save()
        self._disable_signals = False

def my_model_post_save(sender, instance, *args, **kwargs):
    if not instance._disable_signals:
        # Execute the code here.
于 2013-10-31T12:28:37.417 回答
19

如何断开然后重新连接post_save函数中的信号:

def my_post_save_handler(sender, instance, **kwargs):
    post_save.disconnect(my_post_save_handler, sender=sender)
    instance.do_stuff()
    instance.save()
    post_save.connect(my_post_save_handler, sender=sender)
post_save.connect(my_post_save_handler, sender=Order)
于 2012-05-31T19:43:25.280 回答
4

您还可以检查raw参数post_save,然后调用save_base而不是save.

于 2014-03-21T13:31:16.137 回答
4

您应该使用 queryset.update() 而不是 Model.save() 但您需要处理其他事情:

需要注意的是,在使用的时候,如果要使用新的对象,应该重新获取他的对象,因为它不会改变self对象,例如:

>>> MyModel.objects.create(pk=1, text='')
>>> el = MyModel.objects.get(pk=1)
>>> queryset.filter(pk=1).update(text='Updated')
>>> print el.text
>>> ''

所以,如果你想使用新对象,你应该再做一次:

>>> MyModel.objects.create(pk=1, text='')
>>> el = MyModel.objects.get(pk=1)
>>> queryset.filter(pk=1).update(text='Updated')
>>> el = MyModel.objects.get(pk=1) # Do it again
>>> print el.text
>>> 'Updated'
于 2014-01-15T19:43:47.577 回答
0

模型的.objects.update()方法绕过了post_save信号

试试这个:

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


class MyModel(models.Model):

    name = models.CharField(max_length=200)
    num_saves = models.PositiveSmallIntegerField(default=0)

    @classmethod
    def post_save(cls, sender, instance, created, *args, **kwargs):
        MyModel.objects.filter(id=instance.id).update(save_counter=instance.save_counter + 1)

post_save.connect(MyModel.post_save, sender=MyModel)

在这个例子中,一个对象有一个名字,每次.save()被调用时,.num_saves属性都会增加,但没有递归。

于 2020-10-28T17:00:49.830 回答
0

看一下这个...

正如您可以在此处的文档中阅读的那样,每个信号都有其自身的好处,但我想分享一些关于 pre_save 和 post_save 信号的注意事项。

  • 每次调用模型上的 .save() 时都会调用两者。换句话说,如果您保存模型实例,则会发送信号。

  • 在 post_save 内的实例上运行 save() 通常会创建一个永无止境的循环,因此会导致超出最大递归深度错误 --- 仅当您没有正确使用 .save() 时。

  • pre_save 非常适合仅更改实例数据,因为您不必调用 save() ,这消除了上述可能性。您不必调用 save() 的原因是因为 pre_save 信号的字面意思是在被保存之前。

  • 信号可以调用其他信号和/或运行延迟任务(对于 Celery),这对于可用性来说非常重要。

资料来源:https ://www.codingforentrepreneurs.com/blog/post-save-vs-pre-save-vs-override-save-method/

问候!!

于 2018-02-21T04:44:58.653 回答
0

在 django 中的 post_save 信号中,为了避免递归“如果已创建”,需要检查

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

@receiver(post_save, sender=DemoModel)
def _post_save_receiver(sender,instance,created, **kwargs):
    if created:            
       print('hi..')
       instance.save()
于 2021-01-17T16:45:14.070 回答