断开模型的信号,然后在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。

+0

太好了。这是最优雅的解决方案。您可以在代码的多个部分重用上下文管理器。 – 2014-10-10 18:43:46

+2

当接收器连接到信号时,一个小警告:'weak = False'不是默认设置。 – spg 2016-03-18 19:13:57

+1

'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

我没有测试过下面的代码,但它应该工作:

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 
+0

你或许应该换行保存一个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) 

您可以连接和断开信号,Haystack确实在RealTimeSearchIndex,这似乎更标准:

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) 
+0

'pre_savel_model'与'pre_save'相同吗? – Latrova 2018-02-11 10:08:05

我需要防止单元测试在烧制过程中的某些信号,所以我做了基于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 
+0

在测试期间禁用信号有点错过了测试点。 代码流应该保持与场景相同。 如果有代码不需要作为测试的一部分执行,那么模拟它的结果,不要跳过它。 – 2016-08-28 13:20:55

+0

如果包装函数的目的是返回一些值,你的代码将不起作用。您必须在装饰器中返回函数结果值。 – Feanor 2017-06-27 14:12:18

+0

@DanielDubovski有些情况下,您可能有一段测试代码会产生大量测试数据。通常情况下,如果用户创建了这些模型,它会产生副作用,但您现在想要跳过该模型。是的,你可以模拟所有的接收器功能,但是如果你只是禁用了信号,那么它就会更加明确。然后,您将创建一个正常的集成测试,重新启用信号。 – 2017-11-08 04:00:50