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

时间:2012-05-31 19:27:45

标签: python django django-signals

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

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

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

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

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

10 个答案:

答案 0 :(得分:78)

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

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

答案 1 :(得分:34)

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

答案 2 :(得分:26)

您对此解决方案有何看法?

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

答案 3 :(得分:24)

我认为在模型上创建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.

答案 4 :(得分:20)

如何断开然后重新连接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)

答案 5 :(得分:4)

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

重要的是要注意,当你使用它时,如果你想使用新对象,你应该再次得到它的对象,因为它不会改变自我对象,例如:

>>> 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'

答案 6 :(得分:4)

您还可以查看raw中的post_save参数,然后拨打save_base而不是save

答案 7 :(得分:0)

检查出来......

每个信号都有它自己的好处,你可以在这里的文档中阅读,但我想分享一些事情要记住pre_save和post_save信号。

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

  • 在post_save中的实例上运行save()通常会创建一个永不结束的循环,因此只有在你没有正确使用.save()时才会导致超出最大递归深度错误。

  • pre_save非常适合更改实例数据,因为您无需调用save(),从而消除了上述内容的可能性。你不必调用save()的原因是因为pre_save信号在保存之前就意味着它。

  • 信号可以调用其他信号,或者运行延迟的任务(对于C​​elery),这对于可用性来说非常重要。

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

问候!!

答案 8 :(得分:0)

模型的.objects.update()方法bypasses the post_save signal

尝试这样的事情:

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属性都会增加,但不会递归。

答案 9 :(得分:0)

在 django 中的 post_save singal 中,为了避免递归,需要检查“if created”

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