41
For a clean and reusable solution, you can use a context manager:
class temp_disconnect_signal():
""" Temporarily disconnect a model from a signal """
def __init__(self, signal, receiver, sender, dispatch_uid=None):
self.signal = signal
self.receiver = receiver
self.sender = sender
self.dispatch_uid = dispatch_uid
def __enter__(self):
self.signal.disconnect(
receiver=self.receiver,
sender=self.sender,
dispatch_uid=self.dispatch_uid,
weak=False
)
def __exit__(self, type, value, traceback):
self.signal.connect(
receiver=self.receiver,
sender=self.sender,
dispatch_uid=self.dispatch_uid,
weak=False
)
Now, you can do something like the following:
from django.db.models import signals
from your_app.signals import some_receiver_func
from your_app.models import SomeModel
...
kwargs = {
'signal': signals.post_save,
'receiver': some_receiver_func,
'sender': SomeModel,
'dispatch_uid': "optional_uid"
}
with temp_disconnect_signal(**kwargs):
SomeModel.objects.create(
name='Woohoo',
slug='look_mom_no_signals',
)
Note: If your signal handler uses a dispatch_uid
, you MUST use the dispatch_uid
arg.
32
You can connect and disconnect signals as Haystack does in RealTimeSearchIndex, which seems more standard:
from django.db.models import signals
signals.pre_save.disconnect(pre_save_model, sender=MyModel)
a.save()
signals.pre_save.connect(pre_save_model, sender=MyModel)
- [Django]-Django delete FileField
- [Django]-Django: How to set a field to NULL?
- [Django]-How can I use Bootstrap with Django?
10
I haven’t tested the following code, but it should work:
from django.db.models.signals import pre_save
def save_without_the_signals(instance, *args, **kwargs):
receivers = pre_save.receivers
pre_save.receivers = []
new_instance = instance.save(*args, **kwargs)
pre_save.receivers = receivers
return new_instance
It will silence signals from all sender’s though not just instance.__class__
.
This version disables only the given model’s signals:
from django.db.models.signals import pre_save
from django.dispatch.dispatcher import _make_id
def save_without_the_signals(instance, *args, **kwargs):
receivers = []
sender_id = _make_id(instance.__class__)
for index in xrange(len(self.receivers)):
if pre_save.receivers[index][0][1] == sender_id:
receivers.append(pre_save.receivers.pop(index))
new_instance = instance.save(*args, **kwargs)
pre_save.receivers.extend(receivers)
return new_instance
- [Django]-Django return HttpResponseRedirect to an url with a parameter
- [Django]-Django URLs TypeError: view must be a callable or a list/tuple in the case of include()
- [Django]-Django CSRF framework cannot be disabled and is breaking my site
7
If you only want disconnect and reconnect one custom signal, you may use this code:
def disconnect_signal(signal, receiver, sender):
disconnect = getattr(signal, 'disconnect')
disconnect(receiver, sender)
def reconnect_signal(signal, receiver, sender):
connect = getattr(signal, 'connect')
connect(receiver, sender=sender)
In this way you can make this:
disconnect_signal(pre_save, pre_save_model, MyModel)
a.save()
reconnect_signal(pre_save, pre_save_model, MyModel)
- [Django]-Can I Make a foreignKey to same model in django?
- [Django]-How do I restrict foreign keys choices to related objects only in django
- [Django]-ValueError – Cannot assign: must be an instance
1
I needed to prevent certain signals from firing during unittests so I made a decorator based on qris’s response:
from django.db.models import signals
def prevent_signal(signal_name, signal_fn, sender):
def wrap(fn):
def wrapped_fn(*args, **kwargs):
signal = getattr(signals, signal_name)
signal.disconnect(signal_fn, sender)
fn(*args, **kwargs)
signal.connect(signal_fn, sender)
return wrapped_fn
return wrap
Using it is simple:
@prevent_signal('post_save', my_signal, SenderClass)
def test_something_without_signal(self):
# the signal will not fire inside this test
- [Django]-Python Django: You're using the staticfiles app without having set the STATIC_ROOT setting
- [Django]-Django: Safely Remove Old Migrations?
- [Django]-Django serializer Imagefield to get full URL
0
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."""
- [Django]-How to force Django models to be released from memory
- [Django]-Django model "doesn't declare an explicit app_label"
- [Django]-Change the width of form elements created with ModelForm in Django