2011-12-28 2 views
1

J'ai une situation où lorsque l'un de mes modèles est sauvé MyModel Je veux vérifier un champ, et déclencher le même changement de tout autre modèle avec le même some_key.Django, post_save signal recrusion. Comment le signal contourner le tir

Le code fonctionne très bien, mais son appel récursivement les signaux. Par conséquent, je gaspille des appels CPU/DB/API. Je veux essentiellement contourner les signaux pendant le .save(). Des suggestions?

class MyModel(models.Model): 
    #bah 
    some_field = # 
    some_key = # 

#in package code __init__.py 
@receiver(models_.post_save_for, sender=MyModel) 
def my_model_post_processing(sender, **kwargs): 
# do some unrelated logic... 
logic = 'fun! ' 


#if something has changed... update any other field with the same id 
cascade_update = MyModel.exclude(id=sender.id).filter(some_key=sender.some_key) 
for c in cascade_update: 
    c.some_field = sender.some_field 
    c.save() 

Répondre

4

Débranchez le signal avant d'appeler save puis reconnectez-après:

+0

Cela me fait peur (au cas où quelque chose ne va pas ...) ... mais ça va marcher. – Nix

+0

Qu'est-ce qui pourrait mal tourner? La seule chose qui se passe entre la déconnexion et la connexion est la sauvegarde, et si cela ne fonctionnait pas, vous ne seriez même pas là. –

+0

Relier le back up pourrait aller mal ... Je pense que c'est l'approche, il tombe juste dans la catégorie semble vraiment effrayant. – Nix

0

Vous pouvez déplacer le code de mise à jour des objets associés dans la méthode MyModel.save. Ne joue pas avec le signal est nécessaire, alors:

class MyModel(models.Model): 
    some_field = # 
    some_key = # 

    def save(self, *args, **kwargs): 
     super(MyModel, self).save(*args, **kwargs) 
     for c in MyModel.objects.exclude(id=self.id).filter(some_key=self.some_key): 
      c.some_field = self.some_field 
      c.save() 
+0

Cela fonctionnerait, mais nous voulons vraiment cette logique dans le code du signal. – Nix

+0

En plus des méthodes suggérées, que faire si ajouter un attribut à un modèle avant d'appeler save, et dans le gestionnaire de signal, le vérifier. Si attribut set - ignore le traitement. – demalexx

1

Débranchement un signal est pas une solution cohérente SEC et , comme l'utilisation mise à jour() au lieu de sauvegarde(). Pour contourner le déclenchement du signal sur votre modèle, une manière simple d'y aller est de définir un attribut sur l'instance en cours pour empêcher le déclenchement des signaux.

Cela peut être fait à l'aide d'un simple décorateur qui vérifie si l'instance donnée a « skip_signal » attribut, et si oui empêche la méthode d'être appelé:

from functools import wraps 

def skip_signal(): 
    def _skip_signal(signal_func): 
     @wraps(signal_func) 
     def _decorator(sender, instance, **kwargs): 
      if hasattr(instance, 'skip_signal'): 
       return None 
      return signal_func(sender, instance, **kwargs) 
     return _decorator 
    return _skip_signal 

Sur la base de votre exemple, nous donne:

from django.db.models.signals import post_save 
from django.dispatch import receiver 

@receiver(post_save, sender=MyModel) 
@skip_signal() 
def my_model_post_save(sender, instance, **kwargs): 
    instance.some_field = my_value 
    # Here we flag the instance with 'skip_signal' 
    # and my_model_post_save won't be called again 
    # thanks to our decorator, avoiding any signal recursion 
    instance.skip_signal = True 
    instance.save() 

Hope Cela aide.

+0

Cela devrait être la réponse acceptée, c'est une façon beaucoup plus stable et évolutive. – XelharK