trigger - Desconecta señales para modelos y reconecta en django.
events django (5)
Necesitaba evitar que ciertas señales se dispararan durante las pruebas de unidad, así que hice un decorador basado en la respuesta de qris:
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
Usarlo es simple:
@prevent_signal(''post_save'', my_signal, SenderClass)
def test_something_without_signal(self):
# the signal will not fire inside this test
Necesito hacer un ahorro con un modelo pero necesito desconectar algunos receptores de las señales antes de guardarlo.
Quiero decir,
Tengo un modelo:
class MyModel(models.Model):
...
def pre_save_model(sender, instance, **kwargs):
...
pre_save.connect(pre_save_model, sender=MyModel)
y en otro lugar del código necesito algo como:
a = MyModel()
...
disconnect_signals_for_model(a)
a.save()
...
reconnect_signals_for_model(a)
Como necesito en este caso, guarde el modelo sin ejecutar la función pre_save_model.
No he probado el siguiente código, pero debería funcionar:
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
instance.__class__
señales de todos los remitentes, aunque no solo de la instance.__class__
.
Esta versión deshabilita solo las señales del modelo dado:
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
Para una solución limpia y reutilizable, puede usar un administrador de contexto:
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
)
Ahora, puedes hacer algo como lo siguiente:
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'',
)
Nota: Si su manejador de señales usa un dispatch_uid
, DEBE usar el dispatch_uid
arg.
Puede conectar y desconectar señales como lo hace Haystack en RealTimeSearchIndex, lo que parece más estándar:
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)
Si solo desea desconectar y volver a conectar una señal personalizada, puede usar este código:
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)
De esta manera podrás hacer esto:
disconnect_signal(pre_save, pre_save_model, MyModel)
a.save()
reconnect_signal(pre_save, pre_save_model, MyModel)