Here is solution to temporary disable signal receiver
per instance which allows to use it on production (thread-safe)
[usage.py]
from django.db.models.signals import post_save
payment = Payment()
with mute_signals_for(payment, signals=[post_save]):
payment.save() # handle_payment signal receiver will be skipped
[code.py]
from contextlib import contextmanager
from functools import wraps
MUTE_SIGNALS_ATTR = '_mute_signals'
def mutable_signal_receiver(func):
"""Decorator for signals to allow to skip them by setting attr MUTE_SIGNALS_ATTR on instance,
which can be done via mute_signals_for"""
@wraps(func)
def wrapper(sender, instance, signal, **kwargs):
mute_signals = getattr(instance, MUTE_SIGNALS_ATTR, False)
if mute_signals is True:
pass # skip all signals
elif isinstance(mute_signals, list) and signal in mute_signals:
pass # skip user requested signal
else: # allow signal receiver
return func(sender=sender, instance=instance, signal=signal, **kwargs)
return wrapper
@contextmanager
def mute_signals_for(instance, signals):
"""Context manager to skip signals for @instance (django model), @signals can be
True to skip all signals or list of specified signals, like [post_delete, post_save] """
try:
yield setattr(instance, MUTE_SIGNALS_ATTR, signals)
finally:
setattr(instance, MUTE_SIGNALS_ATTR, False)
[signals.py]
@receiver(post_save, sender=Payment, dispatch_uid='post_payment_signal')
@mutable_signal_receiver
def handle_payment(sender, instance, created, **kwargs):
"""called after payment is registered in the system."""