summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/releases/1.9.txt13
-rw-r--r--docs/topics/db/transactions.txt144
2 files changed, 157 insertions, 0 deletions
diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt
index a4cbd7e46d..2716932d7b 100644
--- a/docs/releases/1.9.txt
+++ b/docs/releases/1.9.txt
@@ -25,6 +25,19 @@ Python 3.2 and 3.3, and added support for Python 3.5.
What's new in Django 1.9
========================
+Performing actions after a transaction commit
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The new :func:`~django.db.transaction.on_commit` hook allows performing actions
+after a database transaction is successfully committed. This is useful for
+tasks such as sending notification emails, creating queued tasks, or
+invalidating caches.
+
+This functionality from the `django-transaction-hooks`_ package has been
+integrated into Django.
+
+.. _django-transaction-hooks: https://pypi.python.org/pypi/django-transaction-hooks
+
Password validation
~~~~~~~~~~~~~~~~~~~
diff --git a/docs/topics/db/transactions.txt b/docs/topics/db/transactions.txt
index 6b54510a47..b507b3ff67 100644
--- a/docs/topics/db/transactions.txt
+++ b/docs/topics/db/transactions.txt
@@ -252,6 +252,150 @@ by Django or by third-party libraries. Thus, this is best used in situations
where you want to run your own transaction-controlling middleware or do
something really strange.
+Performing actions after commit
+===============================
+
+.. versionadded:: 1.9
+
+Sometimes you need to perform an action related to the current database
+transaction, but only if the transaction successfully commits. Examples might
+include a `Celery`_ task, an email notification, or a cache invalidation.
+
+.. _Celery: http://www.celeryproject.org/
+
+Django provides the :func:`on_commit` function to register callback functions
+that should be executed after a transaction is successfully committed:
+
+.. function:: on_commit(func, using=None)
+
+Pass any function (that takes no arguments) to :func:`on_commit`::
+
+ from django.db import transaction
+
+ def do_something():
+ pass # send a mail, invalidate a cache, fire off a Celery task, etc.
+
+ transaction.on_commit(do_something)
+
+You can also wrap your function in a lambda::
+
+ transaction.on_commit(lambda: some_celery_task.delay('arg1'))
+
+The function you pass in will be called immediately after a hypothetical
+database write made where ``on_commit()`` is called would be successfully
+committed.
+
+If you call ``on_commit()`` while there isn't an active transaction, the
+callback will be executed immediately.
+
+If that hypothetical database write is instead rolled back (typically when an
+unhandled exception is raised in an :func:`atomic` block), your function will
+be discarded and never called.
+
+Savepoints
+----------
+
+Savepoints (i.e. nested :func:`atomic` blocks) are handled correctly. That is,
+an :func:`on_commit` callable registered after a savepoint (in a nested
+:func:`atomic` block) will be called after the outer transaction is committed,
+but not if a rollback to that savepoint or any previous savepoint occurred
+during the transaction::
+
+ with transaction.atomic(): # Outer atomic, start a new transaction
+ transaction.on_commit(foo)
+
+ with transaction.atomic(): # Inner atomic block, create a savepoint
+ transaction.on_commit(bar)
+
+ # foo() and then bar() will be called when leaving the outermost block
+
+On the other hand, when a savepoint is rolled back (due to an exception being
+raised), the inner callable will not be called::
+
+ with transaction.atomic(): # Outer atomic, start a new transaction
+ transaction.on_commit(foo)
+
+ try:
+ with transaction.atomic(): # Inner atomic block, create a savepoint
+ transaction.on_commit(bar)
+ raise SomeError() # Raising an exception - abort the savepoint
+ except SomeError:
+ pass
+
+ # foo() will be called, but not bar()
+
+Order of execution
+------------------
+
+On-commit functions for a given transaction are executed in the order they were
+registered.
+
+Exception handling
+------------------
+
+If one on-commit function within a given transaction raises an uncaught
+exception, no later registered functions in that same transaction will run.
+This is, of course, the same behavior as if you'd executed the functions
+sequentially yourself without :func:`on_commit`.
+
+Timing of execution
+-------------------
+
+Your callbacks are executed *after* a successful commit, so a failure in a
+callback will not cause the transaction to roll back. They are executed
+conditionally upon the success of the transaction, but they are not *part* of
+the transaction. For the intended use cases (mail notifications, Celery tasks,
+etc.), this should be fine. If it's not (if your follow-up action is so
+critical that its failure should mean the failure of the transaction itself),
+then you don't want to use the :func:`on_commit` hook. Instead, you may want
+`two-phase commit`_ such as the `psycopg Two-Phase Commit protocol support`_
+and the `optional Two-Phase Commit Extensions in the Python DB-API
+specification`_.
+
+Callbacks are not run until autocommit is restored on the connection following
+the commit (because otherwise any queries done in a callback would open an
+implicit transaction, preventing the connection from going back into autocommit
+mode).
+
+When in autocommit mode and outside of an :func:`atomic` block, the function
+will run immediately, not on commit.
+
+On-commit functions only work with :ref:`autocommit mode <managing-autocommit>`
+and the :func:`atomic` (or :setting:`ATOMIC_REQUESTS
+<DATABASE-ATOMIC_REQUESTS>`) transaction API. Calling :func:`on_commit` when
+autocommit is disabled and you are not within an atomic block will result in an
+error.
+
+.. _two-phase commit: http://en.wikipedia.org/wiki/Two-phase_commit_protocol
+.. _psycopg Two-Phase Commit protocol support: http://initd.org/psycopg/docs/usage.html#tpc
+.. _optional Two-Phase Commit Extensions in the Python DB-API specification: https://www.python.org/dev/peps/pep-0249/#optional-two-phase-commit-extensions
+
+Use in tests
+------------
+
+Django's :class:`~django.test.TestCase` class wraps each test in a transaction
+and rolls back that transaction after each test, in order to provide test
+isolation. This means that no transaction is ever actually committed, thus your
+:func:`on_commit` callbacks will never be run. If you need to test the results
+of an :func:`on_commit` callback, use a
+:class:`~django.test.TransactionTestCase` instead.
+
+Why no rollback hook?
+---------------------
+
+A rollback hook is harder to implement robustly than a commit hook, since a
+variety of things can cause an implicit rollback.
+
+For instance, if your database connection is dropped because your process was
+killed without a chance to shut down gracefully, your rollback hook will never
+run.
+
+The solution is simple: instead of doing something during the atomic block
+(transaction) and then undoing it if the transaction fails, use
+:func:`on_commit` to delay doing it in the first place until after the
+transaction succeeds. It’s a lot easier to undo something you never did in the
+first place!
+
Low-level APIs
==============