Skip to content

Commit

Permalink
Deprecated TransactionMiddleware and TRANSACTIONS_MANAGED.
Browse files Browse the repository at this point in the history
Replaced them with per-database options, for proper multi-db support.

Also toned down the recommendation to tie transactions to HTTP requests.
Thanks Jeremy for sharing his experience.
  • Loading branch information
aaugustin committed Mar 11, 2013
1 parent f7245b8 commit ac37ed2
Show file tree
Hide file tree
Showing 12 changed files with 223 additions and 57 deletions.
12 changes: 10 additions & 2 deletions django/core/handlers/base.py
Expand Up @@ -6,10 +6,10 @@


from django import http from django import http
from django.conf import settings from django.conf import settings
from django.core import exceptions
from django.core import urlresolvers from django.core import urlresolvers
from django.core import signals from django.core import signals
from django.core.exceptions import MiddlewareNotUsed, PermissionDenied from django.core.exceptions import MiddlewareNotUsed, PermissionDenied
from django.db import connections, transaction
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.module_loading import import_by_path from django.utils.module_loading import import_by_path
from django.utils import six from django.utils import six
Expand Down Expand Up @@ -65,6 +65,13 @@ def load_middleware(self):
# as a flag for initialization being complete. # as a flag for initialization being complete.
self._request_middleware = request_middleware self._request_middleware = request_middleware


def make_view_atomic(self, view):
if getattr(view, 'transactions_per_request', True):
for db in connections.all():
if db.settings_dict['ATOMIC_REQUESTS']:
view = transaction.atomic(using=db.alias)(view)
return view

def get_response(self, request): def get_response(self, request):
"Returns an HttpResponse object for the given HttpRequest" "Returns an HttpResponse object for the given HttpRequest"
try: try:
Expand Down Expand Up @@ -101,8 +108,9 @@ def get_response(self, request):
break break


if response is None: if response is None:
wrapped_callback = self.make_view_atomic(callback)
try: try:
response = callback(request, *callback_args, **callback_kwargs) response = wrapped_callback(request, *callback_args, **callback_kwargs)
except Exception as e: except Exception as e:
# If the view raised an exception, run it through exception # If the view raised an exception, run it through exception
# middleware, and if the exception middleware returns a # middleware, and if the exception middleware returns a
Expand Down
4 changes: 2 additions & 2 deletions django/db/backends/__init__.py
Expand Up @@ -104,7 +104,7 @@ def connect(self):
conn_params = self.get_connection_params() conn_params = self.get_connection_params()
self.connection = self.get_new_connection(conn_params) self.connection = self.get_new_connection(conn_params)
self.init_connection_state() self.init_connection_state()
if not settings.TRANSACTIONS_MANAGED: if self.settings_dict['AUTOCOMMIT']:
self.set_autocommit() self.set_autocommit()
connection_created.send(sender=self.__class__, connection=self) connection_created.send(sender=self.__class__, connection=self)


Expand Down Expand Up @@ -299,7 +299,7 @@ def leave_transaction_management(self):
if self.transaction_state: if self.transaction_state:
managed = self.transaction_state[-1] managed = self.transaction_state[-1]
else: else:
managed = settings.TRANSACTIONS_MANAGED managed = not self.settings_dict['AUTOCOMMIT']


if self._dirty: if self._dirty:
self.rollback() self.rollback()
Expand Down
8 changes: 8 additions & 0 deletions django/db/utils.py
Expand Up @@ -2,6 +2,7 @@
import os import os
import pkgutil import pkgutil
from threading import local from threading import local
import warnings


from django.conf import settings from django.conf import settings
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
Expand Down Expand Up @@ -158,6 +159,13 @@ def ensure_defaults(self, alias):
except KeyError: except KeyError:
raise ConnectionDoesNotExist("The connection %s doesn't exist" % alias) raise ConnectionDoesNotExist("The connection %s doesn't exist" % alias)


conn.setdefault('ATOMIC_REQUESTS', False)
if settings.TRANSACTIONS_MANAGED:
warnings.warn(
"TRANSACTIONS_MANAGED is deprecated. Use AUTOCOMMIT instead.",
PendingDeprecationWarning, stacklevel=2)
conn.setdefault('AUTOCOMMIT', False)
conn.setdefault('AUTOCOMMIT', True)
conn.setdefault('ENGINE', 'django.db.backends.dummy') conn.setdefault('ENGINE', 'django.db.backends.dummy')
if conn['ENGINE'] == 'django.db.backends.' or not conn['ENGINE']: if conn['ENGINE'] == 'django.db.backends.' or not conn['ENGINE']:
conn['ENGINE'] = 'django.db.backends.dummy' conn['ENGINE'] = 'django.db.backends.dummy'
Expand Down
13 changes: 12 additions & 1 deletion django/middleware/transaction.py
@@ -1,4 +1,7 @@
from django.db import transaction import warnings

from django.core.exceptions import MiddlewareNotUsed
from django.db import connection, transaction


class TransactionMiddleware(object): class TransactionMiddleware(object):
""" """
Expand All @@ -7,6 +10,14 @@ class TransactionMiddleware(object):
commit, the commit is done when a successful response is created. If an commit, the commit is done when a successful response is created. If an
exception happens, the database is rolled back. exception happens, the database is rolled back.
""" """

def __init__(self):
warnings.warn(
"TransactionMiddleware is deprecated in favor of ATOMIC_REQUESTS.",
PendingDeprecationWarning, stacklevel=2)
if connection.settings_dict['ATOMIC_REQUESTS']:
raise MiddlewareNotUsed

def process_request(self, request): def process_request(self, request):
"""Enters transaction management""" """Enters transaction management"""
transaction.enter_transaction_management() transaction.enter_transaction_management()
Expand Down
11 changes: 8 additions & 3 deletions docs/internals/deprecation.txt
Expand Up @@ -329,9 +329,14 @@ these changes.
1.8 1.8
--- ---


* The decorators and context managers ``django.db.transaction.autocommit``, * The following transaction management APIs will be removed:
``commit_on_success`` and ``commit_manually`` will be removed. See
:ref:`transactions-upgrading-from-1.5`. - ``TransactionMiddleware``,
- the decorators and context managers ``autocommit``, ``commit_on_success``,
and ``commit_manually``,
- the ``TRANSACTIONS_MANAGED`` setting.

Upgrade paths are described in :ref:`transactions-upgrading-from-1.5`.


* The :ttag:`cycle` and :ttag:`firstof` template tags will auto-escape their * 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 arguments. In 1.6 and 1.7, this behavior is provided by the version of these
Expand Down
4 changes: 4 additions & 0 deletions docs/ref/middleware.txt
Expand Up @@ -205,6 +205,10 @@ Transaction middleware


.. class:: TransactionMiddleware .. class:: TransactionMiddleware


.. versionchanged:: 1.6
``TransactionMiddleware`` is deprecated. The documentation of transactions
contains :ref:`upgrade instructions <transactions-upgrading-from-1.5>`.

Binds commit and rollback of the default database to the request/response Binds commit and rollback of the default database to the request/response
phase. If a view function runs successfully, a commit is done. If it fails with phase. If a view function runs successfully, a commit is done. If it fails with
an exception, a rollback is done. an exception, a rollback is done.
Expand Down
30 changes: 30 additions & 0 deletions docs/ref/settings.txt
Expand Up @@ -408,6 +408,30 @@ SQLite. This can be configured using the following::
For other database backends, or more complex SQLite configurations, other options For other database backends, or more complex SQLite configurations, other options
will be required. The following inner options are available. will be required. The following inner options are available.


.. setting:: DATABASE-ATOMIC_REQUESTS

ATOMIC_REQUESTS
~~~~~~~~~~~~~~~

.. versionadded:: 1.6

Default: ``False``

Set this to ``True`` to wrap each HTTP request in a transaction on this
database. See :ref:`tying-transactions-to-http-requests`.

.. setting:: DATABASE-AUTOCOMMIT

AUTOCOMMIT
~~~~~~~~~~

.. versionadded:: 1.6

Default: ``True``

Set this to ``False`` if you want to :ref:`disable Django's transaction
management <deactivate-transaction-management>` and implement your own.

.. setting:: DATABASE-ENGINE .. setting:: DATABASE-ENGINE


ENGINE ENGINE
Expand Down Expand Up @@ -1807,6 +1831,12 @@ to ensure your processes are running in the correct environment.
TRANSACTIONS_MANAGED TRANSACTIONS_MANAGED
-------------------- --------------------


.. deprecated:: 1.6

This setting was deprecated because its name is very misleading. Use the
:setting:`AUTOCOMMIT <DATABASE-AUTOCOMMIT>` key in :setting:`DATABASES`
entries instead.

Default: ``False`` Default: ``False``


Set this to ``True`` if you want to :ref:`disable Django's transaction Set this to ``True`` if you want to :ref:`disable Django's transaction
Expand Down
8 changes: 5 additions & 3 deletions docs/releases/1.6.txt
Expand Up @@ -262,9 +262,11 @@ Transaction management APIs
Transaction management was completely overhauled in Django 1.6, and the Transaction management was completely overhauled in Django 1.6, and the
current APIs are deprecated: current APIs are deprecated:


- :func:`django.db.transaction.autocommit` - ``django.middleware.transaction.TransactionMiddleware``
- :func:`django.db.transaction.commit_on_success` - ``django.db.transaction.autocommit``
- :func:`django.db.transaction.commit_manually` - ``django.db.transaction.commit_on_success``
- ``django.db.transaction.commit_manually``
- the ``TRANSACTIONS_MANAGED`` setting


The reasons for this change and the upgrade path are described in the The reasons for this change and the upgrade path are described in the
:ref:`transactions documentation <transactions-upgrading-from-1.5>`. :ref:`transactions documentation <transactions-upgrading-from-1.5>`.
Expand Down
134 changes: 94 additions & 40 deletions docs/topics/db/transactions.txt
Expand Up @@ -26,45 +26,61 @@ immediately committed to the database. :ref:`See below for details
Previous version of Django featured :ref:`a more complicated default Previous version of Django featured :ref:`a more complicated default
behavior <transactions-upgrading-from-1.5>`. behavior <transactions-upgrading-from-1.5>`.


.. _tying-transactions-to-http-requests:

Tying transactions to HTTP requests Tying transactions to HTTP requests
----------------------------------- -----------------------------------


The recommended way to handle transactions in Web requests is to tie them to A common way to handle transactions on the web is to wrap each request in a
the request and response phases via Django's ``TransactionMiddleware``. transaction. Set :setting:`ATOMIC_REQUESTS <DATABASE-ATOMIC_REQUESTS>` to
``True`` in the configuration of each database for which you want to enable
this behavior.


It works like this. When a request starts, Django starts a transaction. If the It works like this. When a request starts, Django starts a transaction. If the
response is produced without problems, Django commits any pending transactions. response is produced without problems, Django commits the transaction. If the
If the view function produces an exception, Django rolls back any pending view function produces an exception, Django rolls back the transaction.
transactions. Middleware always runs outside of this transaction.


To activate this feature, just add the ``TransactionMiddleware`` middleware to You may perfom partial commits and rollbacks in your view code, typically with
your :setting:`MIDDLEWARE_CLASSES` setting:: the :func:`atomic` context manager. However, at the end of the view, either

all the changes will be committed, or none of them.
MIDDLEWARE_CLASSES = (
'django.middleware.cache.UpdateCacheMiddleware', To disable this behavior for a specific view, you must set the
'django.contrib.sessions.middleware.SessionMiddleware', ``transactions_per_request`` attribute of the view function itself to
'django.middleware.common.CommonMiddleware', ``False``, like this::
'django.middleware.transaction.TransactionMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware', def my_view(request):
) do_stuff()

my_view.transactions_per_request = False
The order is quite important. The transaction middleware applies not only to
view functions, but also for all middleware modules that come after it. So if .. warning::
you use the session middleware after the transaction middleware, session
creation will be part of the transaction. While the simplicity of this transaction model is appealing, it also makes it

inefficient when traffic increases. Opening a transaction for every view has
The various cache middlewares are an exception: ``CacheMiddleware``, some overhead. The impact on performance depends on the query patterns of your
:class:`~django.middleware.cache.UpdateCacheMiddleware`, and application and on how well your database handles locking.
:class:`~django.middleware.cache.FetchFromCacheMiddleware` are never affected.
Even when using database caching, Django's cache backend uses its own database .. admonition:: Per-request transactions and streaming responses
connection internally.

When a view returns a :class:`~django.http.StreamingHttpResponse`, reading
.. note:: the contents of the response will often execute code to generate the

content. Since the view has already returned, such code runs outside of
The ``TransactionMiddleware`` only affects the database aliased the transaction.
as "default" within your :setting:`DATABASES` setting. If you are using
multiple databases and want transaction control over databases other than Generally speaking, it isn't advisable to write to the database while
"default", you will need to write your own transaction middleware. generating a streaming response, since there's no sensible way to handle
errors after starting to send the response.

In practice, this feature simply wraps every view function in the :func:`atomic`
decorator described below.

Note that only the execution of your view in enclosed in the transactions.
Middleware run outside of the transaction, and so does the rendering of
template responses.

.. versionchanged:: 1.6
Django used to provide this feature via ``TransactionMiddleware``, which is
now deprecated.


Controlling transactions explicitly Controlling transactions explicitly
----------------------------------- -----------------------------------
Expand Down Expand Up @@ -283,18 +299,20 @@ if autocommit is off. Django will also refuse to turn autocommit off when an
Deactivating transaction management Deactivating transaction management
----------------------------------- -----------------------------------


Control freaks can totally disable all transaction management by setting You can totally disable Django's transaction management for a given database
:setting:`TRANSACTIONS_MANAGED` to ``True`` in the Django settings file. If by setting :setting:`AUTOCOMMIT <DATABASE-AUTOCOMMIT>` to ``False`` in its
you do this, Django won't enable autocommit. You'll get the regular behavior configuration. If you do this, Django won't enable autocommit, and won't
of the underlying database library. perform any commits. You'll get the regular behavior of the underlying
database library.


This requires you to commit explicitly every transaction, even those started 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 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 where you want to run your own transaction-controlling middleware or do
something really strange. something really strange.


In almost all situations, you'll be better off using the default behavior, or .. versionchanged:: 1.6
the transaction middleware, and only modify selected functions as needed. This used to be controlled by the ``TRANSACTIONS_MANAGED`` setting.



Database-specific notes Database-specific notes
======================= =======================
Expand Down Expand Up @@ -459,6 +477,35 @@ atomicity of the outer block.
API changes API changes
----------- -----------


Transaction middleware
~~~~~~~~~~~~~~~~~~~~~~

In Django 1.6, ``TransactionMiddleware`` is deprecated and replaced
:setting:`ATOMIC_REQUESTS <DATABASE-ATOMIC_REQUESTS>`. While the general
behavior is the same, there are a few differences.

With the transaction middleware, it was still possible to switch to autocommit
or to commit explicitly in a view. Since :func:`atomic` guarantees atomicity,
this isn't allowed any longer.

To avoid wrapping a particular view in a transaction, instead of::

@transaction.autocommit
def my_view(request):
do_stuff()

you must now use this pattern::

def my_view(request):
do_stuff()
my_view.transactions_per_request = False

The transaction middleware applied not only to view functions, but also to
middleware modules that come after it. For instance, if you used the session
middleware after the transaction middleware, session creation was part of the
transaction. :setting:`ATOMIC_REQUESTS <DATABASE-ATOMIC_REQUESTS>` only
applies to the view itself.

Managing transactions Managing transactions
~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~


Expand Down Expand Up @@ -508,6 +555,13 @@ you should now use::
finally: finally:
transaction.set_autocommit(autocommit=False) transaction.set_autocommit(autocommit=False)


Disabling transaction management
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Instead of setting ``TRANSACTIONS_MANAGED = True``, set the ``AUTOCOMMIT`` key
to ``False`` in the configuration of each database, as explained in :ref
:`deactivate-transaction-management`.

Backwards incompatibilities Backwards incompatibilities
--------------------------- ---------------------------


Expand Down

0 comments on commit ac37ed2

Please sign in to comment.