Browse Source

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.
Aymeric Augustin 12 years ago
parent
commit
6633eeb886
4 changed files with 57 additions and 33 deletions
  1. 5 4
      django/core/handlers/base.py
  2. 17 0
      django/db/transaction.py
  3. 33 27
      docs/topics/db/transactions.txt
  4. 2 2
      tests/handlers/views.py

+ 5 - 4
django/core/handlers/base.py

@@ -66,10 +66,11 @@ class BaseHandler(object):
         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)
+        non_atomic_requests = getattr(view, '_non_atomic_requests', set())
+        for db in connections.all():
+            if (db.settings_dict['ATOMIC_REQUESTS']
+                    and db.alias not in non_atomic_requests):
+                view = transaction.atomic(using=db.alias)(view)
         return view
 
     def get_response(self, request):

+ 17 - 0
django/db/transaction.py

@@ -333,6 +333,23 @@ def atomic(using=None, savepoint=True):
         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 #
 ############################################

+ 33 - 27
docs/topics/db/transactions.txt

@@ -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
 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::
 
     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
 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
 
     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
-states". This mechanisme was deprecated in Django 1.6, but it's still
-available until Django 1.8.
+states". This mechanism 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:
 
@@ -554,23 +566,14 @@ 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.
+behavior is the same, there are two differences.
 
-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
+With the previous API, it was possible to switch to autocommit or to commit
+explicitly anywhere inside a view. Since :setting:`ATOMIC_REQUESTS
+<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 an entire view in a transaction. To achieve this, decorate
+the view with :func:`non_atomic_requests` instead of :func:`autocommit`.
 
 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
@@ -624,6 +627,9 @@ you should now use::
     finally:
         transaction.set_autocommit(False)
 
+Unless you're implementing a transaction management framework, you shouldn't
+ever need to do this.
+
 Disabling transaction management
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -653,7 +659,7 @@ 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
 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
 usually followed by a call to ``transaction.commit_unless_managed()``, which

+ 2 - 2
tests/handlers/views.py

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