[Django]-Trigering post_save signal only after transaction has completed

9👍

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:

  • you are going to inspect the instance in the post_save method and decide that the model was saved successfully or not; simplest way to do that: in the save method, after the transaction executed successfully, annotate your instance with a flag, say instance.saved_successfully = True, which you will test in the post_save handler.
  • you are going to ditch the post_save signal and create a custom signal for yourself, which you will trigger after the transaction ran successfully.

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.

👤Roba

46👍

I think the simplest way is to use transaction.on_commit(). 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.

7👍

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))

1👍

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:

  1. We only call atomic_post_save when inside a transaction.
  2. It’s too late in the flow to send messages and have them included in the current request from inside atomic_post_save.

Leave a comment