Django post_save preventing recursion without overriding model save()

前端 未结 9 1528
轻奢々
轻奢々 2020-11-29 23:41

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

相关标签:
9条回答
  • 2020-11-30 00:21

    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()
    
    0 讨论(0)
  • 2020-11-30 00:24

    How about disconnecting then reconnecting the signal within your post_save function:

    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)
    
    0 讨论(0)
  • 2020-11-30 00:25

    You could also check the raw argument in post_save and then call save_baseinstead of save.

    0 讨论(0)
  • 2020-11-30 00:25

    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.

    • Both are called every time .save() on a model is called. 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.

    • pre_save is great for changing just instance data because you do not have to call save() ever which eliminates the possibility for above. The reason you don't have to call save() is because a pre_save signal literally means right before being saved.

    • Signals can call other signals and or run delayed tasks (for Celery) which can be huge for usability.

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

    Regards!!

    0 讨论(0)
  • 2020-11-30 00:30

    I think creating a save_without_signals() method on the model is more explicit:

    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.
    
    0 讨论(0)
  • 2020-11-30 00:40

    you can use update instead of save in the signal handler

    queryset.filter(pk=instance.pk).update(....)
    
    0 讨论(0)
提交回复
热议问题