Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Deprecated TransactionMiddleware and TRANSACTIONS_MANAGED.

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...
commit ac37ed21b3d66dde1748f6edf3279656b0267b70 1 parent f7245b8
Aymeric Augustin authored March 06, 2013
12  django/core/handlers/base.py
@@ -6,10 +6,10 @@
6 6
 
7 7
 from django import http
8 8
 from django.conf import settings
9  
-from django.core import exceptions
10 9
 from django.core import urlresolvers
11 10
 from django.core import signals
12 11
 from django.core.exceptions import MiddlewareNotUsed, PermissionDenied
  12
+from django.db import connections, transaction
13 13
 from django.utils.encoding import force_text
14 14
 from django.utils.module_loading import import_by_path
15 15
 from django.utils import six
@@ -65,6 +65,13 @@ def load_middleware(self):
65 65
         # as a flag for initialization being complete.
66 66
         self._request_middleware = request_middleware
67 67
 
  68
+    def make_view_atomic(self, view):
  69
+        if getattr(view, 'transactions_per_request', True):
  70
+            for db in connections.all():
  71
+                if db.settings_dict['ATOMIC_REQUESTS']:
  72
+                    view = transaction.atomic(using=db.alias)(view)
  73
+        return view
  74
+
68 75
     def get_response(self, request):
69 76
         "Returns an HttpResponse object for the given HttpRequest"
70 77
         try:
@@ -101,8 +108,9 @@ def get_response(self, request):
101 108
                             break
102 109
 
103 110
                 if response is None:
  111
+                    wrapped_callback = self.make_view_atomic(callback)
104 112
                     try:
105  
-                        response = callback(request, *callback_args, **callback_kwargs)
  113
+                        response = wrapped_callback(request, *callback_args, **callback_kwargs)
106 114
                     except Exception as e:
107 115
                         # If the view raised an exception, run it through exception
108 116
                         # middleware, and if the exception middleware returns a
4  django/db/backends/__init__.py
@@ -104,7 +104,7 @@ def connect(self):
104 104
         conn_params = self.get_connection_params()
105 105
         self.connection = self.get_new_connection(conn_params)
106 106
         self.init_connection_state()
107  
-        if not settings.TRANSACTIONS_MANAGED:
  107
+        if self.settings_dict['AUTOCOMMIT']:
108 108
             self.set_autocommit()
109 109
         connection_created.send(sender=self.__class__, connection=self)
110 110
 
@@ -299,7 +299,7 @@ def leave_transaction_management(self):
299 299
         if self.transaction_state:
300 300
             managed = self.transaction_state[-1]
301 301
         else:
302  
-            managed = settings.TRANSACTIONS_MANAGED
  302
+            managed = not self.settings_dict['AUTOCOMMIT']
303 303
 
304 304
         if self._dirty:
305 305
             self.rollback()
8  django/db/utils.py
@@ -2,6 +2,7 @@
2 2
 import os
3 3
 import pkgutil
4 4
 from threading import local
  5
+import warnings
5 6
 
6 7
 from django.conf import settings
7 8
 from django.core.exceptions import ImproperlyConfigured
@@ -158,6 +159,13 @@ def ensure_defaults(self, alias):
158 159
         except KeyError:
159 160
             raise ConnectionDoesNotExist("The connection %s doesn't exist" % alias)
160 161
 
  162
+        conn.setdefault('ATOMIC_REQUESTS', False)
  163
+        if settings.TRANSACTIONS_MANAGED:
  164
+            warnings.warn(
  165
+                "TRANSACTIONS_MANAGED is deprecated. Use AUTOCOMMIT instead.",
  166
+                PendingDeprecationWarning, stacklevel=2)
  167
+            conn.setdefault('AUTOCOMMIT', False)
  168
+        conn.setdefault('AUTOCOMMIT', True)
161 169
         conn.setdefault('ENGINE', 'django.db.backends.dummy')
162 170
         if conn['ENGINE'] == 'django.db.backends.' or not conn['ENGINE']:
163 171
             conn['ENGINE'] = 'django.db.backends.dummy'
13  django/middleware/transaction.py
... ...
@@ -1,4 +1,7 @@
1  
-from django.db import transaction
  1
+import warnings
  2
+
  3
+from django.core.exceptions import MiddlewareNotUsed
  4
+from django.db import connection, transaction
2 5
 
3 6
 class TransactionMiddleware(object):
4 7
     """
@@ -7,6 +10,14 @@ class TransactionMiddleware(object):
7 10
     commit, the commit is done when a successful response is created. If an
8 11
     exception happens, the database is rolled back.
9 12
     """
  13
+
  14
+    def __init__(self):
  15
+        warnings.warn(
  16
+            "TransactionMiddleware is deprecated in favor of ATOMIC_REQUESTS.",
  17
+            PendingDeprecationWarning, stacklevel=2)
  18
+        if connection.settings_dict['ATOMIC_REQUESTS']:
  19
+            raise MiddlewareNotUsed
  20
+
10 21
     def process_request(self, request):
11 22
         """Enters transaction management"""
12 23
         transaction.enter_transaction_management()
11  docs/internals/deprecation.txt
@@ -329,9 +329,14 @@ these changes.
329 329
 1.8
330 330
 ---
331 331
 
332  
-* The decorators and context managers ``django.db.transaction.autocommit``,
333  
-  ``commit_on_success`` and ``commit_manually`` will be removed. See
334  
-  :ref:`transactions-upgrading-from-1.5`.
  332
+* The following transaction management APIs will be removed:
  333
+
  334
+  - ``TransactionMiddleware``,
  335
+  - the decorators and context managers ``autocommit``, ``commit_on_success``,
  336
+    and ``commit_manually``,
  337
+  - the ``TRANSACTIONS_MANAGED`` setting.
  338
+
  339
+  Upgrade paths are described in :ref:`transactions-upgrading-from-1.5`.
335 340
 
336 341
 * The :ttag:`cycle` and :ttag:`firstof` template tags will auto-escape their
337 342
   arguments. In 1.6 and 1.7, this behavior is provided by the version of these
4  docs/ref/middleware.txt
@@ -205,6 +205,10 @@ Transaction middleware
205 205
 
206 206
 .. class:: TransactionMiddleware
207 207
 
  208
+.. versionchanged:: 1.6
  209
+    ``TransactionMiddleware`` is deprecated. The documentation of transactions
  210
+    contains :ref:`upgrade instructions <transactions-upgrading-from-1.5>`.
  211
+
208 212
 Binds commit and rollback of the default database to the request/response
209 213
 phase. If a view function runs successfully, a commit is done. If it fails with
210 214
 an exception, a rollback is done.
30  docs/ref/settings.txt
@@ -408,6 +408,30 @@ SQLite. This can be configured using the following::
408 408
 For other database backends, or more complex SQLite configurations, other options
409 409
 will be required. The following inner options are available.
410 410
 
  411
+.. setting:: DATABASE-ATOMIC_REQUESTS
  412
+
  413
+ATOMIC_REQUESTS
  414
+~~~~~~~~~~~~~~~
  415
+
  416
+.. versionadded:: 1.6
  417
+
  418
+Default: ``False``
  419
+
  420
+Set this to ``True`` to wrap each HTTP request in a transaction on this
  421
+database. See :ref:`tying-transactions-to-http-requests`.
  422
+
  423
+.. setting:: DATABASE-AUTOCOMMIT
  424
+
  425
+AUTOCOMMIT
  426
+~~~~~~~~~~
  427
+
  428
+.. versionadded:: 1.6
  429
+
  430
+Default: ``True``
  431
+
  432
+Set this to ``False`` if you want to :ref:`disable Django's transaction
  433
+management <deactivate-transaction-management>` and implement your own.
  434
+
411 435
 .. setting:: DATABASE-ENGINE
412 436
 
413 437
 ENGINE
@@ -1807,6 +1831,12 @@ to ensure your processes are running in the correct environment.
1807 1831
 TRANSACTIONS_MANAGED
1808 1832
 --------------------
1809 1833
 
  1834
+.. deprecated:: 1.6
  1835
+
  1836
+    This setting was deprecated because its name is very misleading. Use the
  1837
+    :setting:`AUTOCOMMIT <DATABASE-AUTOCOMMIT>` key in :setting:`DATABASES`
  1838
+    entries instead.
  1839
+
1810 1840
 Default: ``False``
1811 1841
 
1812 1842
 Set this to ``True`` if you want to :ref:`disable Django's transaction
8  docs/releases/1.6.txt
@@ -262,9 +262,11 @@ Transaction management APIs
262 262
 Transaction management was completely overhauled in Django 1.6, and the
263 263
 current APIs are deprecated:
264 264
 
265  
-- :func:`django.db.transaction.autocommit`
266  
-- :func:`django.db.transaction.commit_on_success`
267  
-- :func:`django.db.transaction.commit_manually`
  265
+- ``django.middleware.transaction.TransactionMiddleware``
  266
+- ``django.db.transaction.autocommit``
  267
+- ``django.db.transaction.commit_on_success``
  268
+- ``django.db.transaction.commit_manually``
  269
+- the ``TRANSACTIONS_MANAGED`` setting
268 270
 
269 271
 The reasons for this change and the upgrade path are described in the
270 272
 :ref:`transactions documentation <transactions-upgrading-from-1.5>`.
134  docs/topics/db/transactions.txt
@@ -26,45 +26,61 @@ immediately committed to the database. :ref:`See below for details
26 26
     Previous version of Django featured :ref:`a more complicated default
27 27
     behavior <transactions-upgrading-from-1.5>`.
28 28
 
  29
+.. _tying-transactions-to-http-requests:
  30
+
29 31
 Tying transactions to HTTP requests
30 32
 -----------------------------------
31 33
 
32  
-The recommended way to handle transactions in Web requests is to tie them to
33  
-the request and response phases via Django's ``TransactionMiddleware``.
  34
+A common way to handle transactions on the web is to wrap each request in a
  35
+transaction. Set :setting:`ATOMIC_REQUESTS <DATABASE-ATOMIC_REQUESTS>` to
  36
+``True`` in the configuration of each database for which you want to enable
  37
+this behavior.
34 38
 
35 39
 It works like this. When a request starts, Django starts a transaction. If the
36  
-response is produced without problems, Django commits any pending transactions.
37  
-If the view function produces an exception, Django rolls back any pending
38  
-transactions.
39  
-
40  
-To activate this feature, just add the ``TransactionMiddleware`` middleware to
41  
-your :setting:`MIDDLEWARE_CLASSES` setting::
42  
-
43  
-    MIDDLEWARE_CLASSES = (
44  
-        'django.middleware.cache.UpdateCacheMiddleware',
45  
-        'django.contrib.sessions.middleware.SessionMiddleware',
46  
-        'django.middleware.common.CommonMiddleware',
47  
-        'django.middleware.transaction.TransactionMiddleware',
48  
-        'django.middleware.cache.FetchFromCacheMiddleware',
49  
-    )
50  
-
51  
-The order is quite important. The transaction middleware applies not only to
52  
-view functions, but also for all middleware modules that come after it. So if
53  
-you use the session middleware after the transaction middleware, session
54  
-creation will be part of the transaction.
55  
-
56  
-The various cache middlewares are an exception: ``CacheMiddleware``,
57  
-:class:`~django.middleware.cache.UpdateCacheMiddleware`, and
58  
-:class:`~django.middleware.cache.FetchFromCacheMiddleware` are never affected.
59  
-Even when using database caching, Django's cache backend uses its own database
60  
-connection internally.
61  
-
62  
-.. note::
63  
-
64  
-    The ``TransactionMiddleware`` only affects the database aliased
65  
-    as "default" within your :setting:`DATABASES` setting.  If you are using
66  
-    multiple databases and want transaction control over databases other than
67  
-    "default", you will need to write your own transaction middleware.
  40
+response is produced without problems, Django commits the transaction. If the
  41
+view function produces an exception, Django rolls back the transaction.
  42
+Middleware always runs outside of this transaction.
  43
+
  44
+You may perfom partial commits and rollbacks in your view code, typically with
  45
+the :func:`atomic` context manager. However, at the end of the view, either
  46
+all the changes will be committed, or none of them.
  47
+
  48
+To disable this behavior for a specific view, you must set the
  49
+``transactions_per_request`` attribute of the view function itself to
  50
+``False``, like this::
  51
+
  52
+    def my_view(request):
  53
+        do_stuff()
  54
+    my_view.transactions_per_request = False
  55
+
  56
+.. warning::
  57
+
  58
+    While the simplicity of this transaction model is appealing, it also makes it
  59
+    inefficient when traffic increases. Opening a transaction for every view has
  60
+    some overhead. The impact on performance depends on the query patterns of your
  61
+    application and on how well your database handles locking.
  62
+
  63
+.. admonition:: Per-request transactions and streaming responses
  64
+
  65
+    When a view returns a :class:`~django.http.StreamingHttpResponse`, reading
  66
+    the contents of the response will often execute code to generate the
  67
+    content. Since the view has already returned, such code runs outside of
  68
+    the transaction.
  69
+
  70
+    Generally speaking, it isn't advisable to write to the database while
  71
+    generating a streaming response, since there's no sensible way to handle
  72
+    errors after starting to send the response.
  73
+
  74
+In practice, this feature simply wraps every view function in the :func:`atomic`
  75
+decorator described below.
  76
+
  77
+Note that only the execution of your view in enclosed in the transactions.
  78
+Middleware run outside of the transaction, and so does the rendering of
  79
+template responses.
  80
+
  81
+.. versionchanged:: 1.6
  82
+    Django used to provide this feature via ``TransactionMiddleware``, which is
  83
+    now deprecated.
68 84
 
69 85
 Controlling transactions explicitly
70 86
 -----------------------------------
@@ -283,18 +299,20 @@ if autocommit is off. Django will also refuse to turn autocommit off when an
283 299
 Deactivating transaction management
284 300
 -----------------------------------
285 301
 
286  
-Control freaks can totally disable all transaction management by setting
287  
-:setting:`TRANSACTIONS_MANAGED` to ``True`` in the Django settings file. If
288  
-you do this, Django won't enable autocommit. You'll get the regular behavior
289  
-of the underlying database library.
  302
+You can totally disable Django's transaction management for a given database
  303
+by setting :setting:`AUTOCOMMIT <DATABASE-AUTOCOMMIT>` to ``False`` in its
  304
+configuration. If you do this, Django won't enable autocommit, and won't
  305
+perform any commits. You'll get the regular behavior of the underlying
  306
+database library.
290 307
 
291 308
 This requires you to commit explicitly every transaction, even those started
292 309
 by Django or by third-party libraries. Thus, this is best used in situations
293 310
 where you want to run your own transaction-controlling middleware or do
294 311
 something really strange.
295 312
 
296  
-In almost all situations, you'll be better off using the default behavior, or
297  
-the transaction middleware, and only modify selected functions as needed.
  313
+.. versionchanged:: 1.6
  314
+    This used to be controlled by the ``TRANSACTIONS_MANAGED`` setting.
  315
+
298 316
 
299 317
 Database-specific notes
300 318
 =======================
@@ -459,6 +477,35 @@ atomicity of the outer block.
459 477
 API changes
460 478
 -----------
461 479
 
  480
+Transaction middleware
  481
+~~~~~~~~~~~~~~~~~~~~~~
  482
+
  483
+In Django 1.6, ``TransactionMiddleware`` is deprecated and replaced
  484
+:setting:`ATOMIC_REQUESTS <DATABASE-ATOMIC_REQUESTS>`. While the general
  485
+behavior is the same, there are a few differences.
  486
+
  487
+With the transaction middleware, it was still possible to switch to autocommit
  488
+or to commit explicitly in a view. Since :func:`atomic` guarantees atomicity,
  489
+this isn't allowed any longer.
  490
+
  491
+To avoid wrapping a particular view in a transaction, instead of::
  492
+
  493
+    @transaction.autocommit
  494
+    def my_view(request):
  495
+        do_stuff()
  496
+
  497
+you must now use this pattern::
  498
+
  499
+    def my_view(request):
  500
+        do_stuff()
  501
+    my_view.transactions_per_request = False
  502
+
  503
+The transaction middleware applied not only to view functions, but also to
  504
+middleware modules that come after it. For instance, if you used the session
  505
+middleware after the transaction middleware, session creation was part of the
  506
+transaction. :setting:`ATOMIC_REQUESTS <DATABASE-ATOMIC_REQUESTS>` only
  507
+applies to the view itself.
  508
+
462 509
 Managing transactions
463 510
 ~~~~~~~~~~~~~~~~~~~~~
464 511
 
@@ -508,6 +555,13 @@ you should now use::
508 555
     finally:
509 556
         transaction.set_autocommit(autocommit=False)
510 557
 
  558
+Disabling transaction management
  559
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  560
+
  561
+Instead of setting ``TRANSACTIONS_MANAGED = True``, set the ``AUTOCOMMIT`` key
  562
+to ``False`` in the configuration of each database, as explained in :ref
  563
+:`deactivate-transaction-management`.
  564
+
511 565
 Backwards incompatibilities
512 566
 ---------------------------
513 567
 
30  tests/handlers/tests.py
... ...
@@ -1,9 +1,8 @@
1 1
 from django.core.handlers.wsgi import WSGIHandler
2 2
 from django.core.signals import request_started, request_finished
3  
-from django.db import close_old_connections
4  
-from django.test import RequestFactory, TestCase
  3
+from django.db import close_old_connections, connection
  4
+from django.test import RequestFactory, TestCase, TransactionTestCase
5 5
 from django.test.utils import override_settings
6  
-from django.utils import six
7 6
 
8 7
 
9 8
 class HandlerTests(TestCase):
@@ -37,6 +36,31 @@ def test_bad_path_info(self):
37 36
         self.assertEqual(response.status_code, 400)
38 37
 
39 38
 
  39
+class TransactionsPerRequestTests(TransactionTestCase):
  40
+    urls = 'handlers.urls'
  41
+
  42
+    def test_no_transaction(self):
  43
+        response = self.client.get('/in_transaction/')
  44
+        self.assertContains(response, 'False')
  45
+
  46
+    def test_auto_transaction(self):
  47
+        old_atomic_requests = connection.settings_dict['ATOMIC_REQUESTS']
  48
+        try:
  49
+            connection.settings_dict['ATOMIC_REQUESTS'] = True
  50
+            response = self.client.get('/in_transaction/')
  51
+        finally:
  52
+            connection.settings_dict['ATOMIC_REQUESTS'] = old_atomic_requests
  53
+        self.assertContains(response, 'True')
  54
+
  55
+    def test_no_auto_transaction(self):
  56
+        old_atomic_requests = connection.settings_dict['ATOMIC_REQUESTS']
  57
+        try:
  58
+            connection.settings_dict['ATOMIC_REQUESTS'] = True
  59
+            response = self.client.get('/not_in_transaction/')
  60
+        finally:
  61
+            connection.settings_dict['ATOMIC_REQUESTS'] = old_atomic_requests
  62
+        self.assertContains(response, 'False')
  63
+
40 64
 class SignalsTests(TestCase):
41 65
     urls = 'handlers.urls'
42 66
 
9  tests/handlers/urls.py
... ...
@@ -1,9 +1,12 @@
1 1
 from __future__ import unicode_literals
2 2
 
3 3
 from django.conf.urls import patterns, url
4  
-from django.http import HttpResponse, StreamingHttpResponse
  4
+
  5
+from . import views
5 6
 
6 7
 urlpatterns = patterns('',
7  
-    url(r'^regular/$', lambda request: HttpResponse(b"regular content")),
8  
-    url(r'^streaming/$', lambda request: StreamingHttpResponse([b"streaming", b" ", b"content"])),
  8
+    url(r'^regular/$', views.regular),
  9
+    url(r'^streaming/$', views.streaming),
  10
+    url(r'^in_transaction/$', views.in_transaction),
  11
+    url(r'^not_in_transaction/$', views.not_in_transaction),
9 12
 )
17  tests/handlers/views.py
... ...
@@ -0,0 +1,17 @@
  1
+from __future__ import unicode_literals
  2
+
  3
+from django.db import connection
  4
+from django.http import HttpResponse, StreamingHttpResponse
  5
+
  6
+def regular(request):
  7
+    return HttpResponse(b"regular content")
  8
+
  9
+def streaming(request):
  10
+    return StreamingHttpResponse([b"streaming", b" ", b"content"])
  11
+
  12
+def in_transaction(request):
  13
+    return HttpResponse(str(connection.in_atomic_block))
  14
+
  15
+def not_in_transaction(request):
  16
+    return HttpResponse(str(connection.in_atomic_block))
  17
+not_in_transaction.transactions_per_request = False

0 notes on commit ac37ed2

Please sign in to comment.
Something went wrong with that request. Please try again.