Django post_save防止递归而不覆盖模型save()

Yuj*_*ita 32 python django django-signals

有许多关于使用post_save信号递归的Stack Overflow帖子,评论和答案都是压倒性的:"为什么不覆盖save()"或者只是触发的保存created == True.

我相信不使用的好例子save()- 例如,我正在添加一个临时应用程序来处理与Order模型完全分开的订单履行数据.

框架的其余部分幸福地没有意识到实现应用程序,并且使用post_save挂钩从我们的订单模型中隔离所有与履行相关的代码.

如果我们放弃履行服务,我们的核心代码就不得不改变.我们删除了履行应用程序,就是这样.

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

mos*_*lix 79

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

queryset.filter(pk=instance.pk).update(....)

  • 嗯,但是如果某些*其他*信号处理程序执行`save()`而不是`update()`会怎么样? (2认同)

小智 36

不要断开信号.如果在信号断开时生成任何相同类型的新模型,则不会触发处理函数.信号在Django中是全局的,并且可以同时运行多个请求,使得一些请求失败而其他请求运行其post_save处理程序.


xak*_*dog 27

您对此解决方案的看法如何?

@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
Run Code Online (Sandbox Code Playgroud)

您还可以创建装饰器

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()
Run Code Online (Sandbox Code Playgroud)

  • 应该是选择的答案. (5认同)
  • @Max Mumford 这不是一面旗帜。为了防止递归,我们可以在“instance.save()”之前使用任何未使用的名称(如“_dirty”)设置自定义属性。然后检查属性是否存在并中断递归。如您所见,我们在保存模型之前设置了“_dirty”属性,并在保存完成后将其删除。 (2认同)
  • 在调用 `instance.save()` 时使用 `try...finally` 会更好,以确保始终运行 `del instance._dirty`,即使是在例如 `DatabaseError` 的情况下。 (2认同)
  • 显示导入 `from functools importwrapps` 可能会有所帮助 (2认同)

Run*_*ard 26

我认为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.
Run Code Online (Sandbox Code Playgroud)

  • 我喜欢.但有一个建议:不是覆盖`__init__`,你可以做``如果不是getattr(例如,'_disable_signals',False)` (3认同)
  • 这个线程安全吗?如果在发生save_without_signals的同时触发常规保存会发生什么? (3认同)
  • 喜欢它.由于post保存信号的第二次调用什么也没做,我希望django会在save函数中添加一个参数,所以我们可以使用`instance.save(send_signal = False)`. (2认同)

dge*_*gel 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)
Run Code Online (Sandbox Code Playgroud)

  • 这很危险,而您的信号断开,实例可以保存,现在您的处理程序将被遗漏. (28认同)
  • 哈哈 - 我用Google搜索了自己的问题并再次使用了这个:)谢谢! (5认同)
  • 这不仅不是线程安全的,而且还缺少`try ... finally`.必须在`finally`子句中执行(重新)`connect`.如果没有它,如果`do_stuff`或`save`引发异常,那么你的处理程序将永远保持断开状态...... (3认同)
  • 此解决方案不是线程安全的,因此并发请求可能会错误地重新调用"my_post_save_handler"内部的保存调用信号,或者在"my_post_save_handler"外部调用时错误地不调用"my_post_save_handler"信号.不建议. (2认同)