简体   繁体   English

Django post_save 在不覆盖模型 save() 的情况下防止递归

[英]Django post_save preventing recursion without overriding model save()

There are many Stack Overflow posts about recursion using the post_save signal, to which the comments and answers are overwhelmingly: "why not override save()" or a save that is only fired upon created == True .有许多关于使用post_save信号进行递归的 Stack Overflow 帖子,绝大多数的评论和答案是:“为什么不覆盖 save()”或仅在created == True时触发的保存。

Well I believe there's a good case for not using save() - for example, I am adding a temporary application that handles order fulfillment data completely separate from our Order model.好吧,我相信有一个不使用save()的好例子——例如,我正在添加一个临时应用程序来处理与我们的订单模型完全分开的订单履行数据。

The rest of the framework is blissfully unaware of the fulfillment application and using post_save hooks isolates all fulfillment related code from our Order model.框架的其余部分完全不知道履行应用程序,并且使用 post_save 挂钩将所有与履行相关的代码与我们的订单模型隔离开来。

If we drop the fulfillment service, nothing about our core code has to change.如果我们放弃 fulfillment 服务,我们的核心代码就不必改变。 We delete the fulfillment app, and that's it.我们删除了履行应用程序,仅此而已。

So, are there any decent methods to ensure the post_save signal doesn't fire the same handler twice?那么,是否有任何合适的方法来确保 post_save 信号不会两次触发同一个处理程序?

you can use update instead of save in the signal handler您可以使用更新而不是保存在信号处理程序中

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

Don't disconnect signals.不要断开信号。 If any new model of the same type is generated while the signal is disconnected the handler function won't be fired.如果在信号断开时生成了任何相同类型的新模型,则不会触发处理程序函数。 Signals are global across Django and several requests can be running concurrently, making some fail while others run their post_save handler.信号在 Django 中是全局的,并且多个请求可以同时运行,使一些请求失败,而其他请求则运行它们的 post_save 处理程序。

What you think about this solution?你怎么看这个解决方案?

@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

You can also create decorator您还可以创建装饰器

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

I think creating a save_without_signals() method on the model is more explicit:我认为在模型上创建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.

How about disconnecting then reconnecting the signal within your post_save function:如何断开然后重新连接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)

You should use queryset.update() instead of Model.save() but you need to take care of something else:您应该使用 queryset.update() 而不是 Model.save() 但您需要处理其他事情:

It's important to note that when you use it, if you want to use the new object you should get his object again, because it will not change the self object, for example:需要注意的是,在使用的时候,如果要使用新的对象,应该再次获取他的对象,因为它不会改变self对象,例如:

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

So, if you want to use the new object you should do again:所以,如果你想使用新对象,你应该再做一次:

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

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

Check this out...看一下这个...

Each signal has it's own benefits as you can read about in the docs here but I wanted to share a couple things to keep in mind with the pre_save and post_save signals.每个信号都有它自己的好处,你可以在这里的文档中阅读,但我想分享一些关于 pre_save 和 post_save 信号的注意事项。

  • Both are called every time .save() on a model is called.每次调用模型上的 .save() 时都会调用两者。 In other words, if you save the model instance, the signals are sent.换句话说,如果您保存模型实例,则会发送信号。

  • running save() on the instance within a post_save can often create a never ending loop and therefore cause a max recursion depth exceeded error --- only if you don't use .save() correctly.在 post_save 中的实例上运行 save() 通常会创建一个永无止境的循环,因此会导致最大递归深度超出错误——仅当您没有正确使用 .save() 时。

  • pre_save is great for changing just instance data because you do not have to call save() ever which eliminates the possibility for above. pre_save 非常适合仅更改实例数据,因为您无需调用 save() 就可以消除上述可能性。 The reason you don't have to call save() is because a pre_save signal literally means right before being saved.您不必调用 save() 的原因是因为 pre_save 信号的字面意思是在被保存之前。

  • Signals can call other signals and or run delayed tasks (for Celery) which can be huge for usability.信号可以调用其他信号和/或运行延迟任务(对于 Celery),这对于可用性来说可能是巨大的。

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

Regards!!问候!!

the Model's .objects.update() method bypasses the post_save signal模型的.objects.update()方法绕过post_save信号

Try this something like this:试试这个:

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)

In this example, an object has a name and each time .save() is called, the .num_saves property is incremented, but without recursion.在这个例子中,一个对象有一个名字,每次调用.save().num_saves属性都会增加,但没有递归。

In post_save singal in django for avoiding recursion 'if created' check is required在 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()

I was using the save_without_signals() method by @Rune Kaagaard until i updated my Django to 4.1.在我将 Django 更新到 4.1 之前,我一直在使用 @Rune Kaagaard 的 save_without_signals() 方法。 On Django 4.1 this method started raising an Integrity error on the database that gave me 4 days of headaches and i couldn't fix it.在 Django 4.1 上,此方法开始在数据库上引发完整性错误,这让我头疼了 4 天,但我无法修复它。

So i started to use the queryset.update() method and it worked like a charm.所以我开始使用 queryset.update() 方法,它非常有效。 It doesn't triggers the pre_save() neither post_save() and you don't need to override the save() method of your model.它不会触发 pre_save() 和 post_save() 并且您不需要重写模型的 save() 方法。 1 line of code. 1行代码。

@receiver(pre_save, sender=Your_model)
def any_name(sender, instance, **kwargs):
    Your_model.objects.filter(pk=instance.pk).update(model_attribute=any_value)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM