2010-02-05 18 views
20

Ho bisogno di fare un salvataggio con un modello, ma ho bisogno di scollegare alcuni ricevitori dei segnali prima di salvarlo.Disconnettere i segnali per i modelli e riconnettersi in django

Voglio dire,

Ho un modello:

class MyModel(models.Model): 
    ... 

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

pre_save.connect(pre_save_model, sender=MyModel) 

e in un altro luogo nel codice ho bisogno di qualcosa di simile:

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

perché ho bisogno in questo caso, salvare il modello senza eseguire la funzione pre_save_model.

risposta

26

Per una soluzione pulita e riutilizzabile, è possibile utilizzare un gestore di contesto:

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 
     ) 

Ora, si può fare qualcosa di simile al seguente:

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

Nota: se il gestore di segnale utilizza un dispatch_uid, si DEVE utilizzare lo dispatch_uid arg.

+0

Grande. Questa è la soluzione più elegante. È possibile riutilizzare il gestore del contesto in diverse parti del codice. –

+2

Un piccolo avviso: 'weak = False' non è l'impostazione predefinita quando si collega un ricevitore a un segnale. – spg

+1

'weak' è [deprecato] (https://docs.djangoproject.com/en/1.10/topics/signals/# disconnecting-signals) Inoltre, le persone dovrebbero essere consapevoli che disabilitare un segnale impedirà * tutte * le istanze di attivare il segnale, non solo il contesto corrente (cioè altri thread, poiché i segnali sembrano essere thread-safe) , come suggerito [qui] (http://stackoverflow.com/questions/577376/django-how-do-i-not-dispatch-a-signal#comment64533494_10881618) –

6

Non ho ancora testato il seguente codice, ma dovrebbe funzionare:

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 

Sarà tacere i segnali da tutti i del mittente anche se non solo instance.__class__.


Questa versione disabilita i segnali solo del modello data:

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

Probabilmente si dovrebbe avvolgere il salvataggio in un blocco try e il re-attachment dei ricevitori in un blocco finale. Altrimenti potresti disconnettere i segnali per sempre. –

8

Se desideri solo scollegare e ricollegare un segnale personalizzato, è possibile utilizzare questo codice:

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) 

In questo modo puoi fare questo:

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

È possibile collegare e segnali scollegare come Haystack fa in RealTimeSearchIndex, che sembra più standard:

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' è lo stesso di' pre_save'? – Latrova

-1

avevo bisogno per evitare che certi segnali di sparare durante Unittests così ho fatto un decoratore sulla base della risposta della 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 

Usarlo è semplice:

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

Disabilitare i segnali durante i test in genere manca il punto di test. Il flusso di codice dovrebbe rimanere lo stesso per quanto riguarda lo scenario. Se c'è un codice che non è necessario eseguire come parte del test, quindi prendere in giro è un risultato, non saltarlo. –

+0

Se la funzione di avvolgimento ha lo scopo di restituire un valore, il codice non funzionerà. È necessario restituire il valore del risultato della funzione nel decoratore. – Feanor

+0

@DanielDubovski ci sono casi in cui potresti avere una sezione di codice di test che sta generando molti dati di test. Normalmente, se un utente creasse questi modelli avrebbe un effetto collaterale, ma per il momento si vuole saltarlo. Sì, potresti prendere in giro tutte le funzioni del ricevitore, ma a quel punto sarà più esplicito se disabiliti i segnali. Quindi crei un normale test di integrazione in cui i segnali vengono riabilitati. –