This is a modification of Django 1.3's transaction.commit_on_success() decorator and context manager. It's inspired by snippet 1343 which unfortunately don't work in current Django neither as a context manager.
In my junior projects it works fine but I've not tested in critical projects yet! YMMV !
How it works: it simply counts the nesting level and does the real transaction enter/exit only on first call and last call respectively (code copied from Django's commit_on_success() ). It use thread local storage to save the per-thread nesting level count safely.
To use it just put the code in a file (i.e. nested_commit_on_success.py) and import and use it exacly as normal commit_on_success(), both as decorator (@nested_commit_success) or context manager (with nested_commit_on_success(): ).
Any feedback is welcome!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
from django.db.transaction import managed, enter_transaction_management, is_dirty, rollback, commit, leave_transaction_management, _transaction_func import threading # storage of nested count _tl = threading.local() def nested_commit_on_success(using=None): def entering(using): lev = getattr(_tl,"level",0) lev += 1 _tl.level = lev if lev >= 2: # is it nested ? return # yes it's nested, do nothing else: # first time, enter transaction enter_transaction_management(using=using) managed(True, using=using) def exiting(exc_value, using): lev = _tl.level _tl.level -= 1 if lev >= 2: # is it nested ? return # yes, do nothing # last time, must do correct transaction ending try: if exc_value is not None: if is_dirty(using=using): rollback(using=using) else: if is_dirty(using=using): try: commit(using=using) except: rollback(using=using) raise finally: leave_transaction_management(using=using) return _transaction_func(entering, exiting, using)