Reputation: 433
I have written some APIs, for which the respective functions executive inside a transaction block. I am calling the save()
method (after some modifications) on instance/s of a/several Model/s, and also consecutively indexing some JSON related information of the instance/s in Elasticsearch. I want the database to rollback even if for some reason the save()
for one of the instances or indexing to the Elasticsearch fails.
Now, the problem is arising that even inside the transaction block, the post_save()
signals gets called, and that is an issue because some notifications are being triggered from those signals.
Is there a way to trigger post_save()
signals only after the transactions have completed successful?
Upvotes: 34
Views: 16171
Reputation: 959
I think the simplest way is to use transaction.on_commit()
(Django 2.1, Django 5.0). Here's an example using the models.Model subclass Photo
that will only talk to Elasticsearch once the current transaction is over:
from django.db import transaction
from django.db.models.signals import post_save
@receiver(post_save, sender=Photo)
def save_photo(**kwargs):
transaction.on_commit(lambda: talk_to_elasticsearch(kwargs['instance']))
Note that if the transaction.on_commit()
gets executed while not in an active transaction, it will run right away.
Upvotes: 50
Reputation: 6798
EDIT: This answer is no longer relevant after on_commit was introduced in Django.
We are using this little nugget:
def atomic_post_save(sender, instance, **kwargs):
if hasattr(instance, "atomic_post_save") and transaction.get_connection().in_atomic_block:
transaction.on_commit(lambda: instance.atomic_post_save(sender, instance=instance, **kwargs))
post_save.connect(atomic_post_save)
Then we simply define a atomic_post_save
method on any model we like:
class MyModel(Model):
def atomic_post_save(self, sender, created, **kwargs):
talk_to_elasticsearch(self)
Two things to notice:
atomic_post_save
when inside a transaction.atomic_post_save
.Upvotes: 2
Reputation: 588
I was having serious issues with django's admin not allowing post_save transactions on parent objects when they had inline children being modified.
This was my solution to an error complaining about conducting queries in the middle of an atomic block:
def on_user_post_save_impl(user):
do_something_to_the_user(user)
def on_user_post_save(sender, instance, **kwargs):
if not transaction.get_connection().in_atomic_block:
on_user_post_save_impl(instance)
else:
transaction.on_commit(lambda: on_user_post_save_impl(instance))
Upvotes: 7
Reputation: 688
Not really. The signals have nothing to do with the db transaction success or failure, but with the save method itself - before the call you have the pre_save signal fired and after the call you have the post_save signal fired.
There are 2 approaches here:
instance.saved_successfully = True
, which you will test in the post_save handler.Makes sense?
P.S.
If you strictly need to bind to the transaction commit signal, have a look over this package: https://django-transaction-hooks.readthedocs.org/en/latest/; it looks like the functionality is integrated in Django 1.9a.
Upvotes: 9