django - example - Problema con ManyToMany Relationships que no se actualiza inmediatamente después de guardar
django signals example (6)
Consulte 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 receptor de señal de post o pre_save, sus cambios se borran en el subsiguiente "borrado" del m2m por Django.
Solución: en el post o el controlador de señales de pre_save, registre otro controlador a la señal m2m_changed en el modelo intermedio de m2m del modelo cuyos m2m desea actualizar.
Tenga en cuenta que este segundo controlador recibirá varias señales de m2m_changed, y es clave probar el valor de los argumentos de ''acción'' que se transmiten junto con ellos.
Dentro de este segundo controlador, verifique la acción ''post_clear''. Cuando recibe una señal con la acción post_clear, Django ha borrado el m2m y tiene la oportunidad 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)
consulte https://docs.djangoproject.com/en/1.5/ref/signals/#m2m-changed
Estoy teniendo problemas con ManytoMany Relationships que no se actualizan en un modelo cuando lo guardo (a través del administrador) y trato de usar el nuevo valor dentro de una función asociada a la señal post_save
o dentro de save_model
del AdminModel
asociado. He intentado volver a cargar el objeto dentro de esas funciones mediante el uso de la función obtener con el id .. pero todavía tiene los valores antiguos.
¿Es este un problema de transacción? ¿Se produce una señal cuando finaliza la transacción?
Gracias,
Cuando guarda un modelo a través de formularios de administración, no es una transacción atómica. El objeto principal se guarda primero (para asegurarse de que tiene una PK), luego se borra el M2M y los nuevos valores se configuran para lo que salió del formulario. Entonces, si está en el guardado () del objeto principal, está en una ventana de oportunidad donde el M2M aún no se ha actualizado. De hecho, si intentas hacerle algo al M2M, el cambio se borrará con el clear (). Me encontré con esto hace aproximadamente un año.
El código ha cambiado un poco desde los días de refactoría de ORM, pero se reduce a codificar en django.db.models.fields.ManyRelatedObjectsDescriptor
y ReverseManyRelatedObjectsDescriptor
. Mira sus métodos __set __ () y verás manager.clear(); manager.add(*value)
manager.clear(); manager.add(*value)
Que clear () complete borra cualquier referencia M2M para el objeto principal actual en esa tabla. El add () luego establece los nuevos valores.
Entonces, para responder a su pregunta: sí, este es un problema de transacción.
¿Se produce una señal cuando finaliza la transacción? Nada oficial, pero sigue leyendo:
Hace unos meses hubo un tema relacionado y MonkeyPatching fue uno de los métodos propuestos. Grégoire publicó un MonkeyPatch para esto. No lo he probado, pero parece que debería funcionar.
Cuando intenta acceder a los campos ManyToMany en la señal post_save del modelo, los objetos relacionados ya se han eliminado y no se volverán a agregar hasta después de que finalice la señal.
Para acceder a estos datos, debe vincularse al método save_related en su ModelAdmin. Desafortunadamente, también tendrá que incluir el código en la señal post_save para solicitudes que no sean de administrador que requieran personalización.
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, puede hacer los mismos cambios que desea ejecutar en un guardado:
# 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: []
Puede encontrar más información en este hilo: ¿ Django muchas señales de muchos?
Tengo una solución general a esto que parece un poco más limpia que parchear el núcleo o incluso usar 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 configuro las relaciones de instancia a la versión cleaner_data. Esto hace que los datos correctos estén disponibles para el método de guardar de la instancia, aunque aún no esté "en los libros". Pruébalo y verás 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])
Una de las soluciones para actualizar m2m, junto con la actualización de uno de sus modelos.
Django 1.11 and higher
En primer lugar, todas las solicitudes a través del panel de administración son atómicas. Puedes mirar ModelAdmin:
@csrf_protect_m def changeform_view(self, request, object_id=None, form_url='''', extra_context=None): with transaction.atomic(using=router.db_for_write(self.model)): return self._changeform_view(request, object_id, form_url, extra_context) @csrf_protect_m def delete_view(self, request, object_id, extra_context=None): with transaction.atomic(using=router.db_for_write(self.model)): return self._delete_view(request, object_id, extra_context)
El comportamiento que puede observar durante la actualización, cuando no se guardaron los cambios que realizó con los registros de m2m, incluso después de haberlos realizado en un método de guardado en uno de sus modelos o en una señal, ocurre solo porque el formulario de m2m reescribe todos los registros después de los principales objeto se actualiza.
Por eso, paso a paso:
El objeto principal se actualiza.
Su código (en un método de guardado o en una señal) hizo cambios (puede mirarlos, simplemente coloque un punto de interrupción en ModelAdmin):
def save_related(self, request, form, formsets, change): breakpoint() form.save_m2m() for formset in formsets: self.save_formset(request, form, formset, change=change)
- form.save_m2m () toma todos los valores de m2m que se colocaron en una página (en términos generales) y reemplaza todos los registros de m2m a través de un administrador relacionado. Es por eso que no puede ver sus cambios al final de una transacción.
Hay una solución: realice sus cambios con m2m a través de
transaction.on_commit
. transaction.on_commit realizará sus cambios después de form.save_m2m () cuando se confirme la transacción.
Desafortunadamente, la desventaja de esta solución: sus cambios con m2m se ejecutarán en una transacción separada.