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" |