断开模型的信号,然后在django重新连接
我需要使用模型进行保存,但在保存之前我需要断开信号的某些接收器。断开模型的信号,然后在django重新连接
我的意思是,
我有一个模型:
class MyModel(models.Model):
...
def pre_save_model(sender, instance, **kwargs):
...
pre_save.connect(pre_save_model, sender=MyModel)
,并在代码中的另一个地方,我需要这样的东西:
a = MyModel()
...
disconnect_signals_for_model(a)
a.save()
...
reconnect_signals_for_model(a)
因为我在这种情况下的需要,节省该模型不执行函数pre_save_model。
对于清洁和可重复使用的解决方案,你可以使用一个上下文管理器:
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
)
现在,你可以这样做以下:
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',
)
注:如果你的信号处理程序使用dispatch_uid
,你必须使用dispatch_uid
arg。
我没有测试过下面的代码,但它应该工作:
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__
。
该版本仅禁用给定模型的信号:
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
你或许应该换行保存一个try块,以及接收器在一个终端中的重新连接。否则,您可能会永远断开信号。 – 2013-04-24 08:26:27
如果你只是想断开连接,重新连接一个自定义的信号,您可以使用此代码:
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)
这样你可以这样做:
disconnect_signal(pre_save, pre_save_model, MyModel)
a.save()
reconnect_signal(pre_save, pre_save_model, MyModel)
我需要防止单元测试在烧制过程中的某些信号,所以我做了基于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
使用它很简单:
@prevent_signal('post_save', my_signal, SenderClass)
def test_something_without_signal(self):
# the signal will not fire inside this test
在测试期间禁用信号有点错过了测试点。 代码流应该保持与场景相同。 如果有代码不需要作为测试的一部分执行,那么模拟它的结果,不要跳过它。 – 2016-08-28 13:20:55
如果包装函数的目的是返回一些值,你的代码将不起作用。您必须在装饰器中返回函数结果值。 – Feanor 2017-06-27 14:12:18
@DanielDubovski有些情况下,您可能有一段测试代码会产生大量测试数据。通常情况下,如果用户创建了这些模型,它会产生副作用,但您现在想要跳过该模型。是的,你可以模拟所有的接收器功能,但是如果你只是禁用了信号,那么它就会更加明确。然后,您将创建一个正常的集成测试,重新启用信号。 – 2017-11-08 04:00:50
太好了。这是最优雅的解决方案。您可以在代码的多个部分重用上下文管理器。 – 2014-10-10 18:43:46
当接收器连接到信号时,一个小警告:'weak = False'不是默认设置。 – spg 2016-03-18 19:13:57
'weak'是[弃用](https://docs.djangoproject.com/en/1。10 /主题/信号/#断开信号) 此外,人们应该意识到,禁用信号将阻止* all *实例触发信号,而不仅仅是当前上下文(即其他线程,因为信号似乎是线程安全的) ,建议[这里](http://*.com/questions/577376/django-how-do-i-not-dispatch-a-signal#comment64533494_10881618) – 2016-08-28 12:56:29