2010-07-14 12 views
5

Aquí está la situación:django: recursion usando la señal de post-guardado

Digamos que tengo un modelo A en django. Cuando estoy guardando un objeto (de clase A) necesito guardar sus campos en todos los demás objetos de esta clase. Quiero decir que necesito que todos los demás objetos A sean copias de una guardada.

Cuando uso señales (después de guardar, por ejemplo) obtengo una recursión (los objetos intentan guardarse entre ellos, supongo) y mi pitón muere.

I men. Esperaba que el uso del método .save() en la misma clase en la señal pre/post-save ocasionara una recursión pero no supiera cómo evitarla.

¿Qué hacemos?

+0

¿Tal vez agregue algún código para que su situación se vuelva más clara? – adamk

+0

No sucederá si usa pre_save, ya que no tiene que llamar a save() usted mismo, sucederá entonces «naturalmente». –

Respuesta

4

Esto funcionará:

class YourModel(models.Model): 
    name = models.CharField(max_length=50) 

    def save_dupe(self): 
     super(YourModel, self).save() 

    def save(self, *args, **kwargs): 
     super(YourModel, self).save(*args, **kwargs) 
     for model in YourModel.objects.exclude(pk=self.pk): 
      model.name = self.name 
      # Repeat the above for all your other fields 
      model.save_dupe() 

Si usted tiene una gran cantidad de campos, es probable que desee iterar sobre ellos cuando copiarlos en el otro modelo. Te lo dejo a ti.

+0

Lo que sucede cuando el modelo que se está guardando es el Usuario y AFAIK no es una buena idea para crear una subclase. Digamos que tengo una función post_save con el remitente como Usuario y dentro de esa función quiero actualizar la instancia. Llamar a guardar nuevamente activará post_save y el servidor local se detendrá. – Marconi

+0

Mejor aún en lugar de 'model.save_dupe()' simplemente haz 'super (YourModel, modelo) .save()'. –

4

Otra forma de manejar esto es eliminar el oyente mientras se guarda. Por lo tanto:

class Foo(models.Model): 
    ... 

def foo_post_save(instance): 
    post_save.disconnect(foo_post_save, sender=Foo) 
    do_stuff_toSaved_instance(instance) 
    instance.save() 
    post_save.connect(foo_post_save, sender=Foo) 

post_save.connect(foo_post_save, sender=Foo) 
+0

¡Me salvaste el día, gracias! –

7

@ShawnFumo Desconexión de una señal es peligroso si el mismo modelo se guarda en otro lugar, al mismo tiempo, no hagas eso!

@Aram Dulyan, ¡su solución funciona pero le impide utilizar señales que son tan potentes!

Si desea evitar la recursión y seguir usando las señales(), una forma sencilla de hacerlo es establecer un atributo en la instancia actual para evitar que las próximas señales se activen.

Esto se puede hacer usando un decorador simple que comprueba si la instancia dada tiene la 'skip_signal' atributo, y si es así evita que el método que se llame:

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 

Ahora podemos usarlo de esta manera:

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): 
    # you processing 
    pass 

m = MyModel() 
# Here we flag the instance with 'skip_signal' 
# and my_model_post_save won't be called 
# thanks to our decorator, avoiding any signal recursion 
m.skip_signal = True 
m.save() 

Espero que esto ayude.

+0

No olvides 'del skip_signal' después de guardar. –