2010-02-05 4 views
20

Je dois faire une sauvegarde avec un modèle mais j'ai besoin de déconnecter certains récepteurs des signaux avant de le sauvegarder.Déconnecter les signaux pour les modèles et reconnecter en django

Je veux dire,

J'ai un modèle:

class MyModel(models.Model): 
    ... 

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

pre_save.connect(pre_save_model, sender=MyModel) 

et dans un autre endroit dans le code je besoin de quelque chose comme:

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

Parce que je dois dans ce cas, sauf le modèle sans exécuter la fonction pre_save_model.

Répondre

26

Pour une solution propre et réutilisable, vous pouvez utiliser un gestionnaire de contexte:

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 
     ) 

Maintenant, vous pouvez faire quelque chose comme ce qui suit:

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

Remarque: Si votre gestionnaire de signal utilise un dispatch_uid, vous DOIT utiliser l'argument dispatch_uid.

+0

Super. C'est la solution la plus élégante. Vous pouvez réutiliser le gestionnaire de contexte dans plusieurs parties du code. –

+2

Un petit avertissement: 'weak = False' n'est pas la valeur par défaut lors de la connexion d'un récepteur à un signal. – spg

+1

'weak' est [obsolète] (https://docs.djangoproject.com/fr/1.10/sujets/signaux/# débrochage signaux) En outre, les gens doivent être conscients que la désactivation d'un signal empêchera * tous * cas de déclenchement du signal, non seulement le contexte actuel (c.-à d'autres threads, en tant que signaux semblent être thread-safe) , comme le suggère [ici] (http://stackoverflow.com/questions/577376/django-how-do-i-not-dispatch-a-signal#comment64533494_10881618) –

6

Je n'ai pas testé le code suivant, mais il devrait fonctionner:

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 

Il fera taire les signaux de tous ÉMETTEUR si pas seulement instance.__class__.


Cette version désactive uniquement les signaux du modèle donné:

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

Vous devriez probablement envelopper la sauvegarde dans un bloc try et la ré-attachement des récepteurs dans un bloc final. Sinon, vous pourriez déconnecter les signaux pour toujours. –

8

Si vous ne souhaitez que déconnecter et reconnecter un signal personnalisé, vous pouvez utiliser ce code:

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) 

De cette façon vous pouvez faire ceci:

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

Vous pouvez connecter et signaux sectionneurs comme Haystack fait dans RealTimeSearchIndex, qui semble plus 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' est le même que' pre_save'? – Latrova

-1

Je avais besoin pour empêcher certains signaux de mise à feu pendant unittests alors j'ai fait un décorateur basé sur la réponse SQRI:

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 

Son utilisation est simple:

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

signaux Désactivation lors des essais rate un peu le point de test. Le flux de code doit rester le même en ce qui concerne le scénario. S'il y a un code que vous ne avez pas besoin d'exécuter dans le cadre du test, se moquer alors il est fait, ne sautez pas. –

+0

Si la fonction enveloppée est destinée à renvoyer une certaine valeur, votre code ne fonctionnera pas. Vous devez renvoyer la valeur du résultat de la fonction dans votre décorateur. – Feanor

+0

@DanielDubovski il y a des cas où vous pourriez avoir une section de code de test qui génère beaucoup de données de test. Normalement, si un utilisateur crée ces modèles, cela aura un effet secondaire, mais vous voulez ignorer cela pour l'instant. Oui, vous pouvez vous moquer de toutes les fonctions du récepteur, mais à ce stade, il sera plus explicite si vous désactivez simplement les signaux. Ensuite, vous créez un test d'intégration normal où les signaux sont réactivés. –

Questions connexes