2009-12-17 23 views
14

Tengo problemas con ManytoMany Relationships que no actualizan en un modelo cuando lo guardo (a través del administrador) y trato de usar el nuevo valor dentro de una función adjunta a la señal post_save o dentro del save_model de el asociado AdminModel. He intentado volver a cargar el objeto dentro de esas funciones usando la función get con el id. Pero todavía tiene los valores anteriores.Problema con ManyToMany Relaciones que no se actualizan inmediatamente después de guardar

¿Este es un problema de transacción? ¿Se emite una señal cuando finaliza la transacción ?

Gracias,

+0

Por lo tanto, están cambiando el pk de los objetos? –

+0

Tengo un objeto, y hay una relación manytomany con otra, pero puedo obtener una relación actualizada – diegueus9

Respuesta

20

Al guardar un modelo a través de formas de administración que no es una transacción atómica. El objeto principal se guarda primero (para asegurarse de que tiene un PK), luego el M2M es borrado y los nuevos valores establecidos para lo que salió del formulario. Entonces, si está en el save() del objeto principal, se encuentra en una ventana de oportunidad donde el M2M aún no se ha actualizado. De hecho, si prueba para hacer algo con el M2M, el cambio se borrará con el comando clear(). Me encontré con esto hace aproximadamente un año.

El código ha cambiado un poco desde los días del refactor pre-ORM, pero se reduce al código en django.db.models.fields.ManyRelatedObjectsDescriptor y ReverseManyRelatedObjectsDescriptor. Mire sus métodos __set __() y verá manager.clear(); manager.add(*value) Ese borrar() completo borra cualquier referencia M2M para el objeto principal actual en esa tabla. El complemento() luego establece los nuevos valores.

Para responder a su pregunta: sí, esto es un problema de transacción.

¿Se emite una señal cuando finaliza la transacción? Nada oficial, pero sigue leyendo:

Hubo un related thread a few months ago y MonkeyPatching fue uno de los métodos propuestos. Grégoire posted a MonkeyPatch para esto. No lo he intentado, pero parece que debería funcionar.

+1

Me topé con este problema también, utilicé una solución que se encuentra aquí http://stackoverflow.com/questions/6200233/manytomany -field-not-saved-when-using-django-admin –

+1

Corrígeme si me equivoco, pero esta respuesta de @peterrowell sigue siendo válida a partir de la versión actual de Django (1.10). El campo M2M está ** despejado ** primero y luego se completa con los datos del formulario. –

+0

@nik_m: Ha pasado bastante tiempo desde la última vez que miré esto. El problema fundamental es que esto es intrínsecamente * no * una transacción atómica porque el modelo primario * debe * crearse/modificarse antes de que se cree/actualice m2m (porque el m2m hace referencia a los PKID de los padres). Puede haber una solución alternativa mencionada [aquí] (http://stackoverflow.com/a/23796604/17017) y [aquí] (https://docs.djangoproject.com/en/1.10/ref/signals/# m2m-cambiado). ¡Buena suerte! –

0

Otro enfoque sin un parche de mono es con celery, puede hacer una tarea que tendrá acceso a los datos correctos para la relación M2M, esto es porque la tarea se ejecuta de manera asíncrona y si pone un retraso de 30 segundos, se asegurará de que la transacción haya finalizado.

Debe llamar al Task.apply_async(args=[...], countdown=30) en post_save o pre_save señales.

0

Puede encontrar más información en este hilo: Django manytomany signals?

+0

Lo probé en django 1.4, es inútil, usando la señal 'm2m_changed', asignar valores m2m causaría un error de resurtido, si desconectas y luego asigna, entonces' instance.save() ', la actualización no funcionó del todo. – est

5

Tengo una solución general a esto que parece un poco más limpio que el mono-parchear el núcleo o incluso utilizando el apio (aunque estoy seguro de que alguien podría encontrar áreas donde falla). Básicamente, agrego un método clean() en el administrador para el formulario que tiene las relaciones m2m, y establezco las relaciones de instancia con la versión clean_data. Esto hace que los datos correctos estén disponibles para el método de guardar de la instancia, aunque todavía no esté "en los libros".Probarlo y ver cómo va:

def clean(self, *args, **kwargs): 
    # ... actual cleaning here 
    # then find the m2m fields and copy from cleaned_data to the instance 
    for f in self.instance._meta.get_all_field_names(): 
     if f in self.cleaned_data: 
      field = self.instance._meta.get_field_by_name(f)[0] 
      if isinstance(field, ManyToManyField): 
       setattr(self.instance,f,self.cleaned_data[f]) 
+0

¿hay alguna actualización de este problema para 1.4? – est

3

Ver http://gterzian.github.io/Django-Cookbook/signals/2013/09/07/manipulating-m2m-with-signals.html

problema: Cuando manipula el M2M de un modelo dentro de un poste o receptor de señal pre_save, los cambios se hacen borrosos en la 'limpieza posterior 'del m2m por Django.

solución: En el controlador de señales post o preventa, registre otro controlador en la señal m2m_changed en el modelo intermediario m2m del modelo cuya m2m desea actualizar.

Tenga en cuenta que este segundo controlador recibirá varias señales m2m_changed, y es clave para probar el valor de los argumentos de 'acción' pasados ​​junto con ellos.

Dentro de este segundo controlador, verifique la acción 'post_clear'. Cuando recibes una señal con la acción post_clear, Django ha borrado el m2m y tienes la posibilidad de manipularlo con éxito.

un ejemplo:

def save_handler(sender, instance, *args, **kwargs): 
    m2m_changed.connect(m2m_handler, sender=sender.m2mfield.through, weak=False) 


def m2m_handler(sender, instance, action, *args, **kwargs): 
    if action =='post_clear': 
     succesfully_manipulate_m2m(instance) 


pre_save.connect(save_handler, sender=YouModel, weak=False) 

ver https://docs.djangoproject.com/en/1.5/ref/signals/#m2m-changed

4

Cuando usted está tratando de acceder a los campos de muchos a muchos en la señal post_save del modelo de los objetos relacionados que ya se han eliminado y no se añadirá otra vez hasta después de que la señal haya terminado

Para acceder a estos datos, debe vincular el método save_related en su ModelAdmin. Lamentablemente, también deberá incluir el código en la señal post_save para las solicitudes que no sean de administrador que requieran su personalización.

ver: https://docs.djangoproject.com/en/1.7/ref/contrib/admin/#django.contrib.admin.ModelAdmin.save_related

Ejemplo:

# admin.py 
Class GroupAdmin(admin.ModelAdmin): 
    ... 
    def save_related(self, request, form, formsets, change): 
     super(GroupAdmin, self).save_related(request, form, formsets, change) 
     # do something with the manytomany data from the admin 
     form.instance.users.add(some_user) 

Luego, en sus señales que pueden realizar los mismos cambios que desea ejecutar en una reserva:

# signals.py 
@receiver(post_save, sender=Group) 
def group_post_save(sender, instance, created, **kwargs): 
    # do somethign with the manytomany data from non-admin 
    instance.users.add(some_user) 
    # note that instance.users.all() will be empty from the admin: [] 
+0

esto definitivamente debería ser la respuesta correcta – psychok7

Cuestiones relacionadas