断开型号的信号并在django中重新连接

断开型号的信号并在django中重新连接,django,django-signals,Django,Django Signals,我需要保存一个模型,但我需要在保存之前断开一些信号接收器的连接 我是说 我有一个模型: class MyModel(models.Model): ... def pre_save_model(sender, instance, **kwargs): ... pre_save.connect(pre_save_model, sender=MyModel) 在代码的另一个地方,我需要这样的东西: a = MyModel() ... disconnect_signals_for_

我需要保存一个模型,但我需要在保存之前断开一些信号接收器的连接

我是说

我有一个模型:

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的情况下保存模型。

我没有测试以下代码,但它应该可以工作:

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
它将使来自所有发送者的信号静音,但不仅仅是
实例


此版本仅禁用给定型号的信号:

从django.db.models.signals导入预保存
从django.dispatch.dispatcher导入\u make\u id
def save_不带_信号(例如,*args,**kwargs):
接收者=[]
发送方\u id=\u生成\u id(实例。\u类\u)
对于xrange中的索引(len(self.receivers)):
如果预存收件人[索引][0][1]==发件人id:
receivers.append(pre_save.receivers.pop(索引))
new_instance=instance.save(*args,**kwargs)
pre_save.receivers.extend(接收器)
返回新的\u实例

如果您只想断开和重新连接一个自定义信号,可以使用以下代码:

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)

您可以像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)

对于干净且可重复使用的解决方案,您可以使用上下文管理器:

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\u uid
,则必须使用
dispatch\u uid
参数。

我需要防止单元测试期间触发某些信号,因此我根据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

您可能应该将save包装在try块中,将receiver的重新连接包装在finally块中。否则你可能会永远断开信号。是的,使用
试试..最后
很好。这是最优雅的解决方案。您可以在代码的多个部分重复使用上下文管理器。在将接收器连接到信号时,一个小警告:
weak=False
不是默认值。
weak
也是默认值,人们应该注意,禁用信号将阻止所有实例触发信号,而不仅仅是当前上下文(即其他线程,因为信号似乎是线程安全的),正如@DanielDubovski所建议的那样,它们似乎是线程安全的?那么它应该是这样工作的,不是吗?如果信号上有多个接收器装饰器,出于某种原因,它就不工作了。但仍然是一个非常好的解决方案!在测试期间禁用信号有点忽略了测试的重点。代码流应该与场景保持一致。如果有是您不需要作为测试的一部分执行的代码,然后模拟其结果,不要跳过它。如果包装的函数要返回一些值,那么您的代码将不起作用。您必须在装饰器中返回函数结果值。@DanielDubovski在某些情况下,您可能有一段测试代码正在生成大量测试数据。Norma实际上,如果用户创建了这些模型,它会产生副作用,但你现在想跳过它。是的,你可以模拟所有的接收器功能,但在这一点上,如果你只是禁用信号,它会更明确。然后你会创建一个正常的集成测试,其中信号被重新启用。@JordanReiter我理解你的观点,but、 我仍然不同意。IMHO,出于测试目的更改代码流是一种糟糕的做法,因为很容易忘记代码可以采用的不同路径。具体来说,模拟在本质上是非常明确的,在我看来更像python。也就是说,就像任何规则一样,我想经验法则总会有例外,危险在于例外情况将成为以后维护人员的标准。
pre_savel_model
pre_save
?@Latrova-我假设
pre_save_model
只是信号接收器名称的一个示例。
connect
disconnect
的第一个参数是信号接收器。()