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