Leyendo esta documentación https://docs.djangoproject.com/en/4.0/topics/db/transactions/#django.db.transaction.on_commit
Este es el caso de uso para on_commit
with transaction.atomic(): # Outer atomic, start a new transaction transaction.on_commit(foo) # Do things... with transaction.atomic(): # Inner atomic block, create a savepoint transaction.on_commit(bar) # Do more things... # foo() and then bar() will be called when leaving the outermost block Pero, ¿por qué no simplemente escribir el código como de costumbre sin ganchos on_commit ? Me gusta esto:
with transaction.atomic(): # Outer atomic, start a new transaction # Do things... with transaction.atomic(): # Inner atomic block, create a savepoint # Do more things... foo() bar() # foo() and then bar() will be called when leaving the outermost blockEs más fácil de leer ya que no requiere más conocimiento de las API de Django y las declaraciones se ponen en el orden en que se ejecutan. Es más fácil de probar ya que no tienes que usar ninguna clase de prueba especial para Django.
Entonces, ¿cuál es el caso de uso para el gancho on_commit ?
Documentación de Django:
Django proporciona la función on_commit() para registrar funciones de devolución de llamada que deben ejecutarse después de que una transacción se confirme con éxito
Es el propósito principal. Una transacción es una unidad de trabajo que desea tratar atómicamente. O sucede completamente o no sucede en absoluto. Lo mismo se aplica a su código. Si algo salió mal durante las operaciones de la base de datos, es posible que no necesite hacer algunas cosas.
Consideremos un flujo de lógica empresarial:
Si algo sale mal durante el paso 2, no deberíamos ir al paso 3.
Podemos pensar que, bueno, obtendré una excepción y no ejecutaré ese código también. ¿Por qué todavía lo necesitamos?
A veces, realiza acciones en su código en función de la suposición de que la transacción es exitosa antes de las operaciones de base de datos potencialmente peligrosas. Por ejemplo, primero desea verificar si puede enviar un correo electrónico a su usuario, porque sabe que su tercero de correo electrónico a menudo le otorga 500. En ese caso, desea recaudar 500 para el usuario y pedirle que se registre más tarde. (una muy mala idea, por cierto, pero es solo un ejemplo sintético).
Cuando su función (por ejemplo, con el decorador @atomic ) contiene muchas operaciones de base de datos, seguramente no querrá memorizar todos los estados de las variables para usarlas después de todo el código relacionado con la base de datos. Me gusta esto:
Puedes imaginarte el lío que tendríamos si no nos on_commit en esta situación y tuviéramos un gran try-catch esto.
El código de ejemplo proporcionado en los documentos de Django es transaction.on_commit(lambda: some_celery_task.delay('arg1')) y probablemente se deba específicamente a que esto surge mucho con las tareas de apio.
Imagínese si hace lo siguiente dentro de una transacción:
my_object = MyObject.objects.create() some_celery_task.delay(my_object.pk)Luego, en su tarea de apio, intente hacer esto:
@app.task def some_celery_task(object_pk) my_object = MyObject.objects.get(pk=object_pk) Esto puede funcionar la mayor parte del tiempo, pero al azar obtendrá errores en los que no puede encontrar el objeto (dependiendo de qué tan rápido se ejecute la tarea de trabajo porque es una condición de carrera). Esto se debe a que creó un registro MyObject dentro de una transacción, pero en realidad no está disponible en la base de datos hasta que se ejecuta COMMIT . Celery no tiene acceso a esa transacción abierta, por lo que debe ejecutarse después de COMMIT . También existe la posibilidad muy real de que algo más tarde provoque un ROLLBACK y esa tarea de apio nunca se deba llamar.
Entonces... Tienes que hacer:
my_object = MyObject.objects.create() transaction.on_commit(lambda: some_celery_task.delay(my_object.pk)) Ahora, la tarea de apio no se llamará hasta MyObject se haya guardado en la base de datos después de llamar a COMMIT .