Changed API to disable ATOMIC_REQUESTS per view.

A decorator is easier to apply to CBVs. Backwards compatibility isn't an
issue here, except for people running on a recent clone of master.

Fixed a few minor problems in the transactions docs while I was there.
This commit is contained in:
Aymeric Augustin 2013-05-19 17:55:12 +02:00
parent bdde7feb26
commit 6633eeb886
4 changed files with 57 additions and 33 deletions

View File

@ -66,10 +66,11 @@ class BaseHandler(object):
self._request_middleware = request_middleware self._request_middleware = request_middleware
def make_view_atomic(self, view): def make_view_atomic(self, view):
if getattr(view, 'transactions_per_request', True): non_atomic_requests = getattr(view, '_non_atomic_requests', set())
for db in connections.all(): for db in connections.all():
if db.settings_dict['ATOMIC_REQUESTS']: if (db.settings_dict['ATOMIC_REQUESTS']
view = transaction.atomic(using=db.alias)(view) and db.alias not in non_atomic_requests):
view = transaction.atomic(using=db.alias)(view)
return view return view
def get_response(self, request): def get_response(self, request):

View File

@ -333,6 +333,23 @@ def atomic(using=None, savepoint=True):
return Atomic(using, savepoint) return Atomic(using, savepoint)
def _non_atomic_requests(view, using):
try:
view._non_atomic_requests.add(using)
except AttributeError:
view._non_atomic_requests = set([using])
return view
def non_atomic_requests(using=None):
if callable(using):
return _non_atomic_requests(using, DEFAULT_DB_ALIAS)
else:
if using is None:
using = DEFAULT_DB_ALIAS
return lambda view: _non_atomic_requests(view, using)
############################################ ############################################
# Deprecated decorators / context managers # # Deprecated decorators / context managers #
############################################ ############################################

View File

@ -45,14 +45,6 @@ You may perfom partial commits and rollbacks in your view code, typically with
the :func:`atomic` context manager. However, at the end of the view, either the :func:`atomic` context manager. However, at the end of the view, either
all the changes will be committed, or none of them. all the changes will be committed, or none of them.
To disable this behavior for a specific view, you must set the
``transactions_per_request`` attribute of the view function itself to
``False``, like this::
def my_view(request):
do_stuff()
my_view.transactions_per_request = False
.. warning:: .. warning::
While the simplicity of this transaction model is appealing, it also makes it While the simplicity of this transaction model is appealing, it also makes it
@ -78,6 +70,26 @@ Note that only the execution of your view is enclosed in the transactions.
Middleware runs outside of the transaction, and so does the rendering of Middleware runs outside of the transaction, and so does the rendering of
template responses. template responses.
When :setting:`ATOMIC_REQUESTS <DATABASE-ATOMIC_REQUESTS>` is enabled, it's
still possible to prevent views from running in a transaction.
.. function:: non_atomic_requests(using=None)
This decorator will negate the effect of :setting:`ATOMIC_REQUESTS
<DATABASE-ATOMIC_REQUESTS>` for a given view::
from django.db import transaction
@transaction.non_atomic_requests
def my_view(request):
do_stuff()
@transaction.non_atomic_requests(using='other')
def my_other_view(request):
do_stuff_on_the_other_database()
It only works if it's applied to the view itself.
.. versionchanged:: 1.6 .. versionchanged:: 1.6
Django used to provide this feature via ``TransactionMiddleware``, which is Django used to provide this feature via ``TransactionMiddleware``, which is
@ -519,8 +531,8 @@ Transaction states
------------------ ------------------
The three functions described above relied on a concept called "transaction The three functions described above relied on a concept called "transaction
states". This mechanisme was deprecated in Django 1.6, but it's still states". This mechanism was deprecated in Django 1.6, but it's still available
available until Django 1.8. until Django 1.8.
At any time, each database connection is in one of these two states: At any time, each database connection is in one of these two states:
@ -554,23 +566,14 @@ Transaction middleware
In Django 1.6, ``TransactionMiddleware`` is deprecated and replaced In Django 1.6, ``TransactionMiddleware`` is deprecated and replaced
:setting:`ATOMIC_REQUESTS <DATABASE-ATOMIC_REQUESTS>`. While the general :setting:`ATOMIC_REQUESTS <DATABASE-ATOMIC_REQUESTS>`. While the general
behavior is the same, there are a few differences. behavior is the same, there are two differences.
With the transaction middleware, it was still possible to switch to autocommit With the previous API, it was possible to switch to autocommit or to commit
or to commit explicitly in a view. Since :func:`atomic` guarantees atomicity, explicitly anywhere inside a view. Since :setting:`ATOMIC_REQUESTS
this isn't allowed any longer. <DATABASE-ATOMIC_REQUESTS>` relies on :func:`atomic` which enforces atomicity,
this isn't allowed any longer. However, at the toplevel, it's still possible
To avoid wrapping a particular view in a transaction, instead of:: to avoid wrapping an entire view in a transaction. To achieve this, decorate
the view with :func:`non_atomic_requests` instead of :func:`autocommit`.
@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 The transaction middleware applied not only to view functions, but also to
middleware modules that came after it. For instance, if you used the session middleware modules that came after it. For instance, if you used the session
@ -624,6 +627,9 @@ you should now use::
finally: finally:
transaction.set_autocommit(False) transaction.set_autocommit(False)
Unless you're implementing a transaction management framework, you shouldn't
ever need to do this.
Disabling transaction management Disabling transaction management
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -653,7 +659,7 @@ Sequences of custom SQL queries
If you're executing several :ref:`custom SQL queries <executing-custom-sql>` 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 in a row, each one now runs in its own transaction, instead of sharing the
same "automatic transaction". If you need to enforce atomicity, you must wrap same "automatic transaction". If you need to enforce atomicity, you must wrap
the sequence of queries in :func:`commit_on_success`. the sequence of queries in :func:`atomic`.
To check for this problem, look for calls to ``cursor.execute()``. They're To check for this problem, look for calls to ``cursor.execute()``. They're
usually followed by a call to ``transaction.commit_unless_managed()``, which usually followed by a call to ``transaction.commit_unless_managed()``, which

View File

@ -1,6 +1,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import connection from django.db import connection, transaction
from django.http import HttpResponse, StreamingHttpResponse from django.http import HttpResponse, StreamingHttpResponse
def regular(request): def regular(request):
@ -12,6 +12,6 @@ def streaming(request):
def in_transaction(request): def in_transaction(request):
return HttpResponse(str(connection.in_atomic_block)) return HttpResponse(str(connection.in_atomic_block))
@transaction.non_atomic_requests
def not_in_transaction(request): def not_in_transaction(request):
return HttpResponse(str(connection.in_atomic_block)) return HttpResponse(str(connection.in_atomic_block))
not_in_transaction.transactions_per_request = False