2010-02-05 61 views
20

我需要使用模型進行保存,但在保存之前我需要斷開信號的某些接收器。斷開模型的信號,然後在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。

回答

26

對於清潔和可重複使用的解決方案,你可以使用一個上下文管理器:

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://stackoverflow.com/questions/577376/django-how-do-i-not-dispatch-a-signal#comment64533494_10881618) – 2016-08-28 12:56:29

6

我沒有測試過下面的代碼,但它應該工作:

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

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) 

這樣你可以這樣做:

disconnect_signal(pre_save, pre_save_model, MyModel) 
a.save() 
reconnect_signal(pre_save, pre_save_model, MyModel) 
18

您可以連接和斷開信號,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

-1

我需要防止單元測試在燒製過程中的某些信號,所以我做了基於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

相關問題