2010-02-05 7 views
20

Ich brauche eine Sicherung mit einem Modell, aber ich muss einige Empfänger der Signale trennen, bevor Sie es speichern.Trennen Sie Signale für Modelle und wieder in Django

Ich meine,

Ich habe ein Modell:

class MyModel(models.Model): 
    ... 

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

pre_save.connect(pre_save_model, sender=MyModel) 

und an einer anderen Stelle im Code muss ich so etwas wie:

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

Weil ich in diesem Fall benötigen, sparen das Modell, ohne die Funktion pre_save_model auszuführen.

Antwort

26

Für eine saubere und wiederverwendbare Lösung, können Sie einen Kontext-Manager verwenden können:

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 
     ) 

Jetzt können Sie etwas tun, wie folgt aus:

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

Hinweis: Wenn Ihr Signal-Handler ein dispatch_uid verwendet, Sie MUSS verwenden Sie die dispatch_uid arg.

+0

Großartig. Dies ist die eleganteste Lösung. Sie können den Kontextmanager in mehreren Teilen des Codes wiederverwenden. –

+2

Eine kleine Warnung: "schwach = falsch" ist nicht der Standard, wenn ein Empfänger an ein Signal angeschlossen wird. – spg

+1

"schwach" ist [veraltet] (https://docs.djangoproject.com/en/1.10/topics/signals/# disconnecting-signal) Außerdem sollte man wissen, dass das Deaktivieren eines Signals * alle * Instanzen daran hindert, das Signal auszulösen, nicht nur den aktuellen Kontext (dh andere Threads, da Signale scheinbar Thread-sicher sind) , wie vorgeschlagen [hier] (http://stackoverflow.com/questions/577376/django-how-doi-i-ot-dispatch-a-signal#comment64533494_10881618) –

6

Ich habe nicht den folgenden Code getestet, aber es sollte funktionieren:

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 

Es wird Signale stumm von alle Absenders aber nicht nur instance.__class__.


Diese Version deaktiviert nur die Signale des gegebenen Modell:

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

Sie sollten wahrscheinlich die wickeln speichern in ein Versuch Block und die erneute Anbringung von Empfängern in einem Endblock. Andernfalls könnten Sie die Signale für immer trennen. –

8

Wenn Sie nur trennen möchten und eine benutzerdefinierte Signal wieder an, können Sie diesen Code verwenden:

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) 

Auf diese Weise Sie können dies machen:

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

Sie können verbinden und trennen Signale als Haystack in RealTimeSearchIndex tut, die mehr Standard scheint:

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' ist das gleiche wie' pre_save'? – Latrova

-1

Ich brauchte bestimmte Signale zu verhindern, dass während der Unittests Brennen so machte ich einen Dekorateur auf qris Antwort basiert:

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 

Mit einfach:

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

Deaktivieren von Signalen während Tests irgendwie fehlt der Punkt des Testens. Der Code-Fluss sollte hinsichtlich des Szenarios gleich bleiben. Wenn es Code gibt, den Sie nicht als Teil des Tests ausführen müssen, dann verspotten Sie das Ergebnis, überspringen Sie es nicht. –

+0

Wenn die umbrochene Funktion einen Wert zurückgeben soll, funktioniert Ihr Code nicht. Sie müssen den Funktionsergebniswert in Ihrem Decorator zurückgeben. – Feanor

+0

@DanielDubovski Es gibt Fälle, in denen Sie möglicherweise einen Abschnitt mit Testcode haben, der viele Testdaten generiert. Normalerweise würde ein Benutzer, der diese Modelle erstellt hat, einen Nebeneffekt haben, den Sie aber vorerst überspringen möchten. Ja, Sie könnten alle Empfängerfunktionen verspotten, aber an diesem Punkt wird es deutlicher, wenn Sie nur die Signale deaktivieren. Dann würden Sie einen normalen Integrationstest erstellen, bei dem die Signale wieder aktiviert werden. –