Skip to content

Commit and rollback signals for Django transactions based on Grégoire Cachet's excellent gist.

Notifications You must be signed in to change notification settings

jjmaestro/django-transaction-signals

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 

Repository files navigation

While working on the ASU Digital Repository, I found I needed the ability to trigger callbacks when a transaction was committed. Django's signals seemed to be the perfect mechanism, but transaction commit and rollback signals are not (as of this writing) supported in the core framework. (See these tickets in the Django issue tracker for background: Ticket #14050, Ticket #14051)

However, a gist written by Grégoire Cachet seems to do the trick. It adds a custom signal implementation and monkey-patches Django's transaction handling functions to dispatch post_commit and post_rollback signals.

This module extends that gist in the following ways:

  • Adds a defer() function that can be used inside a transaction to defer the execution of a function until the transaction has committed (I found this quite useful for triggering Celery tasks that depend on the objects committed).
  • Guards against badly behaved signal handlers (i.e. ones that leave the current transaction in a dirty state) and raises a BadlyBehavedTransactionSignalHandlerError when it detects a misbehaving handler.
  • Clears handlers on transaction exit regardless of whether a commit, rollback, or neither occurred. This fixes an issue where handlers could accumulate and be triggered on a subsequent transaction.

Usage (from the original gist):

You have to make sure to load this before you use signals.

For example, add the the following line to your project's __init__.py file:

import django_transaction_signals

Then, to use the signals, create a function and bind it to the post_commit signal:

from django.db import transaction

def my_function(**kwargs):
   # do your stuff here
   pass
transaction.signals.post_commit.connect(my_function)

If you're using non-local variables in your callback function, make sure to use non-weak reference or your variables could be garbage collected before the function gets called. For example, in a model's save() method:

def save(self, *args, **kwargs):
    def my_function(**kwargs):
        # do your stuff here
        # access self variable
        self
    transaction.signals.post_commit.connect(my_function, weak=False)

Usage of defer() function:

defer() registers a function to be run upon successful completion of the current transaction (if one exists). Calling defer(func, *args, **kwargs) translates to the following:

  • If a transaction is active, register a post-commit listener to execute func(*args, **kwargs)
  • If no transaction is active, execute func(*args, **kwargs) immediately

This example demonstrates a transactional update of a model object which registers a Celery task to be executed when the transaction commits successfully.

from celery.task import task
from django.db import transaction
from django_transaction_signals import defer
import pysolr

@transaction.commit_on_success
def update_object(obj):
    # ...modify and save object...
    defer(index_object.delay, obj)
    # ...do some additional work in the transaction...
    
@task
def index_object(obj):
    index_obj = {'id': obj.id}
    # ...build index object...
    solr = pysolr.Solr('http://localhost:8080/solr')
    solr.add([index_obj])

About

Commit and rollback signals for Django transactions based on Grégoire Cachet's excellent gist.

Resources

Stars

Watchers

Forks

Packages

No packages published