Code

Ticket #6623: 6623-5.diff

File 6623-5.diff, 5.0 KB (added by k4ml, 20 months ago)
Line 
1diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
2index 649f807..c637640 100644
3--- a/django/db/backends/__init__.py
4+++ b/django/db/backends/__init__.py
5@@ -106,11 +106,16 @@ class BaseDatabaseWrapper(object):
6             self._dirty = False
7         self._enter_transaction_management(managed)
8 
9-    def leave_transaction_management(self):
10+    def leave_transaction_management(self, exception=None):
11         """
12         Leaves transaction management for a running thread. A dirty flag is carried
13         over to the surrounding block, as a commit will commit all changes, even
14         those from outside. (Commits are on connection level.)
15+
16+        If this function is called from within a finally block where an
17+        exception is in the process of working its way up the stack, pass
18+        the exception argument so we don't raise a new exception over the
19+        pending one.
20         """
21         if self.transaction_state:
22             del self.transaction_state[-1]
23@@ -122,8 +127,9 @@ class BaseDatabaseWrapper(object):
24         self._leave_transaction_management(self.is_managed())
25         if self._dirty:
26             self.rollback()
27-            raise TransactionManagementError(
28-                "Transaction managed block ended with pending COMMIT/ROLLBACK")
29+            if exception is None:
30+                raise TransactionManagementError(
31+                    "Transaction managed block ended with pending COMMIT/ROLLBACK")
32         self._dirty = False
33 
34     def validate_thread_sharing(self):
35diff --git a/django/db/transaction.py b/django/db/transaction.py
36index f3ce2b2..91744f7 100644
37--- a/django/db/transaction.py
38+++ b/django/db/transaction.py
39@@ -39,7 +39,7 @@ def enter_transaction_management(managed=True, using=None):
40     connection = connections[using]
41     connection.enter_transaction_management(managed)
42 
43-def leave_transaction_management(using=None):
44+def leave_transaction_management(using=None, exception=None):
45     """
46     Leaves transaction management for a running thread. A dirty flag is carried
47     over to the surrounding block, as a commit will commit all changes, even
48@@ -48,7 +48,7 @@ def leave_transaction_management(using=None):
49     if using is None:
50         using = DEFAULT_DB_ALIAS
51     connection = connections[using]
52-    connection.leave_transaction_management()
53+    connection.leave_transaction_management(exception)
54 
55 def is_dirty(using=None):
56     """
57@@ -284,6 +284,6 @@ def commit_manually(using=None):
58         managed(True, using=using)
59 
60     def exiting(exc_value, using):
61-        leave_transaction_management(using=using)
62+        leave_transaction_management(using=using, exception=exc_value)
63 
64     return _transaction_func(entering, exiting, using)
65diff --git a/docs/topics/db/transactions.txt b/docs/topics/db/transactions.txt
66index 9928354..2ca2265 100644
67--- a/docs/topics/db/transactions.txt
68+++ b/docs/topics/db/transactions.txt
69@@ -190,6 +190,13 @@ managers, too.
70         def viewfunc2(request):
71             ....
72 
73+.. note::
74+
75+    If there's any uncaught exception raised from your function,
76+    Django will rollback the transaction and reraise the exception.
77+    This usually happened when a call to ``transaction.commit()``
78+    itself from your function raised an exception.
79+
80 .. _topics-db-transactions-requirements:
81 
82 Requirements for transaction handling
83diff --git a/tests/regressiontests/transactions_regress/tests.py b/tests/regressiontests/transactions_regress/tests.py
84index 90b3df0..f2a3be1 100644
85--- a/tests/regressiontests/transactions_regress/tests.py
86+++ b/tests/regressiontests/transactions_regress/tests.py
87@@ -1,6 +1,6 @@
88 from __future__ import absolute_import
89 
90-from django.core.exceptions import ImproperlyConfigured
91+from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
92 from django.db import connection, connections, transaction, DEFAULT_DB_ALIAS
93 from django.db.transaction import commit_on_success, commit_manually, TransactionManagementError
94 from django.test import TransactionTestCase, skipUnlessDBFeature
95@@ -228,6 +228,27 @@ class TestPostgresAutocommit(TransactionTestCase):
96         self.assertEqual(connection.isolation_level, self._autocommit)
97 
98 
99+class TestExceptionDuringTransaction(TransactionTestCase):
100+    """ When an exception happens during a transaction, the transaction should
101+        not swallow the original exception and replace it by a
102+        TransactionManagementError. The original exception should have priority.
103+    """
104+
105+    def test_commit_manually_exception_raised(self):
106+        @commit_manually
107+        def fun_with_exception():
108+            _ = Mod.objects.get(fld=777)
109+
110+        self.assertRaises(ObjectDoesNotExist, fun_with_exception)
111+
112+    def test_commit_on_success_exception_raised(self):
113+        @commit_on_success
114+        def fun_with_exception():
115+            _ = Mod.objects.get(fld=777)
116+
117+        self.assertRaises(ObjectDoesNotExist, fun_with_exception)
118+
119+
120 class TestManyToManyAddTransaction(TransactionTestCase):
121     def test_manyrelated_add_commit(self):
122         "Test for https://code.djangoproject.com/ticket/16818"