2010-02-05 39 views
20

Tôi cần thực hiện lưu với mô hình nhưng tôi cần ngắt kết nối một số bộ thu tín hiệu trước khi lưu.Ngắt kết nối tín hiệu cho mô hình và kết nối lại trong django

Ý tôi là,

Tôi có một mô hình:

class MyModel(models.Model): 
    ... 

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

pre_save.connect(pre_save_model, sender=MyModel) 

và ở một nơi khác trong mã tôi cần một cái gì đó như:

a = MyModel() 
... 
disconnect_signals_for_model(a) 
a.save() 
... 
reconnect_signals_for_model(a) 

Bởi vì tôi cần trong trường hợp này, tiết kiệm mô hình mà không thực thi hàm pre_save_model.

Trả lời

26

Đối với một giải pháp làm sạch và tái sử dụng, bạn có thể sử dụng một trình quản lý nội dung:

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 
     ) 

Bây giờ, bạn có thể làm điều gì đó như sau:

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', 
    ) 

Lưu ý: Nếu xử lý tín hiệu của bạn sử dụng một dispatch_uid, bạn PHẢI sử dụng dispatch_uid arg.

+0

Tuyệt vời. Đây là giải pháp thanh lịch nhất. Bạn có thể sử dụng lại trình quản lý ngữ cảnh trong một số phần của mã. –

+2

Cảnh báo nhỏ: 'weak = False' không phải là mặc định khi kết nối máy thu với tín hiệu. – spg

+1

'yếu' là [không được chấp nhận] (https://docs.djangoproject.com/en/1.Ngoài ra, mọi người nên biết rằng vô hiệu hóa một tín hiệu sẽ ngăn chặn * tất cả * trường hợp từ kích hoạt tín hiệu, không chỉ bối cảnh hiện tại (tức là chủ đề khác, như tín hiệu dường như thread an toàn) , như được đề xuất [ở đây] (http://stackoverflow.com/questions/577376/django-how-do-i-not-dispatch-a-signal#comment64533494_10881618) –

6

tôi đã không kiểm tra đoạn mã sau, nhưng nó cũng làm việc:

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 

Nó sẽ im lặng tín hiệu từ tất cả người gửi mặc dù không chỉ instance.__class__.


Phiên bản này sẽ vô hiệu hóa các tín hiệu chỉ có mô hình cho trước:

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

Bạn có lẽ nên quấn tiết kiệm trong một khối thử và sự gắn lại của người nhận trong một khối cuối cùng. Nếu không, bạn có thể ngắt kết nối tín hiệu mãi mãi. –

8

Nếu bạn chỉ muốn ngắt kết nối và kết nối lại một tín hiệu tùy chỉnh, bạn có thể sử dụng mã này:

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) 

Bằng cách này bạn có thể thực hiện việc này:

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

Bạn có thể kết nối và tín hiệu ngắt kết nối như Haystack làm trong RealTimeSearchIndex, mà dường như nhiều tiêu chuẩn:

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' giống với' pre_save'? – Latrova

-1

tôi cần thiết để ngăn chặn các tín hiệu nhất định từ bắn trong unittests vì vậy tôi đã trang trí dựa trên phản ứng QRIS của:

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 

Sử dụng nó rất đơn giản:

@prevent_signal('post_save', my_signal, SenderClass) 
def test_something_without_signal(self): 
    # the signal will not fire inside this test 
+0

Tắt tín hiệu trong khi kiểm tra, đừng bỏ lỡ điểm kiểm tra. Luồng mã phải được giữ nguyên về kịch bản. Nếu có mã bạn không cần thực hiện như một phần của thử nghiệm, thì hãy thử kết quả của nó, đừng bỏ qua nó. –

+0

Nếu hàm được bọc có nghĩa là trả lại một số giá trị, mã của bạn sẽ không hoạt động. Bạn phải trả về giá trị kết quả hàm trong trình trang trí của bạn. – Feanor

+0

@DanielDubovski có những trường hợp bạn có thể có một phần của mã thử nghiệm đang tạo ra rất nhiều dữ liệu thử nghiệm. Thông thường, nếu người dùng tạo các mô hình này, nó sẽ có tác dụng phụ, nhưng bạn muốn bỏ qua điều đó ngay bây giờ. Có, bạn có thể giả lập tất cả các chức năng thu, nhưng tại thời điểm đó nó sẽ rõ ràng hơn nếu bạn chỉ cần vô hiệu hóa các tín hiệu. Sau đó, bạn sẽ tạo một thử nghiệm tích hợp thông thường, nơi các tín hiệu được kích hoạt lại. –

Các vấn đề liên quan