From 6633eeb88634aa8c03c8737d2d23c96b96a37e0d Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 19 May 2013 17:55:12 +0200 Subject: [PATCH] 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. --- django/core/handlers/base.py | 9 ++--- django/db/transaction.py | 17 ++++++++++ docs/topics/db/transactions.txt | 60 ++++++++++++++++++--------------- tests/handlers/views.py | 4 +-- 4 files changed, 57 insertions(+), 33 deletions(-) diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index acc74db6f5..555fd98fc6 100644 --- a/django/core/handlers/base.py +++ b/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): diff --git a/django/db/transaction.py b/django/db/transaction.py index 48e7f900dd..f770f2efa7 100644 --- a/django/db/transaction.py +++ b/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 # ############################################ diff --git a/docs/topics/db/transactions.txt b/docs/topics/db/transactions.txt index 78786996cd..3193fe2c9a 100644 --- a/docs/topics/db/transactions.txt +++ b/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 ` 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 + ` 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 `. 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 -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 +With the previous API, it was possible to switch to autocommit or to commit +explicitly anywhere inside a view. Since :setting:`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 ` 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 diff --git a/tests/handlers/views.py b/tests/handlers/views.py index 22d9ea4c7d..9cc86ae6f3 100644 --- a/tests/handlers/views.py +++ b/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