Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django, post_save signal recrusion. How to bypass signal firing

I have a situation where when one of my models is saved MyModel I want to check a field, and trigger the same change in any other Model with the same some_key.

The code works fine, but its recursively calling the signals. As a result I am wasting CPU/DB/API calls. I basically want to bypass the signals during the .save(). Any suggestions?

class MyModel(models.Model):
    #bah
    some_field = #
    some_key = #

#in package code __init__.py 
@receiver(models_.post_save_for, sender=MyModel)
def my_model_post_processing(sender, **kwargs):
 # do some unrelated logic...
 logic = 'fun!  '


 #if something has changed... update any other field with the same id
 cascade_update = MyModel.exclude(id=sender.id).filter(some_key=sender.some_key)
 for c in cascade_update:
     c.some_field  = sender.some_field 
     c.save()
like image 631
Nix Avatar asked Nov 26 '25 11:11

Nix


2 Answers

Disconnect the signal before calling save and then reconnect it afterwards:

post_save.disconnect(my_receiver_function, sender=MyModel)
instance.save()
post_save.connect(my_receiver_function, sender=MyModel)
like image 144
Chris Pratt Avatar answered Nov 28 '25 01:11

Chris Pratt


Disconnecting a signal is not a DRY and consistent solution, such as using update() instead of save().

To bypass signal firing on your model, a simple way to go is to set an attribute on the current instance to prevent upcoming signals firing.

This can be done using a simple decorator that checks if the given instance has the 'skip_signal' attribute, and if so prevents the method from being called:

from functools import wraps

def skip_signal(signal_func):
    @wraps(signal_func)
    def _decorator(sender, instance, **kwargs):
        if hasattr(instance, 'skip_signal'):
            return None
        return signal_func(sender, instance, **kwargs)  
    return _decorator

Based on your example, that gives us:

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

@receiver(post_save, sender=MyModel)
@skip_signal()
def my_model_post_save(sender, instance, **kwargs):
    instance.some_field = my_value
    # Here we flag the instance with 'skip_signal'
    # and my_model_post_save won't be called again
    # thanks to our decorator, avoiding any signal recursion
    instance.skip_signal  = True
    instance.save()

Hope This helps.

like image 22
Charlesthk Avatar answered Nov 28 '25 00:11

Charlesthk



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!