Подтвердить что ты не робот

Отключить сигналы для моделей и снова подключиться к 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.

4b9b3361

Ответ 1

Для чистого и многоразового решения вы можете использовать диспетчер контекстов:

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

...

with temp_disconnect_signal(
        signal=signals.post_save,
        receiver=some_receiver_func,
        sender=SomeModel, 
        dispatch_uid="optional_uid"):
    SomeModel.objects.create(
        name='Woohoo',
        slug='look_mom_no_signals',
    )

Примечание. Если ваш обработчик сигнала использует dispatch_uid, вы ДОЛЖНЫ использовать аргумент dispatch_uid.

Ответ 2

Вы можете подключать и отключать сигналы как 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)

Ответ 3

Если вы хотите отключить и снова подключить один настраиваемый сигнал, вы можете использовать этот код:

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)

Ответ 4

Я не тестировал следующий код, но он должен работать:

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

Ответ 5

Мне нужно было предотвратить определенные сигналы от стрельбы во время unittests, поэтому я сделал декоратор на основе ответа 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