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

die*_*us9 26 django django-signals

我需要使用模型进行保存但我需要在保存信号之前断开信号的某些接收器.

我的意思是,

我有一个模特:

class MyModel(models.Model):
    ...

def pre_save_model(sender, instance, **kwargs):
    ...

pre_save.connect(pre_save_model, sender=MyModel)
Run Code Online (Sandbox Code Playgroud)

在代码中的另一个地方我需要类似的东西:

a = MyModel()
...
disconnect_signals_for_model(a)
a.save()
...
reconnect_signals_for_model(a)
Run Code Online (Sandbox Code Playgroud)

因为在这种情况下我需要保存模型而不执行函数pre_save_model.

alu*_*ach 32

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

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
        )
Run Code Online (Sandbox Code Playgroud)

现在,您可以执行以下操作:

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',
    )
Run Code Online (Sandbox Code Playgroud)

注意:如果信号处理程序使用a dispatch_uid,则必须使用dispatch_uidarg.

  • 一个小警告:当将接收器连接到信号时,"weak = False"不是默认值. (3认同)
  • `weak` 是 [deprecated](https://docs.djangoproject.com/en/1.10/topics/signals/#disconnecting-signals) 此外,人们应该意识到禁用信号将阻止 *all* 实例触发信号,而不仅仅是当前上下文(即其他线程,因为信号似乎是线程安全的),如 [here](http://stackoverflow.com/questions/577376/django-how-do-i-not-dispatch- a-信号#comment64533494_10881618) (3认同)

qri*_*ris 24

您可以像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)
Run Code Online (Sandbox Code Playgroud)


muh*_*huk 9

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

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
Run Code Online (Sandbox Code Playgroud)

它会使来自所有发送者的信号静音但不仅仅是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
Run Code Online (Sandbox Code Playgroud)

  • 您可能应该将保存内容保存在try块中,然后将接收方重新附加到最后一个块中。否则,您可能会永远断开信号。 (2认同)

die*_*us9 8

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

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)
Run Code Online (Sandbox Code Playgroud)

通过这种方式你可以做到这一点:

disconnect_signal(pre_save, pre_save_model, MyModel)
a.save()
reconnect_signal(pre_save, pre_save_model, MyModel)
Run Code Online (Sandbox Code Playgroud)