Fixed #10771 -- added support for using the transaction management functions as context managers in Python 2.5 and above. Thanks to Jacob for help with the docs.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@14288 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
cfbba28c39
commit
27db9378cf
|
@ -11,6 +11,7 @@ called, a commit is made.
|
||||||
Managed transactions don't do those commits, but will need some kind of manual
|
Managed transactions don't do those commits, but will need some kind of manual
|
||||||
or implicit commits or rollbacks.
|
or implicit commits or rollbacks.
|
||||||
"""
|
"""
|
||||||
|
import sys
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import thread
|
import thread
|
||||||
|
@ -20,8 +21,9 @@ try:
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from django.utils.functional import wraps # Python 2.4 fallback.
|
from django.utils.functional import wraps # Python 2.4 fallback.
|
||||||
from django.db import connections, DEFAULT_DB_ALIAS
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.db import connections, DEFAULT_DB_ALIAS
|
||||||
|
|
||||||
class TransactionManagementError(Exception):
|
class TransactionManagementError(Exception):
|
||||||
"""
|
"""
|
||||||
|
@ -257,32 +259,80 @@ def savepoint_commit(sid, using=None):
|
||||||
# DECORATORS #
|
# DECORATORS #
|
||||||
##############
|
##############
|
||||||
|
|
||||||
def autocommit(using=None):
|
class Transaction(object):
|
||||||
"""
|
"""
|
||||||
Decorator that activates commit on save. This is Django's default behavior;
|
Acts as either a decorator, or a context manager. If it's a decorator it
|
||||||
this decorator is useful if you globally activated transaction management in
|
takes a function and returns a wrapped function. If it's a contextmanager
|
||||||
your settings file and want the default behavior in some view functions.
|
it's used with the ``with`` statement. In either event entering/exiting
|
||||||
"""
|
are called before and after, respectively, the function/block is executed.
|
||||||
def inner_autocommit(func, db=None):
|
|
||||||
def _autocommit(*args, **kw):
|
|
||||||
try:
|
|
||||||
enter_transaction_management(managed=False, using=db)
|
|
||||||
managed(False, using=db)
|
|
||||||
return func(*args, **kw)
|
|
||||||
finally:
|
|
||||||
leave_transaction_management(using=db)
|
|
||||||
return wraps(func)(_autocommit)
|
|
||||||
|
|
||||||
|
autocommit, commit_on_success, and commit_manually contain the
|
||||||
|
implementations of entering and exiting.
|
||||||
|
"""
|
||||||
|
def __init__(self, entering, exiting, using):
|
||||||
|
self.entering = entering
|
||||||
|
self.exiting = exiting
|
||||||
|
self.using = using
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.entering(self.using)
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
self.exiting(exc_value, self.using)
|
||||||
|
|
||||||
|
def __call__(self, func):
|
||||||
|
@wraps(func)
|
||||||
|
def inner(*args, **kwargs):
|
||||||
|
# Once we drop support for Python 2.4 this block should become:
|
||||||
|
# with self:
|
||||||
|
# func(*args, **kwargs)
|
||||||
|
self.__enter__()
|
||||||
|
try:
|
||||||
|
res = func(*args, **kwargs)
|
||||||
|
except:
|
||||||
|
self.__exit__(*sys.exc_info())
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
self.__exit__(None, None, None)
|
||||||
|
return res
|
||||||
|
return inner
|
||||||
|
|
||||||
|
def _transaction_func(entering, exiting, using):
|
||||||
|
"""
|
||||||
|
Takes 3 things, an entering function (what to do to start this block of
|
||||||
|
transaction management), an exiting function (what to do to end it, on both
|
||||||
|
success and failure, and using which can be: None, indiciating using is
|
||||||
|
DEFAULT_DB_ALIAS, a callable, indicating that using is DEFAULT_DB_ALIAS and
|
||||||
|
to return the function already wrapped.
|
||||||
|
|
||||||
|
Returns either a Transaction objects, which is both a decorator and a
|
||||||
|
context manager, or a wrapped function, if using is a callable.
|
||||||
|
"""
|
||||||
# Note that although the first argument is *called* `using`, it
|
# Note that although the first argument is *called* `using`, it
|
||||||
# may actually be a function; @autocommit and @autocommit('foo')
|
# may actually be a function; @autocommit and @autocommit('foo')
|
||||||
# are both allowed forms.
|
# are both allowed forms.
|
||||||
if using is None:
|
if using is None:
|
||||||
using = DEFAULT_DB_ALIAS
|
using = DEFAULT_DB_ALIAS
|
||||||
if callable(using):
|
if callable(using):
|
||||||
return inner_autocommit(using, DEFAULT_DB_ALIAS)
|
return Transaction(entering, exiting, DEFAULT_DB_ALIAS)(using)
|
||||||
return lambda func: inner_autocommit(func, using)
|
return Transaction(entering, exiting, using)
|
||||||
|
|
||||||
|
|
||||||
|
def autocommit(using=None):
|
||||||
|
"""
|
||||||
|
Decorator that activates commit on save. This is Django's default behavior;
|
||||||
|
this decorator is useful if you globally activated transaction management in
|
||||||
|
your settings file and want the default behavior in some view functions.
|
||||||
|
"""
|
||||||
|
def entering(using):
|
||||||
|
enter_transaction_management(managed=False, using=using)
|
||||||
|
managed(False, using=using)
|
||||||
|
|
||||||
|
def exiting(exc_value, using):
|
||||||
|
leave_transaction_management(using=using)
|
||||||
|
|
||||||
|
return _transaction_func(entering, exiting, using)
|
||||||
|
|
||||||
def commit_on_success(using=None):
|
def commit_on_success(using=None):
|
||||||
"""
|
"""
|
||||||
This decorator activates commit on response. This way, if the view function
|
This decorator activates commit on response. This way, if the view function
|
||||||
|
@ -290,38 +340,23 @@ def commit_on_success(using=None):
|
||||||
a rollback is made. This is one of the most common ways to do transaction
|
a rollback is made. This is one of the most common ways to do transaction
|
||||||
control in Web apps.
|
control in Web apps.
|
||||||
"""
|
"""
|
||||||
def inner_commit_on_success(func, db=None):
|
def entering(using):
|
||||||
def _commit_on_success(*args, **kw):
|
enter_transaction_management(using=using)
|
||||||
try:
|
managed(True, using=using)
|
||||||
enter_transaction_management(using=db)
|
|
||||||
managed(True, using=db)
|
|
||||||
try:
|
|
||||||
res = func(*args, **kw)
|
|
||||||
except:
|
|
||||||
# All exceptions must be handled here (even string ones).
|
|
||||||
if is_dirty(using=db):
|
|
||||||
rollback(using=db)
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
if is_dirty(using=db):
|
|
||||||
try:
|
|
||||||
commit(using=db)
|
|
||||||
except:
|
|
||||||
rollback(using=db)
|
|
||||||
raise
|
|
||||||
return res
|
|
||||||
finally:
|
|
||||||
leave_transaction_management(using=db)
|
|
||||||
return wraps(func)(_commit_on_success)
|
|
||||||
|
|
||||||
# Note that although the first argument is *called* `using`, it
|
def exiting(exc_value, using):
|
||||||
# may actually be a function; @autocommit and @autocommit('foo')
|
if exc_value is not None:
|
||||||
# are both allowed forms.
|
if is_dirty(using=using):
|
||||||
if using is None:
|
rollback(using=using)
|
||||||
using = DEFAULT_DB_ALIAS
|
else:
|
||||||
if callable(using):
|
if is_dirty(using=using):
|
||||||
return inner_commit_on_success(using, DEFAULT_DB_ALIAS)
|
try:
|
||||||
return lambda func: inner_commit_on_success(func, using)
|
commit(using=using)
|
||||||
|
except:
|
||||||
|
rollback(using=using)
|
||||||
|
raise
|
||||||
|
|
||||||
|
return _transaction_func(entering, exiting, using)
|
||||||
|
|
||||||
def commit_manually(using=None):
|
def commit_manually(using=None):
|
||||||
"""
|
"""
|
||||||
|
@ -330,22 +365,11 @@ def commit_manually(using=None):
|
||||||
own -- it's up to the user to call the commit and rollback functions
|
own -- it's up to the user to call the commit and rollback functions
|
||||||
themselves.
|
themselves.
|
||||||
"""
|
"""
|
||||||
def inner_commit_manually(func, db=None):
|
def entering(using):
|
||||||
def _commit_manually(*args, **kw):
|
enter_transaction_management(using=using)
|
||||||
try:
|
managed(True, using=using)
|
||||||
enter_transaction_management(using=db)
|
|
||||||
managed(True, using=db)
|
|
||||||
return func(*args, **kw)
|
|
||||||
finally:
|
|
||||||
leave_transaction_management(using=db)
|
|
||||||
|
|
||||||
return wraps(func)(_commit_manually)
|
def exiting(exc_value, using):
|
||||||
|
leave_transaction_management(using=using)
|
||||||
|
|
||||||
# Note that although the first argument is *called* `using`, it
|
return _transaction_func(entering, exiting, using)
|
||||||
# may actually be a function; @autocommit and @autocommit('foo')
|
|
||||||
# are both allowed forms.
|
|
||||||
if using is None:
|
|
||||||
using = DEFAULT_DB_ALIAS
|
|
||||||
if callable(using):
|
|
||||||
return inner_commit_manually(using, DEFAULT_DB_ALIAS)
|
|
||||||
return lambda func: inner_commit_manually(func, using)
|
|
||||||
|
|
|
@ -73,6 +73,19 @@ you just won't get any of the nice new unittest2 features.
|
||||||
|
|
||||||
.. _unittest2: http://pypi.python.org/pypi/unittest2
|
.. _unittest2: http://pypi.python.org/pypi/unittest2
|
||||||
|
|
||||||
|
Transaction context managers
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Users of Python 2.5 and above may now use :ref:`transaction management functions
|
||||||
|
<transaction-management-functions>` as `context managers`_. For example::
|
||||||
|
|
||||||
|
with transaction.autocommit():
|
||||||
|
# ...
|
||||||
|
|
||||||
|
.. _context managers: http://docs.python.org/glossary.html#term-context-manager
|
||||||
|
|
||||||
|
For more information, see :ref:`transaction-management-functions`.
|
||||||
|
|
||||||
Everything else
|
Everything else
|
||||||
~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Managing database transactions
|
Managing database transactions
|
||||||
==============================
|
==============================
|
||||||
|
|
||||||
.. currentmodule:: django.db
|
.. currentmodule:: django.db.transaction
|
||||||
|
|
||||||
Django gives you a few ways to control how database transactions are managed,
|
Django gives you a few ways to control how database transactions are managed,
|
||||||
if you're using a database that supports transactions.
|
if you're using a database that supports transactions.
|
||||||
|
@ -50,105 +50,138 @@ An exception is ``CacheMiddleware``, which is never affected. The cache
|
||||||
middleware uses its own database cursor (which is mapped to its own database
|
middleware uses its own database cursor (which is mapped to its own database
|
||||||
connection internally).
|
connection internally).
|
||||||
|
|
||||||
|
.. _transaction-management-functions:
|
||||||
|
|
||||||
Controlling transaction management in views
|
Controlling transaction management in views
|
||||||
===========================================
|
===========================================
|
||||||
|
|
||||||
For most people, implicit request-based transactions work wonderfully. However,
|
.. versionchanged:: 1.3
|
||||||
if you need more fine-grained control over how transactions are managed, you
|
Transaction management context managers are new in Django 1.3.
|
||||||
can use Python decorators to change the way transactions are handled by a
|
|
||||||
particular view function. All of the decorators take an option ``using``
|
|
||||||
parameter which should be the alias for a database connection for which the
|
|
||||||
behavior applies to. If no alias is specified then the ``"default"`` database
|
|
||||||
is used.
|
|
||||||
|
|
||||||
.. note::
|
For most people, implicit request-based transactions work wonderfully. However,
|
||||||
|
if you need more fine-grained control over how transactions are managed, you can
|
||||||
|
use a set of functions in ``django.db.transaction`` to control transactions on a
|
||||||
|
per-function or per-code-block basis.
|
||||||
|
|
||||||
|
These functions, described in detail below, can be used in two different ways:
|
||||||
|
|
||||||
|
* As a decorator_ on a particular function. For example::
|
||||||
|
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
@transaction.commit_on_success()
|
||||||
|
def viewfunc(request):
|
||||||
|
# ...
|
||||||
|
# this code executes inside a transaction
|
||||||
|
# ...
|
||||||
|
|
||||||
|
This technique works with all supported version of Python (that is, with
|
||||||
|
Python 2.4 and greater).
|
||||||
|
|
||||||
|
* As a `context manager`_ around a particular block of code::
|
||||||
|
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
def viewfunc(request):
|
||||||
|
# ...
|
||||||
|
# this code executes using default transaction management
|
||||||
|
# ...
|
||||||
|
|
||||||
|
with transaction.commit_on_success():
|
||||||
|
# ...
|
||||||
|
# this code executes inside a transaction
|
||||||
|
# ...
|
||||||
|
|
||||||
|
The ``with`` statement is new in Python 2.5, and so this syntax can only
|
||||||
|
be used with Python 2.5 and above.
|
||||||
|
|
||||||
|
.. _decorator: http://docs.python.org/glossary.html#term-decorator
|
||||||
|
.. _context manager: http://docs.python.org/glossary.html#term-context-manager
|
||||||
|
|
||||||
|
For maximum compatibility, all of the examples below show transactions using the
|
||||||
|
decorator syntax, but all of the follow functions may be used as context
|
||||||
|
managers, too.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
Although the examples below use view functions as examples, these
|
Although the examples below use view functions as examples, these
|
||||||
decorators can be applied to non-view functions as well.
|
decorators and context managers can be used anywhere in your code
|
||||||
|
that you need to deal with transactions.
|
||||||
|
|
||||||
.. _topics-db-transactions-autocommit:
|
.. _topics-db-transactions-autocommit:
|
||||||
|
|
||||||
``django.db.transaction.autocommit``
|
.. function:: autocommit
|
||||||
------------------------------------
|
|
||||||
|
|
||||||
Use the ``autocommit`` decorator to switch a view function to Django's default
|
Use the ``autocommit`` decorator to switch a view function to Django's
|
||||||
commit behavior, regardless of the global transaction setting.
|
default commit behavior, regardless of the global transaction setting.
|
||||||
|
|
||||||
Example::
|
Example::
|
||||||
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
||||||
@transaction.autocommit
|
@transaction.autocommit
|
||||||
def viewfunc(request):
|
def viewfunc(request):
|
||||||
....
|
....
|
||||||
|
|
||||||
@transaction.autocommit(using="my_other_database")
|
@transaction.autocommit(using="my_other_database")
|
||||||
def viewfunc2(request):
|
def viewfunc2(request):
|
||||||
....
|
....
|
||||||
|
|
||||||
Within ``viewfunc()``, transactions will be committed as soon as you call
|
Within ``viewfunc()``, transactions will be committed as soon as you call
|
||||||
``model.save()``, ``model.delete()``, or any other function that writes to the
|
``model.save()``, ``model.delete()``, or any other function that writes to
|
||||||
database. ``viewfunc2()`` will have this same behavior, but for the
|
the database. ``viewfunc2()`` will have this same behavior, but for the
|
||||||
``"my_other_database"`` connection.
|
``"my_other_database"`` connection.
|
||||||
|
|
||||||
``django.db.transaction.commit_on_success``
|
.. function:: commit_on_success
|
||||||
-------------------------------------------
|
|
||||||
|
|
||||||
Use the ``commit_on_success`` decorator to use a single transaction for
|
Use the ``commit_on_success`` decorator to use a single transaction for all
|
||||||
all the work done in a function::
|
the work done in a function::
|
||||||
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
||||||
@transaction.commit_on_success
|
@transaction.commit_on_success
|
||||||
def viewfunc(request):
|
def viewfunc(request):
|
||||||
....
|
....
|
||||||
|
|
||||||
@transaction.commit_on_success(using="my_other_database")
|
@transaction.commit_on_success(using="my_other_database")
|
||||||
def viewfunc2(request):
|
def viewfunc2(request):
|
||||||
....
|
....
|
||||||
|
|
||||||
If the function returns successfully, then Django will commit all work done
|
If the function returns successfully, then Django will commit all work done
|
||||||
within the function at that point. If the function raises an exception, though,
|
within the function at that point. If the function raises an exception,
|
||||||
Django will roll back the transaction.
|
though, Django will roll back the transaction.
|
||||||
|
|
||||||
``django.db.transaction.commit_manually``
|
.. function:: commit_manually
|
||||||
-----------------------------------------
|
|
||||||
|
|
||||||
Use the ``commit_manually`` decorator if you need full control over
|
Use the ``commit_manually`` decorator if you need full control over
|
||||||
transactions. It tells Django you'll be managing the transaction on your own.
|
transactions. It tells Django you'll be managing the transaction on your
|
||||||
|
own.
|
||||||
|
|
||||||
If your view changes data and doesn't ``commit()`` or ``rollback()``, Django
|
If your view changes data and doesn't ``commit()`` or ``rollback()``,
|
||||||
will raise a ``TransactionManagementError`` exception.
|
Django will raise a ``TransactionManagementError`` exception.
|
||||||
|
|
||||||
Manual transaction management looks like this::
|
Manual transaction management looks like this::
|
||||||
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
||||||
@transaction.commit_manually
|
@transaction.commit_manually
|
||||||
def viewfunc(request):
|
def viewfunc(request):
|
||||||
...
|
|
||||||
# You can commit/rollback however and whenever you want
|
|
||||||
transaction.commit()
|
|
||||||
...
|
|
||||||
|
|
||||||
# But you've got to remember to do it yourself!
|
|
||||||
try:
|
|
||||||
...
|
...
|
||||||
except:
|
# You can commit/rollback however and whenever you want
|
||||||
transaction.rollback()
|
|
||||||
else:
|
|
||||||
transaction.commit()
|
transaction.commit()
|
||||||
|
...
|
||||||
|
|
||||||
@transaction.commit_manually(using="my_other_database")
|
# But you've got to remember to do it yourself!
|
||||||
def viewfunc2(request):
|
try:
|
||||||
....
|
...
|
||||||
|
except:
|
||||||
|
transaction.rollback()
|
||||||
|
else:
|
||||||
|
transaction.commit()
|
||||||
|
|
||||||
.. admonition:: An important note to users of earlier Django releases:
|
@transaction.commit_manually(using="my_other_database")
|
||||||
|
def viewfunc2(request):
|
||||||
The database ``connection.commit()`` and ``connection.rollback()`` methods
|
....
|
||||||
(called ``db.commit()`` and ``db.rollback()`` in 0.91 and earlier) no
|
|
||||||
longer exist. They've been replaced by ``transaction.commit()`` and
|
|
||||||
``transaction.rollback()``.
|
|
||||||
|
|
||||||
How to globally deactivate transaction management
|
How to globally deactivate transaction management
|
||||||
=================================================
|
=================================================
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
from django.db import connection, transaction, IntegrityError, DEFAULT_DB_ALIAS
|
from django.db import connection, transaction, IntegrityError, DEFAULT_DB_ALIAS
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.test import TransactionTestCase, skipUnlessDBFeature
|
from django.test import TransactionTestCase, skipUnlessDBFeature
|
||||||
|
@ -5,6 +7,10 @@ from django.test import TransactionTestCase, skipUnlessDBFeature
|
||||||
from models import Reporter
|
from models import Reporter
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info >= (2, 5):
|
||||||
|
from tests_25 import TransactionContextManagerTests
|
||||||
|
|
||||||
|
|
||||||
class TransactionTests(TransactionTestCase):
|
class TransactionTests(TransactionTestCase):
|
||||||
def create_a_reporter_then_fail(self, first, last):
|
def create_a_reporter_then_fail(self, first, last):
|
||||||
a = Reporter(first_name=first, last_name=last)
|
a = Reporter(first_name=first, last_name=last)
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
from django.db import connection, transaction, IntegrityError
|
||||||
|
from django.test import TransactionTestCase, skipUnlessDBFeature
|
||||||
|
|
||||||
|
from models import Reporter
|
||||||
|
|
||||||
|
|
||||||
|
class TransactionContextManagerTests(TransactionTestCase):
|
||||||
|
def create_reporter_and_fail(self):
|
||||||
|
Reporter.objects.create(first_name="Bob", last_name="Holtzman")
|
||||||
|
raise Exception
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('supports_transactions')
|
||||||
|
def test_autocommit(self):
|
||||||
|
"""
|
||||||
|
The default behavior is to autocommit after each save() action.
|
||||||
|
"""
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
self.create_reporter_and_fail()
|
||||||
|
# The object created before the exception still exists
|
||||||
|
self.assertEqual(Reporter.objects.count(), 1)
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('supports_transactions')
|
||||||
|
def test_autocommit_context_manager(self):
|
||||||
|
"""
|
||||||
|
The autocommit context manager works exactly the same as the default
|
||||||
|
behavior.
|
||||||
|
"""
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
with transaction.autocommit():
|
||||||
|
self.create_reporter_and_fail()
|
||||||
|
|
||||||
|
self.assertEqual(Reporter.objects.count(), 1)
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('supports_transactions')
|
||||||
|
def test_autocommit_context_manager_with_using(self):
|
||||||
|
"""
|
||||||
|
The autocommit context manager also works with a using argument.
|
||||||
|
"""
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
with transaction.autocommit(using="default"):
|
||||||
|
self.create_reporter_and_fail()
|
||||||
|
|
||||||
|
self.assertEqual(Reporter.objects.count(), 1)
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('supports_transactions')
|
||||||
|
def test_commit_on_success(self):
|
||||||
|
"""
|
||||||
|
With the commit_on_success context manager, the transaction is only
|
||||||
|
committed if the block doesn't throw an exception.
|
||||||
|
"""
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
with transaction.commit_on_success():
|
||||||
|
self.create_reporter_and_fail()
|
||||||
|
|
||||||
|
self.assertEqual(Reporter.objects.count(), 0)
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('supports_transactions')
|
||||||
|
def test_commit_on_success_with_using(self):
|
||||||
|
"""
|
||||||
|
The commit_on_success context manager also works with a using argument.
|
||||||
|
"""
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
with transaction.commit_on_success(using="default"):
|
||||||
|
self.create_reporter_and_fail()
|
||||||
|
|
||||||
|
self.assertEqual(Reporter.objects.count(), 0)
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('supports_transactions')
|
||||||
|
def test_commit_on_success_succeed(self):
|
||||||
|
"""
|
||||||
|
If there aren't any exceptions, the data will get saved.
|
||||||
|
"""
|
||||||
|
Reporter.objects.create(first_name="Alice", last_name="Smith")
|
||||||
|
with transaction.commit_on_success():
|
||||||
|
Reporter.objects.filter(first_name="Alice").delete()
|
||||||
|
|
||||||
|
self.assertQuerysetEqual(Reporter.objects.all(), [])
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('supports_transactions')
|
||||||
|
def test_manually_managed(self):
|
||||||
|
"""
|
||||||
|
You can manually manage transactions if you really want to, but you
|
||||||
|
have to remember to commit/rollback.
|
||||||
|
"""
|
||||||
|
with transaction.commit_manually():
|
||||||
|
Reporter.objects.create(first_name="Libby", last_name="Holtzman")
|
||||||
|
transaction.commit()
|
||||||
|
self.assertEqual(Reporter.objects.count(), 1)
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('supports_transactions')
|
||||||
|
def test_manually_managed_mistake(self):
|
||||||
|
"""
|
||||||
|
If you forget, you'll get bad errors.
|
||||||
|
"""
|
||||||
|
with self.assertRaises(transaction.TransactionManagementError):
|
||||||
|
with transaction.commit_manually():
|
||||||
|
Reporter.objects.create(first_name="Scott", last_name="Browning")
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('supports_transactions')
|
||||||
|
def test_manually_managed_with_using(self):
|
||||||
|
"""
|
||||||
|
The commit_manually function also works with a using argument.
|
||||||
|
"""
|
||||||
|
with self.assertRaises(transaction.TransactionManagementError):
|
||||||
|
with transaction.commit_manually(using="default"):
|
||||||
|
Reporter.objects.create(first_name="Walter", last_name="Cronkite")
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('requires_rollback_on_dirty_transaction')
|
||||||
|
def test_bad_sql(self):
|
||||||
|
"""
|
||||||
|
Regression for #11900: If a block wrapped by commit_on_success
|
||||||
|
writes a transaction that can't be committed, that transaction should
|
||||||
|
be rolled back. The bug is only visible using the psycopg2 backend,
|
||||||
|
though the fix is generally a good idea.
|
||||||
|
"""
|
||||||
|
with self.assertRaises(IntegrityError):
|
||||||
|
with transaction.commit_on_success():
|
||||||
|
cursor = connection.cursor()
|
||||||
|
cursor.execute("INSERT INTO transactions_reporter (first_name, last_name) VALUES ('Douglas', 'Adams');")
|
||||||
|
transaction.set_dirty()
|
||||||
|
transaction.rollback()
|
Loading…
Reference in New Issue