From 7c46c8d5f27fe305507359588ca0635b6d87c59a Mon Sep 17 00:00:00 2001
From: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon, 4 Mar 2013 23:26:31 +0100
Subject: [PATCH] Added some assertions to enforce the atomicity of atomic.

---
 django/db/__init__.py                 |   1 +
 django/db/backends/__init__.py        |  15 +
 django/db/transaction.py              |  18 +-
 docs/internals/deprecation.txt        |   4 +
 docs/releases/1.3-alpha-1.txt         |   6 +-
 docs/releases/1.3.txt                 |   6 +-
 docs/releases/1.6.txt                 |  17 +-
 docs/topics/db/transactions.txt       | 459 ++++++++++++--------------
 tests/backends/tests.py               |  15 +-
 tests/fixtures_model_package/tests.py |  11 +-
 tests/fixtures_regress/tests.py       |   7 +-
 tests/middleware/tests.py             |   6 +-
 tests/transactions/tests.py           |  71 +++-
 tests/transactions_regress/tests.py   |  12 +-
 14 files changed, 369 insertions(+), 279 deletions(-)

diff --git a/django/db/__init__.py b/django/db/__init__.py
index 13ba68ba7e..08c901ab7b 100644
--- a/django/db/__init__.py
+++ b/django/db/__init__.py
@@ -70,6 +70,7 @@ signals.request_started.connect(reset_queries)
 # their lifetime. NB: abort() doesn't do anything outside of a transaction.
 def close_old_connections(**kwargs):
     for conn in connections.all():
+        # Remove this when the legacy transaction management goes away.
         try:
             conn.abort()
         except DatabaseError:
diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
index 818850bf43..346d10198d 100644
--- a/django/db/backends/__init__.py
+++ b/django/db/backends/__init__.py
@@ -157,6 +157,7 @@ class BaseDatabaseWrapper(object):
         Commits a transaction and resets the dirty flag.
         """
         self.validate_thread_sharing()
+        self.validate_no_atomic_block()
         self._commit()
         self.set_clean()
 
@@ -165,6 +166,7 @@ class BaseDatabaseWrapper(object):
         Rolls back a transaction and resets the dirty flag.
         """
         self.validate_thread_sharing()
+        self.validate_no_atomic_block()
         self._rollback()
         self.set_clean()
 
@@ -265,6 +267,8 @@ class BaseDatabaseWrapper(object):
         If you switch off transaction management and there is a pending
         commit/rollback, the data will be commited, unless "forced" is True.
         """
+        self.validate_no_atomic_block()
+
         self.transaction_state.append(managed)
 
         if not managed and self.is_dirty() and not forced:
@@ -280,6 +284,8 @@ class BaseDatabaseWrapper(object):
         over to the surrounding block, as a commit will commit all changes, even
         those from outside. (Commits are on connection level.)
         """
+        self.validate_no_atomic_block()
+
         if self.transaction_state:
             del self.transaction_state[-1]
         else:
@@ -305,10 +311,19 @@ class BaseDatabaseWrapper(object):
         """
         Enable or disable autocommit.
         """
+        self.validate_no_atomic_block()
         self.ensure_connection()
         self._set_autocommit(autocommit)
         self.autocommit = autocommit
 
+    def validate_no_atomic_block(self):
+        """
+        Raise an error if an atomic block is active.
+        """
+        if self.in_atomic_block:
+            raise TransactionManagementError(
+                "This is forbidden when an 'atomic' block is active.")
+
     def abort(self):
         """
         Roll back any ongoing transaction and clean the transaction state
diff --git a/django/db/transaction.py b/django/db/transaction.py
index 8126c18a70..eb9d85e274 100644
--- a/django/db/transaction.py
+++ b/django/db/transaction.py
@@ -367,6 +367,9 @@ def autocommit(using=None):
     this decorator is useful if you globally activated transaction management in
     your settings file and want the default behavior in some view functions.
     """
+    warnings.warn("autocommit is deprecated in favor of set_autocommit.",
+        PendingDeprecationWarning, stacklevel=2)
+
     def entering(using):
         enter_transaction_management(managed=False, using=using)
 
@@ -382,6 +385,9 @@ def commit_on_success(using=None):
     a rollback is made. This is one of the most common ways to do transaction
     control in Web apps.
     """
+    warnings.warn("commit_on_success is deprecated in favor of atomic.",
+        PendingDeprecationWarning, stacklevel=2)
+
     def entering(using):
         enter_transaction_management(using=using)
 
@@ -409,6 +415,9 @@ def commit_manually(using=None):
     own -- it's up to the user to call the commit and rollback functions
     themselves.
     """
+    warnings.warn("commit_manually is deprecated in favor of set_autocommit.",
+        PendingDeprecationWarning, stacklevel=2)
+
     def entering(using):
         enter_transaction_management(using=using)
 
@@ -420,10 +429,15 @@ def commit_manually(using=None):
 def commit_on_success_unless_managed(using=None):
     """
     Transitory API to preserve backwards-compatibility while refactoring.
+
+    Once the legacy transaction management is fully deprecated, this should
+    simply be replaced by atomic. Until then, it's necessary to avoid making a
+    commit where Django didn't use to, since entering atomic in managed mode
+    triggers a commmit.
     """
     connection = get_connection(using)
-    if connection.autocommit and not connection.in_atomic_block:
-        return commit_on_success(using)
+    if connection.autocommit or connection.in_atomic_block:
+        return atomic(using)
     else:
         def entering(using):
             pass
diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt
index 1c8618713a..6c13af7ae4 100644
--- a/docs/internals/deprecation.txt
+++ b/docs/internals/deprecation.txt
@@ -329,6 +329,10 @@ these changes.
 1.8
 ---
 
+* The decorators and context managers ``django.db.transaction.autocommit``,
+  ``commit_on_success`` and ``commit_manually`` will be removed. See
+  :ref:`transactions-upgrading-from-1.5`.
+
 * The :ttag:`cycle` and :ttag:`firstof` template tags will auto-escape their
   arguments. In 1.6 and 1.7, this behavior is provided by the version of these
   tags in the ``future`` template tag library.
diff --git a/docs/releases/1.3-alpha-1.txt b/docs/releases/1.3-alpha-1.txt
index ba8a4fc557..53d38a006b 100644
--- a/docs/releases/1.3-alpha-1.txt
+++ b/docs/releases/1.3-alpha-1.txt
@@ -105,16 +105,14 @@ you just won't get any of the nice new unittest2 features.
 Transaction context managers
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Users of Python 2.5 and above may now use :ref:`transaction management functions
-<transaction-management-functions>` as `context managers`_. For example::
+Users of Python 2.5 and above may now use transaction management functions as
+`context managers`_. For example::
 
     with transaction.autocommit():
         # ...
 
 .. _context managers: http://docs.python.org/glossary.html#term-context-manager
 
-For more information, see :ref:`transaction-management-functions`.
-
 Configurable delete-cascade
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/docs/releases/1.3.txt b/docs/releases/1.3.txt
index 4c8dd2f81f..582bceffca 100644
--- a/docs/releases/1.3.txt
+++ b/docs/releases/1.3.txt
@@ -148,16 +148,14 @@ you just won't get any of the nice new unittest2 features.
 Transaction context managers
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Users of Python 2.5 and above may now use :ref:`transaction management functions
-<transaction-management-functions>` as `context managers`_. For example::
+Users of Python 2.5 and above may now use transaction management functions as
+`context managers`_. For example::
 
     with transaction.autocommit():
         # ...
 
 .. _context managers: http://docs.python.org/glossary.html#term-context-manager
 
-For more information, see :ref:`transaction-management-functions`.
-
 Configurable delete-cascade
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt
index c55ef0ef38..cc3bf94ef5 100644
--- a/docs/releases/1.6.txt
+++ b/docs/releases/1.6.txt
@@ -39,7 +39,7 @@ should improve performance. The existing APIs were deprecated, and new APIs
 were introduced, as described in :doc:`/topics/db/transactions`.
 
 Please review carefully the list of :ref:`known backwards-incompatibilities
-<transactions-changes-from-1.5>` to determine if you need to make changes in
+<transactions-upgrading-from-1.5>` to determine if you need to make changes in
 your code.
 
 Persistent database connections
@@ -163,7 +163,7 @@ Backwards incompatible changes in 1.6
 * Database-level autocommit is enabled by default in Django 1.6. While this
   doesn't change the general spirit of Django's transaction management, there
   are a few known backwards-incompatibities, described in the :ref:`transaction
-  management docs <transactions-changes-from-1.5>`. You should review your code
+  management docs <transactions-upgrading-from-1.5>`. You should review your code
   to determine if you're affected.
 
 * In previous versions, database-level autocommit was only an option for
@@ -256,6 +256,19 @@ Backwards incompatible changes in 1.6
 Features deprecated in 1.6
 ==========================
 
+Transaction management APIs
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Transaction management was completely overhauled in Django 1.6, and the
+current APIs are deprecated:
+
+- :func:`django.db.transaction.autocommit`
+- :func:`django.db.transaction.commit_on_success`
+- :func:`django.db.transaction.commit_manually`
+
+The reasons for this change and the upgrade path are described in the
+:ref:`transactions documentation <transactions-upgrading-from-1.5>`.
+
 Changes to :ttag:`cycle` and :ttag:`firstof`
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/docs/topics/db/transactions.txt b/docs/topics/db/transactions.txt
index 2a4cd306c6..91b2cf41b3 100644
--- a/docs/topics/db/transactions.txt
+++ b/docs/topics/db/transactions.txt
@@ -24,7 +24,7 @@ immediately committed to the database. :ref:`See below for details
 
 .. versionchanged:: 1.6
     Previous version of Django featured :ref:`a more complicated default
-    behavior <transactions-changes-from-1.5>`.
+    behavior <transactions-upgrading-from-1.5>`.
 
 Tying transactions to HTTP requests
 -----------------------------------
@@ -89,7 +89,7 @@ Django provides a single API to control database transactions.
     database. If this argument isn't provided, Django uses the ``"default"``
     database.
 
-    ``atomic`` is usable both as a decorator::
+    ``atomic`` is usable both as a `decorator`_::
 
         from django.db import transaction
 
@@ -98,7 +98,7 @@ Django provides a single API to control database transactions.
             # This code executes inside a transaction.
             do_stuff()
 
-    and as a context manager::
+    and as a `context manager`_::
 
         from django.db import transaction
 
@@ -110,6 +110,9 @@ Django provides a single API to control database transactions.
                 # This code executes inside a transaction.
                 do_more_stuff()
 
+    .. _decorator: http://docs.python.org/glossary.html#term-decorator
+    .. _context manager: http://docs.python.org/glossary.html#term-context-manager
+
     Wrapping ``atomic`` in a try/except block allows for natural handling of
     integrity errors::
 
@@ -145,189 +148,6 @@ Django provides a single API to control database transactions.
     - releases or rolls back to the savepoint when exiting an inner block;
     - commits or rolls back the transaction when exiting the outermost block.
 
-.. _transaction-management-functions:
-
-Controlling transaction management in views
-===========================================
-
-For most people, implicit request-based transactions work wonderfully. However,
-if you need more fine-grained control over how transactions are managed, you can
-use a set of functions in ``django.db.transaction`` to control transactions on a
-per-function or per-code-block basis.
-
-These functions, described in detail below, can be used in two different ways:
-
-* As a decorator_ on a particular function. For example::
-
-    from django.db import transaction
-
-    @transaction.commit_on_success
-    def viewfunc(request):
-        # ...
-        # this code executes inside a transaction
-        # ...
-
-* As a `context manager`_ around a particular block of code::
-
-    from django.db import transaction
-
-    def viewfunc(request):
-        # ...
-        # this code executes using default transaction management
-        # ...
-
-        with transaction.commit_on_success():
-            # ...
-            # this code executes inside a transaction
-            # ...
-
-Both techniques work with all supported version of Python.
-
-.. _decorator: http://docs.python.org/glossary.html#term-decorator
-.. _context manager: http://docs.python.org/glossary.html#term-context-manager
-
-For maximum compatibility, all of the examples below show transactions using the
-decorator syntax, but all of the follow functions may be used as context
-managers, too.
-
-.. note::
-
-    Although the examples below use view functions as examples, these
-    decorators and context managers can be used anywhere in your code
-    that you need to deal with transactions.
-
-.. _topics-db-transactions-autocommit:
-
-.. function:: autocommit
-
-    Use the ``autocommit`` decorator to switch a view function to Django's
-    default commit behavior.
-
-    Example::
-
-        from django.db import transaction
-
-        @transaction.autocommit
-        def viewfunc(request):
-            ....
-
-        @transaction.autocommit(using="my_other_database")
-        def viewfunc2(request):
-            ....
-
-    Within ``viewfunc()``, transactions will be committed as soon as you call
-    ``model.save()``, ``model.delete()``, or any other function that writes to
-    the database.  ``viewfunc2()`` will have this same behavior, but for the
-    ``"my_other_database"`` connection.
-
-.. function:: commit_on_success
-
-    Use the ``commit_on_success`` decorator to use a single transaction for all
-    the work done in a function::
-
-        from django.db import transaction
-
-        @transaction.commit_on_success
-        def viewfunc(request):
-            ....
-
-        @transaction.commit_on_success(using="my_other_database")
-        def viewfunc2(request):
-            ....
-
-    If the function returns successfully, then Django will commit all work done
-    within the function at that point. If the function raises an exception,
-    though, Django will roll back the transaction.
-
-.. function:: commit_manually
-
-    Use the ``commit_manually`` decorator if you need full control over
-    transactions. It tells Django you'll be managing the transaction on your
-    own.
-
-    Whether you are writing or simply reading from the database, you must
-    ``commit()`` or ``rollback()`` explicitly or Django will raise a
-    :exc:`TransactionManagementError` exception. This is required when reading
-    from the database because ``SELECT`` statements may call functions which
-    modify tables, and thus it is impossible to know if any data has been
-    modified.
-
-    Manual transaction management looks like this::
-
-        from django.db import transaction
-
-        @transaction.commit_manually
-        def viewfunc(request):
-            ...
-            # You can commit/rollback however and whenever you want
-            transaction.commit()
-            ...
-
-            # But you've got to remember to do it yourself!
-            try:
-                ...
-            except:
-                transaction.rollback()
-            else:
-                transaction.commit()
-
-        @transaction.commit_manually(using="my_other_database")
-        def viewfunc2(request):
-            ....
-
-.. _topics-db-transactions-requirements:
-
-Requirements for transaction handling
-=====================================
-
-Django requires that every transaction that is opened is closed before the
-completion of a request.
-
-If you are using :func:`autocommit` (the default commit mode) or
-:func:`commit_on_success`, this will be done for you automatically. However,
-if you are manually managing transactions (using the :func:`commit_manually`
-decorator), you must ensure that the transaction is either committed or rolled
-back before a request is completed.
-
-This applies to all database operations, not just write operations. Even
-if your transaction only reads from the database, the transaction must
-be committed or rolled back before you complete a request.
-
-.. _managing-autocommit:
-
-Managing autocommit
-===================
-
-.. versionadded:: 1.6
-
-Django provides a straightforward API to manage the autocommit state of each
-database connection, if you need to.
-
-.. function:: get_autocommit(using=None)
-
-.. function:: set_autocommit(using=None, autocommit=True)
-
-These functions take a ``using`` argument which should be the name of a
-database. If it isn't provided, Django uses the ``"default"`` database.
-
-.. _deactivate-transaction-management:
-
-How to globally deactivate transaction management
-=================================================
-
-Control freaks can totally disable all transaction management by setting
-:setting:`TRANSACTIONS_MANAGED` to ``True`` in the Django settings file. If
-you do this, Django won't enable autocommit. You'll get the regular behavior
-of the underlying database library.
-
-This requires you to commit explicitly every transaction, even those started
-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.
-
-In almost all situations, you'll be better off using the default behavior, or
-the transaction middleware, and only modify selected functions as needed.
-
 .. _topics-db-transactions-savepoints:
 
 Savepoints
@@ -339,13 +159,19 @@ available with the SQLite (≥ 3.6.8), PostgreSQL, Oracle and MySQL (when using
 the InnoDB storage engine) backends. Other backends provide the savepoint
 functions, but they're empty operations -- they don't actually do anything.
 
-Savepoints aren't especially useful if you are using the default
-``autocommit`` behavior of Django. However, if you are using
-``commit_on_success`` or ``commit_manually``, each open transaction will build
-up a series of database operations, awaiting a commit or rollback. If you
-issue a rollback, the entire transaction is rolled back. Savepoints provide
-the ability to perform a fine-grained rollback, rather than the full rollback
-that would be performed by ``transaction.rollback()``.
+Savepoints aren't especially useful if you are using autocommit, the default
+behavior of Django. However, once you open a transaction with :func:`atomic`,
+you build up a series of database operations awaiting a commit or rollback. If
+you issue a rollback, the entire transaction is rolled back. Savepoints
+provide the ability to perform a fine-grained rollback, rather than the full
+rollback that would be performed by ``transaction.rollback()``.
+
+.. versionchanged:: 1.6
+
+When the :func:`atomic` decorator is nested, it creates a savepoint to allow
+partial commit or rollback. You're strongly encouraged to use :func:`atomic`
+rather than the functions described below, but they're still part of the
+public API, and there's no plan to deprecate them.
 
 Each of these functions takes a ``using`` argument which should be the name of
 a database for which the behavior applies.  If no ``using`` argument is
@@ -374,15 +200,17 @@ The following example demonstrates the use of savepoints::
 
     from django.db import transaction
 
-    @transaction.commit_manually
+    # open a transaction
+    @transaction.atomic
     def viewfunc(request):
 
       a.save()
-      # open transaction now contains a.save()
+      # transaction now contains a.save()
+
       sid = transaction.savepoint()
 
       b.save()
-      # open transaction now contains a.save() and b.save()
+      # transaction now contains a.save() and b.save()
 
       if want_to_keep_b:
           transaction.savepoint_commit(sid)
@@ -391,7 +219,82 @@ The following example demonstrates the use of savepoints::
           transaction.savepoint_rollback(sid)
           # open transaction now contains only a.save()
 
-      transaction.commit()
+Autocommit
+==========
+
+.. _autocommit-details:
+
+Why Django uses autocommit
+--------------------------
+
+In the SQL standards, each SQL query starts a transaction, unless one is
+already in progress. Such transactions must then be committed or rolled back.
+
+This isn't always convenient for application developers. To alleviate this
+problem, most databases provide an autocommit mode. When autocommit is turned
+on, each SQL query is wrapped in its own transaction. In other words, the
+transaction is not only automatically started, but also automatically
+committed.
+
+:pep:`249`, the Python Database API Specification v2.0, requires autocommit to
+be initially turned off. Django overrides this default and turns autocommit
+on.
+
+To avoid this, you can :ref:`deactivate the transaction management
+<deactivate-transaction-management>`, but it isn't recommended.
+
+.. versionchanged:: 1.6
+    Before Django 1.6, autocommit was turned off, and it was emulated by
+    forcing a commit after write operations in the ORM.
+
+.. warning::
+
+    If you're using the database API directly — for instance, you're running
+    SQL queries with ``cursor.execute()`` — be aware that autocommit is on,
+    and consider wrapping your operations in a transaction, with
+    :func:`atomic`, to ensure consistency.
+
+.. _managing-autocommit:
+
+Managing autocommit
+-------------------
+
+.. versionadded:: 1.6
+
+Django provides a straightforward API to manage the autocommit state of each
+database connection, if you need to.
+
+.. function:: get_autocommit(using=None)
+
+.. function:: set_autocommit(using=None, autocommit=True)
+
+These functions take a ``using`` argument which should be the name of a
+database. If it isn't provided, Django uses the ``"default"`` database.
+
+Autocommit is initially turned on. If you turn it off, it's your
+responsibility to restore it.
+
+:func:`atomic` requires autocommit to be turned on; it will raise an exception
+if autocommit is off. Django will also refuse to turn autocommit off when an
+:func:`atomic` block is active, because that would break atomicity.
+
+.. _deactivate-transaction-management:
+
+Deactivating transaction management
+-----------------------------------
+
+Control freaks can totally disable all transaction management by setting
+:setting:`TRANSACTIONS_MANAGED` to ``True`` in the Django settings file. If
+you do this, Django won't enable autocommit. You'll get the regular behavior
+of the underlying database library.
+
+This requires you to commit explicitly every transaction, even those started
+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.
+
+In almost all situations, you'll be better off using the default behavior, or
+the transaction middleware, and only modify selected functions as needed.
 
 Database-specific notes
 =======================
@@ -477,45 +380,57 @@ transaction. For example::
 In this example, ``a.save()`` will not be undone in the case where
 ``b.save()`` raises an exception.
 
-Under the hood
-==============
+.. _transactions-upgrading-from-1.5:
 
-.. _autocommit-details:
+Changes from Django 1.5 and earlier
+===================================
 
-Details on autocommit
----------------------
+The features described below were deprecated in Django 1.6 and will be removed
+in Django 1.8. They're documented in order to ease the migration to the new
+transaction management APIs.
 
-In the SQL standards, each SQL query starts a transaction, unless one is
-already in progress. Such transactions must then be committed or rolled back.
+Legacy APIs
+-----------
 
-This isn't always convenient for application developers. To alleviate this
-problem, most databases provide an autocommit mode. When autocommit is turned
-on, each SQL query is wrapped in its own transaction. In other words, the
-transaction is not only automatically started, but also automatically
-committed.
+The following functions, defined in ``django.db.transaction``, provided a way
+to control transactions on a per-function or per-code-block basis. They could
+be used as decorators or as context managers, and they accepted a ``using``
+argument, exactly like :func:`atomic`.
 
-:pep:`249`, the Python Database API Specification v2.0, requires autocommit to
-be initially turned off. Django overrides this default and turns autocommit
-on.
+.. function:: autocommit
 
-To avoid this, you can :ref:`deactivate the transaction management
-<deactivate-transaction-management>`, but it isn't recommended.
+    Enable Django's default autocommit behavior.
 
-.. versionchanged:: 1.6
-    Before Django 1.6, autocommit was turned off, and it was emulated by
-    forcing a commit after write operations in the ORM.
+    Transactions will be committed as soon as you call ``model.save()``,
+    ``model.delete()``, or any other function that writes to the database.
 
-.. warning::
+.. function:: commit_on_success
 
-    If you're using the database API directly — for instance, you're running
-    SQL queries with ``cursor.execute()`` — be aware that autocommit is on,
-    and consider wrapping your operations in a transaction to ensure
-    consistency.
+    Use a single transaction for all the work done in a function.
+
+    If the function returns successfully, then Django will commit all work done
+    within the function at that point. If the function raises an exception,
+    though, Django will roll back the transaction.
+
+.. function:: commit_manually
+
+    Tells Django you'll be managing the transaction on your own.
+
+    Whether you are writing or simply reading from the database, you must
+    ``commit()`` or ``rollback()`` explicitly or Django will raise a
+    :exc:`TransactionManagementError` exception. This is required when reading
+    from the database because ``SELECT`` statements may call functions which
+    modify tables, and thus it is impossible to know if any data has been
+    modified.
 
 .. _transaction-states:
 
-Transaction management states
------------------------------
+Transaction states
+------------------
+
+The three functions described above relied on a concept called "transaction
+states". This mechanisme was deprecated in Django 1.6, but it's still
+available until Django 1.8..
 
 At any time, each database connection is in one of these two states:
 
@@ -529,35 +444,80 @@ Django starts in auto mode. ``TransactionMiddleware``,
 Internally, Django keeps a stack of states. Activations and deactivations must
 be balanced.
 
-For example, at the beginning of each HTTP request, ``TransactionMiddleware``
-switches to managed mode; at the end of the request, it commits or rollbacks,
+For example, ``commit_on_success`` switches to managed mode when entering the
+block of code it controls; when exiting the block, it commits or rollbacks,
 and switches back to auto mode.
 
-.. admonition:: Nesting decorators / context managers
+So :func:`commit_on_success` really has two effects: it changes the
+transaction state and it defines an transaction block. Nesting will give the
+expected results in terms of transaction state, but not in terms of
+transaction semantics. Most often, the inner block will commit, breaking the
+atomicity of the outer block.
 
-    :func:`commit_on_success` has two effects: it changes the transaction
-    state, and defines an atomic transaction block.
+:func:`autocommit` and :func:`commit_manually` have similar limitations.
 
-    Nesting with :func:`autocommit` and :func:`commit_manually` will give the
-    expected results in terms of transaction state, but not in terms of
-    transaction semantics. Most often, the inner block will commit, breaking
-    the atomicity of the outer block.
+API changes
+-----------
 
-Django currently doesn't provide any APIs to create transactions in auto mode.
+Managing transactions
+~~~~~~~~~~~~~~~~~~~~~
 
-.. _transactions-changes-from-1.5:
+Starting with Django 1.6, :func:`atomic` is the only supported API for
+defining a transaction. Unlike the deprecated APIs, it's nestable and always
+guarantees atomicity.
 
-Changes from Django 1.5 and earlier
-===================================
+In most cases, it will be a drop-in replacement for :func:`commit_on_success`.
+
+During the deprecation period, it's possible to use :func:`atomic` within
+:func:`autocommit`, :func:`commit_on_success` or :func:`commit_manually`.
+However, the reverse is forbidden, because nesting the old decorators /
+context managers breaks atomicity.
+
+If you enter :func:`atomic` while you're in managed mode, it will trigger a
+commit to start from a clean slate.
+
+Managing autocommit
+~~~~~~~~~~~~~~~~~~~
+
+Django 1.6 introduces an explicit :ref:`API for mananging autocommit
+<managing-autocommit>`.
+
+To disable autocommit temporarily, instead of::
+
+    with transaction.commit_manually():
+        # do stuff
+
+you should now use::
+
+    transaction.set_autocommit(autocommit=False)
+    try:
+        # do stuff
+    finally:
+        transaction.set_autocommit(autocommit=True)
+
+To enable autocommit temporarily, instead of::
+
+    with transaction.autocommit():
+        # do stuff
+
+you should now use::
+
+    transaction.set_autocommit(autocommit=True)
+    try:
+        # do stuff
+    finally:
+        transaction.set_autocommit(autocommit=False)
+
+Backwards incompatibilities
+---------------------------
 
 Since version 1.6, Django uses database-level autocommit in auto mode.
-
 Previously, it implemented application-level autocommit by triggering a commit
 after each ORM write.
 
-As a consequence, each database query (for instance, an
-ORM read) started a transaction that lasted until the next ORM write. Such
-"automatic transactions" no longer exist in Django 1.6.
+As a consequence, each database query (for instance, an ORM read) started a
+transaction that lasted until the next ORM write. Such "automatic
+transactions" no longer exist in Django 1.6.
 
 There are four known scenarios where this is backwards-incompatible.
 
@@ -565,7 +525,7 @@ Note that managed mode isn't affected at all. This section assumes auto mode.
 See the :ref:`description of modes <transaction-states>` above.
 
 Sequences of custom SQL queries
--------------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 If you're executing several :ref:`custom SQL queries <executing-custom-sql>`
 in a row, each one now runs in its own transaction, instead of sharing the
@@ -577,20 +537,20 @@ usually followed by a call to ``transaction.commit_unless_managed``, which
 isn't necessary any more and should be removed.
 
 Select for update
------------------
+~~~~~~~~~~~~~~~~~
 
 If you were relying on "automatic transactions" to provide locking between
 :meth:`~django.db.models.query.QuerySet.select_for_update` and a subsequent
 write operation — an extremely fragile design, but nonetheless possible — you
-must wrap the relevant code in :func:`commit_on_success`.
+must wrap the relevant code in :func:`atomic`.
 
 Using a high isolation level
-----------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 If you were using the "repeatable read" isolation level or higher, and if you
 relied on "automatic transactions" to guarantee consistency between successive
-reads, the new behavior is backwards-incompatible. To maintain consistency,
-you must wrap such sequences in :func:`commit_on_success`.
+reads, the new behavior might be backwards-incompatible. To enforce
+consistency, you must wrap such sequences in :func:`atomic`.
 
 MySQL defaults to "repeatable read" and SQLite to "serializable"; they may be
 affected by this problem.
@@ -602,10 +562,9 @@ PostgreSQL and Oracle default to "read committed" and aren't affected, unless
 you changed the isolation level.
 
 Using unsupported database features
------------------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 With triggers, views, or functions, it's possible to make ORM reads result in
 database modifications. Django 1.5 and earlier doesn't deal with this case and
 it's theoretically possible to observe a different behavior after upgrading to
-Django 1.6 or later. In doubt, use :func:`commit_on_success` to enforce
-integrity.
+Django 1.6 or later. In doubt, use :func:`atomic` to enforce integrity.
diff --git a/tests/backends/tests.py b/tests/backends/tests.py
index 5c8a8955eb..51acbcb07f 100644
--- a/tests/backends/tests.py
+++ b/tests/backends/tests.py
@@ -522,7 +522,8 @@ class FkConstraintsTests(TransactionTestCase):
         """
         When constraint checks are disabled, should be able to write bad data without IntegrityErrors.
         """
-        with transaction.commit_manually():
+        transaction.set_autocommit(autocommit=False)
+        try:
             # Create an Article.
             models.Article.objects.create(headline="Test article", pub_date=datetime.datetime(2010, 9, 4), reporter=self.r)
             # Retrive it from the DB
@@ -536,12 +537,15 @@ class FkConstraintsTests(TransactionTestCase):
                 self.fail("IntegrityError should not have occurred.")
             finally:
                 transaction.rollback()
+        finally:
+            transaction.set_autocommit(autocommit=True)
 
     def test_disable_constraint_checks_context_manager(self):
         """
         When constraint checks are disabled (using context manager), should be able to write bad data without IntegrityErrors.
         """
-        with transaction.commit_manually():
+        transaction.set_autocommit(autocommit=False)
+        try:
             # Create an Article.
             models.Article.objects.create(headline="Test article", pub_date=datetime.datetime(2010, 9, 4), reporter=self.r)
             # Retrive it from the DB
@@ -554,12 +558,15 @@ class FkConstraintsTests(TransactionTestCase):
                 self.fail("IntegrityError should not have occurred.")
             finally:
                 transaction.rollback()
+        finally:
+            transaction.set_autocommit(autocommit=True)
 
     def test_check_constraints(self):
         """
         Constraint checks should raise an IntegrityError when bad data is in the DB.
         """
-        with transaction.commit_manually():
+        try:
+            transaction.set_autocommit(autocommit=False)
             # Create an Article.
             models.Article.objects.create(headline="Test article", pub_date=datetime.datetime(2010, 9, 4), reporter=self.r)
             # Retrive it from the DB
@@ -572,6 +579,8 @@ class FkConstraintsTests(TransactionTestCase):
                         connection.check_constraints()
             finally:
                 transaction.rollback()
+        finally:
+            transaction.set_autocommit(autocommit=True)
 
 
 class ThreadTests(TestCase):
diff --git a/tests/fixtures_model_package/tests.py b/tests/fixtures_model_package/tests.py
index d147fe68a7..894a6c7fde 100644
--- a/tests/fixtures_model_package/tests.py
+++ b/tests/fixtures_model_package/tests.py
@@ -25,7 +25,8 @@ class SampleTestCase(TestCase):
 
 class TestNoInitialDataLoading(TransactionTestCase):
     def test_syncdb(self):
-        with transaction.commit_manually():
+        transaction.set_autocommit(autocommit=False)
+        try:
             Book.objects.all().delete()
 
             management.call_command(
@@ -35,6 +36,9 @@ class TestNoInitialDataLoading(TransactionTestCase):
             )
             self.assertQuerysetEqual(Book.objects.all(), [])
             transaction.rollback()
+        finally:
+            transaction.set_autocommit(autocommit=True)
+
 
     def test_flush(self):
         # Test presence of fixture (flush called by TransactionTestCase)
@@ -45,7 +49,8 @@ class TestNoInitialDataLoading(TransactionTestCase):
             lambda a: a.name
         )
 
-        with transaction.commit_manually():
+        transaction.set_autocommit(autocommit=False)
+        try:
             management.call_command(
                 'flush',
                 verbosity=0,
@@ -55,6 +60,8 @@ class TestNoInitialDataLoading(TransactionTestCase):
             )
             self.assertQuerysetEqual(Book.objects.all(), [])
             transaction.rollback()
+        finally:
+            transaction.set_autocommit(autocommit=True)
 
 
 class FixtureTestCase(TestCase):
diff --git a/tests/fixtures_regress/tests.py b/tests/fixtures_regress/tests.py
index 61dc4460df..f965dd81ac 100644
--- a/tests/fixtures_regress/tests.py
+++ b/tests/fixtures_regress/tests.py
@@ -684,5 +684,8 @@ class TestTicket11101(TransactionTestCase):
     @skipUnlessDBFeature('supports_transactions')
     def test_ticket_11101(self):
         """Test that fixtures can be rolled back (ticket #11101)."""
-        ticket_11101 = transaction.commit_manually(self.ticket_11101)
-        ticket_11101()
+        transaction.set_autocommit(autocommit=False)
+        try:
+            self.ticket_11101()
+        finally:
+            transaction.set_autocommit(autocommit=True)
diff --git a/tests/middleware/tests.py b/tests/middleware/tests.py
index e704fce342..7e26037967 100644
--- a/tests/middleware/tests.py
+++ b/tests/middleware/tests.py
@@ -24,6 +24,8 @@ from django.utils.encoding import force_str
 from django.utils.six.moves import xrange
 from django.utils.unittest import expectedFailure
 
+from transactions.tests import IgnorePendingDeprecationWarningsMixin
+
 from .models import Band
 
 
@@ -670,11 +672,12 @@ class ETagGZipMiddlewareTest(TestCase):
 
         self.assertNotEqual(gzip_etag, nogzip_etag)
 
-class TransactionMiddlewareTest(TransactionTestCase):
+class TransactionMiddlewareTest(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
     """
     Test the transaction middleware.
     """
     def setUp(self):
+        super(TransactionMiddlewareTest, self).setUp()
         self.request = HttpRequest()
         self.request.META = {
             'SERVER_NAME': 'testserver',
@@ -686,6 +689,7 @@ class TransactionMiddlewareTest(TransactionTestCase):
 
     def tearDown(self):
         transaction.abort()
+        super(TransactionMiddlewareTest, self).tearDown()
 
     def test_request(self):
         TransactionMiddleware().process_request(self.request)
diff --git a/tests/transactions/tests.py b/tests/transactions/tests.py
index 14252dd6dc..d6cfd8ae95 100644
--- a/tests/transactions/tests.py
+++ b/tests/transactions/tests.py
@@ -1,9 +1,10 @@
 from __future__ import absolute_import
 
 import sys
+import warnings
 
 from django.db import connection, transaction, IntegrityError
-from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature
+from django.test import TransactionTestCase, skipUnlessDBFeature
 from django.utils import six
 from django.utils.unittest import skipUnless
 
@@ -158,7 +159,69 @@ class AtomicInsideTransactionTests(AtomicTests):
         self.atomic.__exit__(*sys.exc_info())
 
 
-class TransactionTests(TransactionTestCase):
+class AtomicInsideLegacyTransactionManagementTests(AtomicTests):
+
+    def setUp(self):
+        transaction.enter_transaction_management()
+
+    def tearDown(self):
+        # The tests access the database after exercising 'atomic', making the
+        # connection dirty; a rollback is required to make it clean.
+        transaction.rollback()
+        transaction.leave_transaction_management()
+
+
+@skipUnless(connection.features.uses_savepoints,
+        "'atomic' requires transactions and savepoints.")
+class AtomicErrorsTests(TransactionTestCase):
+
+    def test_atomic_requires_autocommit(self):
+        transaction.set_autocommit(autocommit=False)
+        try:
+            with self.assertRaises(transaction.TransactionManagementError):
+                with transaction.atomic():
+                    pass
+        finally:
+            transaction.set_autocommit(autocommit=True)
+
+    def test_atomic_prevents_disabling_autocommit(self):
+        autocommit = transaction.get_autocommit()
+        with transaction.atomic():
+            with self.assertRaises(transaction.TransactionManagementError):
+                transaction.set_autocommit(autocommit=not autocommit)
+        # Make sure autocommit wasn't changed.
+        self.assertEqual(connection.autocommit, autocommit)
+
+    def test_atomic_prevents_calling_transaction_methods(self):
+        with transaction.atomic():
+            with self.assertRaises(transaction.TransactionManagementError):
+                transaction.commit()
+            with self.assertRaises(transaction.TransactionManagementError):
+                transaction.rollback()
+
+    def test_atomic_prevents_calling_transaction_management_methods(self):
+        with transaction.atomic():
+            with self.assertRaises(transaction.TransactionManagementError):
+                transaction.enter_transaction_management()
+            with self.assertRaises(transaction.TransactionManagementError):
+                transaction.leave_transaction_management()
+
+
+class IgnorePendingDeprecationWarningsMixin(object):
+
+    def setUp(self):
+        super(IgnorePendingDeprecationWarningsMixin, self).setUp()
+        self.catch_warnings = warnings.catch_warnings()
+        self.catch_warnings.__enter__()
+        warnings.filterwarnings("ignore", category=PendingDeprecationWarning)
+
+    def tearDown(self):
+        self.catch_warnings.__exit__(*sys.exc_info())
+        super(IgnorePendingDeprecationWarningsMixin, self).tearDown()
+
+
+class TransactionTests(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
+
     def create_a_reporter_then_fail(self, first, last):
         a = Reporter(first_name=first, last_name=last)
         a.save()
@@ -313,7 +376,7 @@ class TransactionTests(TransactionTestCase):
         )
 
 
-class TransactionRollbackTests(TransactionTestCase):
+class TransactionRollbackTests(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
     def execute_bad_sql(self):
         cursor = connection.cursor()
         cursor.execute("INSERT INTO transactions_reporter (first_name, last_name) VALUES ('Douglas', 'Adams');")
@@ -330,7 +393,7 @@ class TransactionRollbackTests(TransactionTestCase):
         self.assertRaises(IntegrityError, execute_bad_sql)
         transaction.rollback()
 
-class TransactionContextManagerTests(TransactionTestCase):
+class TransactionContextManagerTests(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
     def create_reporter_and_fail(self):
         Reporter.objects.create(first_name="Bob", last_name="Holtzman")
         raise Exception
diff --git a/tests/transactions_regress/tests.py b/tests/transactions_regress/tests.py
index e86db4d0aa..d5ee62da5e 100644
--- a/tests/transactions_regress/tests.py
+++ b/tests/transactions_regress/tests.py
@@ -6,10 +6,12 @@ from django.test import TransactionTestCase, skipUnlessDBFeature
 from django.test.utils import override_settings
 from django.utils.unittest import skipIf, skipUnless, expectedFailure
 
+from transactions.tests import IgnorePendingDeprecationWarningsMixin
+
 from .models import Mod, M2mA, M2mB
 
 
-class TestTransactionClosing(TransactionTestCase):
+class TestTransactionClosing(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
     """
     Tests to make sure that transactions are properly closed
     when they should be, and aren't left pending after operations
@@ -166,7 +168,7 @@ class TestTransactionClosing(TransactionTestCase):
         (connection.settings_dict['NAME'] == ':memory:' or
          not connection.settings_dict['NAME']),
         'Test uses multiple connections, but in-memory sqlite does not support this')
-class TestNewConnection(TransactionTestCase):
+class TestNewConnection(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
     """
     Check that new connections don't have special behaviour.
     """
@@ -211,7 +213,7 @@ class TestNewConnection(TransactionTestCase):
 
 @skipUnless(connection.vendor == 'postgresql',
             "This test only valid for PostgreSQL")
-class TestPostgresAutocommitAndIsolation(TransactionTestCase):
+class TestPostgresAutocommitAndIsolation(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
     """
     Tests to make sure psycopg2's autocommit mode and isolation level
     is restored after entering and leaving transaction management.
@@ -292,7 +294,7 @@ class TestPostgresAutocommitAndIsolation(TransactionTestCase):
         self.assertTrue(connection.autocommit)
 
 
-class TestManyToManyAddTransaction(TransactionTestCase):
+class TestManyToManyAddTransaction(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
     def test_manyrelated_add_commit(self):
         "Test for https://code.djangoproject.com/ticket/16818"
         a = M2mA.objects.create()
@@ -307,7 +309,7 @@ class TestManyToManyAddTransaction(TransactionTestCase):
         self.assertEqual(a.others.count(), 1)
 
 
-class SavepointTest(TransactionTestCase):
+class SavepointTest(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
 
     @skipIf(connection.vendor == 'sqlite',
             "SQLite doesn't support savepoints in managed mode")