diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
index 649f807..c637640 100644
|
a
|
b
|
class BaseDatabaseWrapper(object):
|
| 106 | 106 | self._dirty = False |
| 107 | 107 | self._enter_transaction_management(managed) |
| 108 | 108 | |
| 109 | | def leave_transaction_management(self): |
| | 109 | def leave_transaction_management(self, exception=None): |
| 110 | 110 | """ |
| 111 | 111 | Leaves transaction management for a running thread. A dirty flag is carried |
| 112 | 112 | over to the surrounding block, as a commit will commit all changes, even |
| 113 | 113 | those from outside. (Commits are on connection level.) |
| | 114 | |
| | 115 | If this function is called from within a finally block where an |
| | 116 | exception is in the process of working its way up the stack, pass |
| | 117 | the exception argument so we don't raise a new exception over the |
| | 118 | pending one. |
| 114 | 119 | """ |
| 115 | 120 | if self.transaction_state: |
| 116 | 121 | del self.transaction_state[-1] |
| … |
… |
class BaseDatabaseWrapper(object):
|
| 122 | 127 | self._leave_transaction_management(self.is_managed()) |
| 123 | 128 | if self._dirty: |
| 124 | 129 | self.rollback() |
| 125 | | raise TransactionManagementError( |
| 126 | | "Transaction managed block ended with pending COMMIT/ROLLBACK") |
| | 130 | if exception is None: |
| | 131 | raise TransactionManagementError( |
| | 132 | "Transaction managed block ended with pending COMMIT/ROLLBACK") |
| 127 | 133 | self._dirty = False |
| 128 | 134 | |
| 129 | 135 | def validate_thread_sharing(self): |
diff --git a/django/db/transaction.py b/django/db/transaction.py
index f3ce2b2..91744f7 100644
|
a
|
b
|
def enter_transaction_management(managed=True, using=None):
|
| 39 | 39 | connection = connections[using] |
| 40 | 40 | connection.enter_transaction_management(managed) |
| 41 | 41 | |
| 42 | | def leave_transaction_management(using=None): |
| | 42 | def leave_transaction_management(using=None, exception=None): |
| 43 | 43 | """ |
| 44 | 44 | Leaves transaction management for a running thread. A dirty flag is carried |
| 45 | 45 | over to the surrounding block, as a commit will commit all changes, even |
| … |
… |
def leave_transaction_management(using=None):
|
| 48 | 48 | if using is None: |
| 49 | 49 | using = DEFAULT_DB_ALIAS |
| 50 | 50 | connection = connections[using] |
| 51 | | connection.leave_transaction_management() |
| | 51 | connection.leave_transaction_management(exception) |
| 52 | 52 | |
| 53 | 53 | def is_dirty(using=None): |
| 54 | 54 | """ |
| … |
… |
def commit_manually(using=None):
|
| 284 | 284 | managed(True, using=using) |
| 285 | 285 | |
| 286 | 286 | def exiting(exc_value, using): |
| 287 | | leave_transaction_management(using=using) |
| | 287 | leave_transaction_management(using=using, exception=exc_value) |
| 288 | 288 | |
| 289 | 289 | return _transaction_func(entering, exiting, using) |
diff --git a/docs/topics/db/transactions.txt b/docs/topics/db/transactions.txt
index 9928354..2ca2265 100644
|
a
|
b
|
managers, too.
|
| 190 | 190 | def viewfunc2(request): |
| 191 | 191 | .... |
| 192 | 192 | |
| | 193 | .. note:: |
| | 194 | |
| | 195 | If there's any uncaught exception raised from your function, |
| | 196 | Django will rollback the transaction and reraise the exception. |
| | 197 | This usually happened when a call to ``transaction.commit()`` |
| | 198 | itself from your function raised an exception. |
| | 199 | |
| 193 | 200 | .. _topics-db-transactions-requirements: |
| 194 | 201 | |
| 195 | 202 | Requirements for transaction handling |
diff --git a/tests/regressiontests/transactions_regress/tests.py b/tests/regressiontests/transactions_regress/tests.py
index 90b3df0..f2a3be1 100644
|
a
|
b
|
|
| 1 | 1 | from __future__ import absolute_import |
| 2 | 2 | |
| 3 | | from django.core.exceptions import ImproperlyConfigured |
| | 3 | from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist |
| 4 | 4 | from django.db import connection, connections, transaction, DEFAULT_DB_ALIAS |
| 5 | 5 | from django.db.transaction import commit_on_success, commit_manually, TransactionManagementError |
| 6 | 6 | from django.test import TransactionTestCase, skipUnlessDBFeature |
| … |
… |
class TestPostgresAutocommit(TransactionTestCase):
|
| 228 | 228 | self.assertEqual(connection.isolation_level, self._autocommit) |
| 229 | 229 | |
| 230 | 230 | |
| | 231 | class TestExceptionDuringTransaction(TransactionTestCase): |
| | 232 | """ When an exception happens during a transaction, the transaction should |
| | 233 | not swallow the original exception and replace it by a |
| | 234 | TransactionManagementError. The original exception should have priority. |
| | 235 | """ |
| | 236 | |
| | 237 | def test_commit_manually_exception_raised(self): |
| | 238 | @commit_manually |
| | 239 | def fun_with_exception(): |
| | 240 | _ = Mod.objects.get(fld=777) |
| | 241 | |
| | 242 | self.assertRaises(ObjectDoesNotExist, fun_with_exception) |
| | 243 | |
| | 244 | def test_commit_on_success_exception_raised(self): |
| | 245 | @commit_on_success |
| | 246 | def fun_with_exception(): |
| | 247 | _ = Mod.objects.get(fld=777) |
| | 248 | |
| | 249 | self.assertRaises(ObjectDoesNotExist, fun_with_exception) |
| | 250 | |
| | 251 | |
| 231 | 252 | class TestManyToManyAddTransaction(TransactionTestCase): |
| 232 | 253 | def test_manyrelated_add_commit(self): |
| 233 | 254 | "Test for https://code.djangoproject.com/ticket/16818" |