2006-05-02 09:31:56 +08:00
|
|
|
"""
|
|
|
|
This module implements a transaction manager that can be used to define
|
|
|
|
transaction handling in a request or view function. It is used by transaction
|
|
|
|
control middleware and decorators.
|
|
|
|
|
|
|
|
The transaction manager can be in managed or in auto state. Auto state means the
|
|
|
|
system is using a commit-on-save strategy (actually it's more like
|
|
|
|
commit-on-change). As soon as the .save() or .delete() (or related) methods are
|
|
|
|
called, a commit is made.
|
|
|
|
|
|
|
|
Managed transactions don't do those commits, but will need some kind of manual
|
|
|
|
or implicit commits or rollbacks.
|
|
|
|
"""
|
|
|
|
|
2013-03-03 03:25:25 +08:00
|
|
|
import warnings
|
|
|
|
|
2011-03-28 10:11:19 +08:00
|
|
|
from functools import wraps
|
2010-10-20 03:38:15 +08:00
|
|
|
|
2013-03-05 05:17:35 +08:00
|
|
|
from django.db import connections, DatabaseError, DEFAULT_DB_ALIAS
|
2006-05-02 09:31:56 +08:00
|
|
|
|
2011-01-17 17:52:47 +08:00
|
|
|
|
2006-05-02 09:31:56 +08:00
|
|
|
class TransactionManagementError(Exception):
|
|
|
|
"""
|
|
|
|
This exception is thrown when something bad happens with transaction
|
|
|
|
management.
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
2013-02-22 04:26:06 +08:00
|
|
|
################
|
|
|
|
# Private APIs #
|
|
|
|
################
|
|
|
|
|
|
|
|
def get_connection(using=None):
|
|
|
|
"""
|
|
|
|
Get a database connection by name, or the default database connection
|
|
|
|
if no name is provided.
|
|
|
|
"""
|
|
|
|
if using is None:
|
|
|
|
using = DEFAULT_DB_ALIAS
|
|
|
|
return connections[using]
|
|
|
|
|
2013-03-02 20:47:46 +08:00
|
|
|
def get_autocommit(using=None):
|
|
|
|
"""
|
|
|
|
Get the autocommit status of the connection.
|
|
|
|
"""
|
|
|
|
return get_connection(using).autocommit
|
|
|
|
|
|
|
|
def set_autocommit(using=None, autocommit=True):
|
|
|
|
"""
|
|
|
|
Set the autocommit status of the connection.
|
|
|
|
"""
|
|
|
|
return get_connection(using).set_autocommit(autocommit)
|
|
|
|
|
2013-02-06 05:52:29 +08:00
|
|
|
def abort(using=None):
|
|
|
|
"""
|
|
|
|
Roll back any ongoing transactions and clean the transaction management
|
|
|
|
state of the connection.
|
|
|
|
|
|
|
|
This method is to be used only in cases where using balanced
|
|
|
|
leave_transaction_management() calls isn't possible. For example after a
|
|
|
|
request has finished, the transaction state isn't known, yet the connection
|
|
|
|
must be cleaned up for the next request.
|
|
|
|
"""
|
2013-02-22 04:26:06 +08:00
|
|
|
get_connection(using).abort()
|
2013-02-06 05:52:29 +08:00
|
|
|
|
2013-03-03 03:25:25 +08:00
|
|
|
def enter_transaction_management(managed=True, using=None, forced=False):
|
2006-05-02 09:31:56 +08:00
|
|
|
"""
|
|
|
|
Enters transaction management for a running thread. It must be balanced with
|
|
|
|
the appropriate leave_transaction_management call, since the actual state is
|
|
|
|
managed as a stack.
|
|
|
|
|
|
|
|
The state and dirty flag are carried over from the surrounding block or
|
|
|
|
from the settings, if there is no surrounding block (dirty is always false
|
|
|
|
when no current block is running).
|
|
|
|
"""
|
2013-03-03 03:25:25 +08:00
|
|
|
get_connection(using).enter_transaction_management(managed, forced)
|
2006-05-02 09:31:56 +08:00
|
|
|
|
2009-12-22 23:18:51 +08:00
|
|
|
def leave_transaction_management(using=None):
|
2006-05-02 09:31:56 +08:00
|
|
|
"""
|
|
|
|
Leaves transaction management for a running thread. A dirty flag is carried
|
|
|
|
over to the surrounding block, as a commit will commit all changes, even
|
|
|
|
those from outside. (Commits are on connection level.)
|
|
|
|
"""
|
2013-02-22 04:26:06 +08:00
|
|
|
get_connection(using).leave_transaction_management()
|
2006-05-02 09:31:56 +08:00
|
|
|
|
2009-12-22 23:18:51 +08:00
|
|
|
def is_dirty(using=None):
|
2006-05-02 09:31:56 +08:00
|
|
|
"""
|
|
|
|
Returns True if the current transaction requires a commit for changes to
|
|
|
|
happen.
|
|
|
|
"""
|
2013-02-22 04:26:06 +08:00
|
|
|
return get_connection(using).is_dirty()
|
2006-05-02 09:31:56 +08:00
|
|
|
|
2009-12-22 23:18:51 +08:00
|
|
|
def set_dirty(using=None):
|
2006-05-02 09:31:56 +08:00
|
|
|
"""
|
|
|
|
Sets a dirty flag for the current thread and code streak. This can be used
|
|
|
|
to decide in a managed block of code to decide whether there are open
|
|
|
|
changes waiting for commit.
|
|
|
|
"""
|
2013-02-22 04:26:06 +08:00
|
|
|
get_connection(using).set_dirty()
|
2006-05-02 09:31:56 +08:00
|
|
|
|
2009-12-22 23:18:51 +08:00
|
|
|
def set_clean(using=None):
|
2006-05-02 09:31:56 +08:00
|
|
|
"""
|
|
|
|
Resets a dirty flag for the current thread and code streak. This can be used
|
|
|
|
to decide in a managed block of code to decide whether a commit or rollback
|
|
|
|
should happen.
|
|
|
|
"""
|
2013-02-22 04:26:06 +08:00
|
|
|
get_connection(using).set_clean()
|
2008-08-12 13:59:43 +08:00
|
|
|
|
2009-12-22 23:18:51 +08:00
|
|
|
def clean_savepoints(using=None):
|
2013-02-22 04:26:06 +08:00
|
|
|
"""
|
|
|
|
Resets the counter used to generate unique savepoint ids in this thread.
|
|
|
|
"""
|
|
|
|
get_connection(using).clean_savepoints()
|
2006-05-02 09:31:56 +08:00
|
|
|
|
2009-12-22 23:18:51 +08:00
|
|
|
def is_managed(using=None):
|
2013-03-04 22:24:01 +08:00
|
|
|
warnings.warn("'is_managed' is deprecated.",
|
|
|
|
PendingDeprecationWarning, stacklevel=2)
|
2006-05-02 09:31:56 +08:00
|
|
|
|
2009-12-22 23:18:51 +08:00
|
|
|
def managed(flag=True, using=None):
|
2013-03-03 03:25:25 +08:00
|
|
|
warnings.warn("'managed' no longer serves a purpose.",
|
|
|
|
PendingDeprecationWarning, stacklevel=2)
|
2006-05-02 09:31:56 +08:00
|
|
|
|
2009-12-22 23:18:51 +08:00
|
|
|
def commit_unless_managed(using=None):
|
2013-03-04 20:12:59 +08:00
|
|
|
warnings.warn("'commit_unless_managed' is now a no-op.",
|
|
|
|
PendingDeprecationWarning, stacklevel=2)
|
2006-05-02 09:31:56 +08:00
|
|
|
|
2009-12-22 23:18:51 +08:00
|
|
|
def rollback_unless_managed(using=None):
|
2013-03-04 20:12:59 +08:00
|
|
|
warnings.warn("'rollback_unless_managed' is now a no-op.",
|
|
|
|
PendingDeprecationWarning, stacklevel=2)
|
2013-02-22 04:26:06 +08:00
|
|
|
|
|
|
|
###############
|
|
|
|
# Public APIs #
|
|
|
|
###############
|
2006-05-02 09:31:56 +08:00
|
|
|
|
2009-12-22 23:18:51 +08:00
|
|
|
def commit(using=None):
|
2006-05-02 09:31:56 +08:00
|
|
|
"""
|
2013-03-05 05:17:35 +08:00
|
|
|
Commits a transaction and resets the dirty flag.
|
2006-05-02 09:31:56 +08:00
|
|
|
"""
|
2013-02-22 04:26:06 +08:00
|
|
|
get_connection(using).commit()
|
2006-05-02 09:31:56 +08:00
|
|
|
|
2009-12-22 23:18:51 +08:00
|
|
|
def rollback(using=None):
|
2006-05-02 09:31:56 +08:00
|
|
|
"""
|
2013-03-05 05:17:35 +08:00
|
|
|
Rolls back a transaction and resets the dirty flag.
|
2006-05-02 09:31:56 +08:00
|
|
|
"""
|
2013-02-22 04:26:06 +08:00
|
|
|
get_connection(using).rollback()
|
2006-05-02 09:31:56 +08:00
|
|
|
|
2009-12-22 23:18:51 +08:00
|
|
|
def savepoint(using=None):
|
2008-08-12 13:34:56 +08:00
|
|
|
"""
|
|
|
|
Creates a savepoint (if supported and required by the backend) inside the
|
|
|
|
current transaction. Returns an identifier for the savepoint that will be
|
|
|
|
used for the subsequent rollback or commit.
|
|
|
|
"""
|
2013-02-22 04:26:06 +08:00
|
|
|
return get_connection(using).savepoint()
|
2008-08-12 13:34:56 +08:00
|
|
|
|
2009-12-22 23:18:51 +08:00
|
|
|
def savepoint_rollback(sid, using=None):
|
2008-08-12 13:34:56 +08:00
|
|
|
"""
|
|
|
|
Rolls back the most recent savepoint (if one exists). Does nothing if
|
|
|
|
savepoints are not supported.
|
|
|
|
"""
|
2013-02-22 04:26:06 +08:00
|
|
|
get_connection(using).savepoint_rollback(sid)
|
2008-08-12 13:34:56 +08:00
|
|
|
|
2009-12-22 23:18:51 +08:00
|
|
|
def savepoint_commit(sid, using=None):
|
2008-08-12 13:34:56 +08:00
|
|
|
"""
|
|
|
|
Commits the most recent savepoint (if one exists). Does nothing if
|
|
|
|
savepoints are not supported.
|
|
|
|
"""
|
2013-02-22 04:26:06 +08:00
|
|
|
get_connection(using).savepoint_commit(sid)
|
2008-08-12 13:34:56 +08:00
|
|
|
|
2013-03-05 05:17:35 +08:00
|
|
|
|
|
|
|
#################################
|
|
|
|
# Decorators / context managers #
|
|
|
|
#################################
|
|
|
|
|
|
|
|
class Atomic(object):
|
|
|
|
"""
|
|
|
|
This class guarantees the atomic execution of a given block.
|
|
|
|
|
|
|
|
An instance can be used either as a decorator or as a context manager.
|
|
|
|
|
|
|
|
When it's used as a decorator, __call__ wraps the execution of the
|
|
|
|
decorated function in the instance itself, used as a context manager.
|
|
|
|
|
|
|
|
When it's used as a context manager, __enter__ creates a transaction or a
|
|
|
|
savepoint, depending on whether a transaction is already in progress, and
|
|
|
|
__exit__ commits the transaction or releases the savepoint on normal exit,
|
|
|
|
and rolls back the transaction or to the savepoint on exceptions.
|
|
|
|
|
|
|
|
A stack of savepoints identifiers is maintained as an attribute of the
|
|
|
|
connection. None denotes a plain transaction.
|
|
|
|
|
|
|
|
This allows reentrancy even if the same AtomicWrapper is reused. For
|
|
|
|
example, it's possible to define `oa = @atomic('other')` and use `@ao` or
|
|
|
|
`with oa:` multiple times.
|
|
|
|
|
|
|
|
Since database connections are thread-local, this is thread-safe.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, using):
|
|
|
|
self.using = using
|
|
|
|
|
|
|
|
def _legacy_enter_transaction_management(self, connection):
|
|
|
|
if not connection.in_atomic_block:
|
|
|
|
if connection.transaction_state and connection.transaction_state[-1]:
|
|
|
|
connection._atomic_forced_unmanaged = True
|
|
|
|
connection.enter_transaction_management(managed=False)
|
|
|
|
else:
|
|
|
|
connection._atomic_forced_unmanaged = False
|
|
|
|
|
|
|
|
def _legacy_leave_transaction_management(self, connection):
|
|
|
|
if not connection.in_atomic_block and connection._atomic_forced_unmanaged:
|
|
|
|
connection.leave_transaction_management()
|
|
|
|
|
|
|
|
def __enter__(self):
|
|
|
|
connection = get_connection(self.using)
|
|
|
|
|
|
|
|
# Ensure we have a connection to the database before testing
|
|
|
|
# autocommit status.
|
|
|
|
connection.ensure_connection()
|
|
|
|
|
|
|
|
# Remove this when the legacy transaction management goes away.
|
|
|
|
self._legacy_enter_transaction_management(connection)
|
|
|
|
|
|
|
|
if not connection.in_atomic_block and not connection.autocommit:
|
|
|
|
raise TransactionManagementError(
|
|
|
|
"'atomic' cannot be used when autocommit is disabled.")
|
|
|
|
|
|
|
|
if connection.in_atomic_block:
|
|
|
|
# We're already in a transaction; create a savepoint.
|
|
|
|
sid = connection.savepoint()
|
|
|
|
connection.savepoint_ids.append(sid)
|
|
|
|
else:
|
|
|
|
# We aren't in a transaction yet; create one.
|
|
|
|
# The usual way to start a transaction is to turn autocommit off.
|
|
|
|
# However, some database adapters (namely sqlite3) don't handle
|
|
|
|
# transactions and savepoints properly when autocommit is off.
|
|
|
|
# In such cases, start an explicit transaction instead, which has
|
|
|
|
# the side-effect of disabling autocommit.
|
|
|
|
if connection.features.autocommits_when_autocommit_is_off:
|
|
|
|
connection._start_transaction_under_autocommit()
|
|
|
|
connection.autocommit = False
|
|
|
|
else:
|
|
|
|
connection.set_autocommit(False)
|
|
|
|
connection.in_atomic_block = True
|
|
|
|
connection.savepoint_ids.append(None)
|
|
|
|
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
|
|
connection = get_connection(self.using)
|
|
|
|
sid = connection.savepoint_ids.pop()
|
|
|
|
if exc_value is None:
|
|
|
|
if sid is None:
|
|
|
|
# Commit transaction
|
|
|
|
connection.in_atomic_block = False
|
|
|
|
try:
|
|
|
|
connection.commit()
|
|
|
|
except DatabaseError:
|
|
|
|
connection.rollback()
|
|
|
|
# Remove this when the legacy transaction management goes away.
|
|
|
|
self._legacy_leave_transaction_management(connection)
|
|
|
|
raise
|
|
|
|
finally:
|
|
|
|
if connection.features.autocommits_when_autocommit_is_off:
|
|
|
|
connection.autocommit = True
|
|
|
|
else:
|
|
|
|
connection.set_autocommit(True)
|
|
|
|
else:
|
|
|
|
# Release savepoint
|
|
|
|
try:
|
|
|
|
connection.savepoint_commit(sid)
|
|
|
|
except DatabaseError:
|
|
|
|
connection.savepoint_rollback(sid)
|
|
|
|
# Remove this when the legacy transaction management goes away.
|
|
|
|
self._legacy_leave_transaction_management(connection)
|
|
|
|
raise
|
|
|
|
else:
|
|
|
|
if sid is None:
|
|
|
|
# Roll back transaction
|
|
|
|
connection.in_atomic_block = False
|
|
|
|
try:
|
|
|
|
connection.rollback()
|
|
|
|
finally:
|
|
|
|
if connection.features.autocommits_when_autocommit_is_off:
|
|
|
|
connection.autocommit = True
|
|
|
|
else:
|
|
|
|
connection.set_autocommit(True)
|
|
|
|
else:
|
|
|
|
# Roll back to savepoint
|
|
|
|
connection.savepoint_rollback(sid)
|
|
|
|
|
|
|
|
# Remove this when the legacy transaction management goes away.
|
|
|
|
self._legacy_leave_transaction_management(connection)
|
|
|
|
|
|
|
|
|
|
|
|
def __call__(self, func):
|
|
|
|
@wraps(func)
|
|
|
|
def inner(*args, **kwargs):
|
|
|
|
with self:
|
|
|
|
return func(*args, **kwargs)
|
|
|
|
return inner
|
|
|
|
|
|
|
|
|
|
|
|
def atomic(using=None):
|
|
|
|
# Bare decorator: @atomic -- although the first argument is called
|
|
|
|
# `using`, it's actually the function being decorated.
|
|
|
|
if callable(using):
|
|
|
|
return Atomic(DEFAULT_DB_ALIAS)(using)
|
|
|
|
# Decorator: @atomic(...) or context manager: with atomic(...): ...
|
|
|
|
else:
|
|
|
|
return Atomic(using)
|
|
|
|
|
|
|
|
|
|
|
|
############################################
|
|
|
|
# Deprecated decorators / context managers #
|
|
|
|
############################################
|
2006-05-02 09:31:56 +08:00
|
|
|
|
2010-10-20 03:38:15 +08:00
|
|
|
class Transaction(object):
|
2006-05-02 09:31:56 +08:00
|
|
|
"""
|
2010-10-20 03:38:15 +08:00
|
|
|
Acts as either a decorator, or a context manager. If it's a decorator it
|
|
|
|
takes a function and returns a wrapped function. If it's a contextmanager
|
|
|
|
it's used with the ``with`` statement. In either event entering/exiting
|
|
|
|
are called before and after, respectively, the function/block is executed.
|
|
|
|
|
|
|
|
autocommit, commit_on_success, and commit_manually contain the
|
|
|
|
implementations of entering and exiting.
|
2006-05-02 09:31:56 +08:00
|
|
|
"""
|
2010-10-20 03:38:15 +08:00
|
|
|
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):
|
2011-03-28 10:11:19 +08:00
|
|
|
with self:
|
2011-03-28 22:22:48 +08:00
|
|
|
return func(*args, **kwargs)
|
2010-10-20 03:38:15 +08:00
|
|
|
return inner
|
2010-03-10 21:13:57 +08:00
|
|
|
|
2010-10-20 03:38:15 +08:00
|
|
|
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.
|
|
|
|
"""
|
2010-03-10 21:13:57 +08:00
|
|
|
# Note that although the first argument is *called* `using`, it
|
|
|
|
# may actually be a function; @autocommit and @autocommit('foo')
|
|
|
|
# are both allowed forms.
|
|
|
|
if using is None:
|
|
|
|
using = DEFAULT_DB_ALIAS
|
|
|
|
if callable(using):
|
2010-10-20 03:38:15 +08:00
|
|
|
return Transaction(entering, exiting, DEFAULT_DB_ALIAS)(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.
|
|
|
|
"""
|
2013-03-05 06:26:31 +08:00
|
|
|
warnings.warn("autocommit is deprecated in favor of set_autocommit.",
|
|
|
|
PendingDeprecationWarning, stacklevel=2)
|
|
|
|
|
2010-10-20 03:38:15 +08:00
|
|
|
def entering(using):
|
|
|
|
enter_transaction_management(managed=False, using=using)
|
2006-05-02 09:31:56 +08:00
|
|
|
|
2010-10-20 03:38:15 +08:00
|
|
|
def exiting(exc_value, using):
|
|
|
|
leave_transaction_management(using=using)
|
|
|
|
|
|
|
|
return _transaction_func(entering, exiting, using)
|
2009-12-22 23:18:51 +08:00
|
|
|
|
2010-03-10 21:13:57 +08:00
|
|
|
def commit_on_success(using=None):
|
2006-05-02 09:31:56 +08:00
|
|
|
"""
|
|
|
|
This decorator activates commit on response. This way, if the view function
|
|
|
|
runs successfully, a commit is made; if the viewfunc produces an exception,
|
|
|
|
a rollback is made. This is one of the most common ways to do transaction
|
2010-10-09 16:12:50 +08:00
|
|
|
control in Web apps.
|
2006-05-02 09:31:56 +08:00
|
|
|
"""
|
2013-03-05 06:26:31 +08:00
|
|
|
warnings.warn("commit_on_success is deprecated in favor of atomic.",
|
|
|
|
PendingDeprecationWarning, stacklevel=2)
|
|
|
|
|
2010-10-20 03:38:15 +08:00
|
|
|
def entering(using):
|
|
|
|
enter_transaction_management(using=using)
|
|
|
|
|
|
|
|
def exiting(exc_value, using):
|
2010-10-25 01:25:49 +08:00
|
|
|
try:
|
|
|
|
if exc_value is not None:
|
|
|
|
if is_dirty(using=using):
|
2010-10-20 03:38:15 +08:00
|
|
|
rollback(using=using)
|
2010-10-25 01:25:49 +08:00
|
|
|
else:
|
|
|
|
if is_dirty(using=using):
|
|
|
|
try:
|
|
|
|
commit(using=using)
|
|
|
|
except:
|
|
|
|
rollback(using=using)
|
|
|
|
raise
|
|
|
|
finally:
|
|
|
|
leave_transaction_management(using=using)
|
2006-05-02 09:31:56 +08:00
|
|
|
|
2010-10-20 03:38:15 +08:00
|
|
|
return _transaction_func(entering, exiting, using)
|
2010-03-10 21:13:57 +08:00
|
|
|
|
|
|
|
def commit_manually(using=None):
|
2006-05-02 09:31:56 +08:00
|
|
|
"""
|
|
|
|
Decorator that activates manual transaction control. It just disables
|
|
|
|
automatic transaction control and doesn't do any commit/rollback of its
|
|
|
|
own -- it's up to the user to call the commit and rollback functions
|
|
|
|
themselves.
|
|
|
|
"""
|
2013-03-05 06:26:31 +08:00
|
|
|
warnings.warn("commit_manually is deprecated in favor of set_autocommit.",
|
|
|
|
PendingDeprecationWarning, stacklevel=2)
|
|
|
|
|
2010-10-20 03:38:15 +08:00
|
|
|
def entering(using):
|
|
|
|
enter_transaction_management(using=using)
|
2006-05-02 09:31:56 +08:00
|
|
|
|
2010-10-20 03:38:15 +08:00
|
|
|
def exiting(exc_value, using):
|
|
|
|
leave_transaction_management(using=using)
|
2010-03-10 21:13:57 +08:00
|
|
|
|
2010-10-20 03:38:15 +08:00
|
|
|
return _transaction_func(entering, exiting, using)
|
2013-03-04 20:12:59 +08:00
|
|
|
|
|
|
|
def commit_on_success_unless_managed(using=None):
|
|
|
|
"""
|
|
|
|
Transitory API to preserve backwards-compatibility while refactoring.
|
2013-03-05 06:26:31 +08:00
|
|
|
|
|
|
|
Once the legacy transaction management is fully deprecated, this should
|
|
|
|
simply be replaced by atomic. Until then, it's necessary to avoid making a
|
|
|
|
commit where Django didn't use to, since entering atomic in managed mode
|
|
|
|
triggers a commmit.
|
2013-03-04 20:12:59 +08:00
|
|
|
"""
|
2013-03-05 05:17:35 +08:00
|
|
|
connection = get_connection(using)
|
2013-03-05 06:26:31 +08:00
|
|
|
if connection.autocommit or connection.in_atomic_block:
|
|
|
|
return atomic(using)
|
2013-03-04 22:24:01 +08:00
|
|
|
else:
|
2013-03-04 20:12:59 +08:00
|
|
|
def entering(using):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def exiting(exc_value, using):
|
|
|
|
set_dirty(using=using)
|
|
|
|
|
|
|
|
return _transaction_func(entering, exiting, using)
|