mirror of
https://github.com/django/django.git
synced 2024-12-22 17:16:24 +00:00
Removed legacy transaction management per the deprecation timeline.
This commit is contained in:
parent
907ac64641
commit
0f9560855e
@ -500,7 +500,7 @@ def create_generic_related_manager(superclass):
|
|||||||
if bulk:
|
if bulk:
|
||||||
queryset.delete()
|
queryset.delete()
|
||||||
else:
|
else:
|
||||||
with transaction.commit_on_success_unless_managed(using=db, savepoint=False):
|
with transaction.atomic(using=db, savepoint=False):
|
||||||
for obj in queryset:
|
for obj in queryset:
|
||||||
obj.delete()
|
obj.delete()
|
||||||
_clear.alters_data = True
|
_clear.alters_data = True
|
||||||
|
@ -71,7 +71,7 @@ class Command(BaseCommand):
|
|||||||
for i, line in enumerate(table_output):
|
for i, line in enumerate(table_output):
|
||||||
full_statement.append(' %s%s' % (line, ',' if i < len(table_output) - 1 else ''))
|
full_statement.append(' %s%s' % (line, ',' if i < len(table_output) - 1 else ''))
|
||||||
full_statement.append(');')
|
full_statement.append(');')
|
||||||
with transaction.commit_on_success_unless_managed():
|
with transaction.atomic():
|
||||||
with connection.cursor() as curs:
|
with connection.cursor() as curs:
|
||||||
try:
|
try:
|
||||||
curs.execute("\n".join(full_statement))
|
curs.execute("\n".join(full_statement))
|
||||||
|
@ -63,7 +63,7 @@ Are you sure you want to do this?
|
|||||||
|
|
||||||
if confirm == 'yes':
|
if confirm == 'yes':
|
||||||
try:
|
try:
|
||||||
with transaction.commit_on_success_unless_managed():
|
with transaction.atomic():
|
||||||
with connection.cursor() as cursor:
|
with connection.cursor() as cursor:
|
||||||
for sql in sql_list:
|
for sql in sql_list:
|
||||||
cursor.execute(sql)
|
cursor.execute(sql)
|
||||||
|
@ -56,7 +56,7 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
self.verbosity = int(options.get('verbosity'))
|
self.verbosity = int(options.get('verbosity'))
|
||||||
|
|
||||||
with transaction.commit_on_success_unless_managed(using=self.using):
|
with transaction.atomic(using=self.using):
|
||||||
self.loaddata(fixture_labels)
|
self.loaddata(fixture_labels)
|
||||||
|
|
||||||
# Close the DB connection -- unless we're still in a transaction. This
|
# Close the DB connection -- unless we're still in a transaction. This
|
||||||
|
@ -223,10 +223,6 @@ class Command(BaseCommand):
|
|||||||
for statement in sql:
|
for statement in sql:
|
||||||
cursor.execute(statement)
|
cursor.execute(statement)
|
||||||
tables.append(connection.introspection.table_name_converter(model._meta.db_table))
|
tables.append(connection.introspection.table_name_converter(model._meta.db_table))
|
||||||
|
|
||||||
# We force a commit here, as that was the previous behavior.
|
|
||||||
# If you can prove we don't need this, remove it.
|
|
||||||
transaction.set_dirty(using=connection.alias)
|
|
||||||
finally:
|
finally:
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
@ -245,7 +241,7 @@ class Command(BaseCommand):
|
|||||||
if self.verbosity >= 2:
|
if self.verbosity >= 2:
|
||||||
self.stdout.write(" Installing custom SQL for %s.%s model\n" % (app_name, model._meta.object_name))
|
self.stdout.write(" Installing custom SQL for %s.%s model\n" % (app_name, model._meta.object_name))
|
||||||
try:
|
try:
|
||||||
with transaction.commit_on_success_unless_managed(using=connection.alias):
|
with transaction.atomic(using=connection.alias):
|
||||||
for sql in custom_sql:
|
for sql in custom_sql:
|
||||||
cursor.execute(sql)
|
cursor.execute(sql)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -268,7 +264,7 @@ class Command(BaseCommand):
|
|||||||
if self.verbosity >= 2:
|
if self.verbosity >= 2:
|
||||||
self.stdout.write(" Installing index for %s.%s model\n" % (app_name, model._meta.object_name))
|
self.stdout.write(" Installing index for %s.%s model\n" % (app_name, model._meta.object_name))
|
||||||
try:
|
try:
|
||||||
with transaction.commit_on_success_unless_managed(using=connection.alias):
|
with transaction.atomic(using=connection.alias):
|
||||||
for sql in index_sql:
|
for sql in index_sql:
|
||||||
cursor.execute(sql)
|
cursor.execute(sql)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -77,20 +77,6 @@ class DefaultBackendProxy(object):
|
|||||||
backend = DefaultBackendProxy()
|
backend = DefaultBackendProxy()
|
||||||
|
|
||||||
|
|
||||||
def close_connection(**kwargs):
|
|
||||||
warnings.warn(
|
|
||||||
"close_connection is superseded by close_old_connections.",
|
|
||||||
RemovedInDjango18Warning, stacklevel=2)
|
|
||||||
# Avoid circular imports
|
|
||||||
from django.db import transaction
|
|
||||||
for conn in connections:
|
|
||||||
# If an error happens here the connection will be left in broken
|
|
||||||
# state. Once a good db connection is again available, the
|
|
||||||
# connection state will be cleaned up.
|
|
||||||
transaction.abort(conn)
|
|
||||||
connections[conn].close()
|
|
||||||
|
|
||||||
|
|
||||||
# Register an event to reset saved queries when a Django request is started.
|
# Register an event to reset saved queries when a Django request is started.
|
||||||
def reset_queries(**kwargs):
|
def reset_queries(**kwargs):
|
||||||
for conn in connections.all():
|
for conn in connections.all():
|
||||||
@ -99,14 +85,9 @@ signals.request_started.connect(reset_queries)
|
|||||||
|
|
||||||
|
|
||||||
# Register an event to reset transaction state and close connections past
|
# Register an event to reset transaction state and close connections past
|
||||||
# their lifetime. NB: abort() doesn't do anything outside of a transaction.
|
# their lifetime.
|
||||||
def close_old_connections(**kwargs):
|
def close_old_connections(**kwargs):
|
||||||
for conn in connections.all():
|
for conn in connections.all():
|
||||||
# Remove this when the legacy transaction management goes away.
|
|
||||||
try:
|
|
||||||
conn.abort()
|
|
||||||
except DatabaseError:
|
|
||||||
pass
|
|
||||||
conn.close_if_unusable_or_obsolete()
|
conn.close_if_unusable_or_obsolete()
|
||||||
signals.request_started.connect(close_old_connections)
|
signals.request_started.connect(close_old_connections)
|
||||||
signals.request_finished.connect(close_old_connections)
|
signals.request_finished.connect(close_old_connections)
|
||||||
|
@ -30,28 +30,25 @@ class BaseDatabaseWrapper(object):
|
|||||||
|
|
||||||
def __init__(self, settings_dict, alias=DEFAULT_DB_ALIAS,
|
def __init__(self, settings_dict, alias=DEFAULT_DB_ALIAS,
|
||||||
allow_thread_sharing=False):
|
allow_thread_sharing=False):
|
||||||
|
# Connection related attributes.
|
||||||
|
self.connection = None
|
||||||
|
self.queries = []
|
||||||
# `settings_dict` should be a dictionary containing keys such as
|
# `settings_dict` should be a dictionary containing keys such as
|
||||||
# NAME, USER, etc. It's called `settings_dict` instead of `settings`
|
# NAME, USER, etc. It's called `settings_dict` instead of `settings`
|
||||||
# to disambiguate it from Django settings modules.
|
# to disambiguate it from Django settings modules.
|
||||||
self.connection = None
|
|
||||||
self.queries = []
|
|
||||||
self.settings_dict = settings_dict
|
self.settings_dict = settings_dict
|
||||||
self.alias = alias
|
self.alias = alias
|
||||||
self.use_debug_cursor = None
|
self.use_debug_cursor = None
|
||||||
|
|
||||||
# Savepoint management related attributes
|
# Transaction related attributes.
|
||||||
self.savepoint_state = 0
|
# Tracks if the connection is in autocommit mode. Per PEP 249, by
|
||||||
|
# default, it isn't.
|
||||||
# Transaction management related attributes
|
|
||||||
self.autocommit = False
|
self.autocommit = False
|
||||||
self.transaction_state = []
|
|
||||||
# Tracks if the connection is believed to be in transaction. This is
|
|
||||||
# set somewhat aggressively, as the DBAPI doesn't make it easy to
|
|
||||||
# deduce if the connection is in transaction or not.
|
|
||||||
self._dirty = False
|
|
||||||
# Tracks if the connection is in a transaction managed by 'atomic'.
|
# Tracks if the connection is in a transaction managed by 'atomic'.
|
||||||
self.in_atomic_block = False
|
self.in_atomic_block = False
|
||||||
# List of savepoints created by 'atomic'
|
# Increment to generate unique savepoint ids.
|
||||||
|
self.savepoint_state = 0
|
||||||
|
# List of savepoints created by 'atomic'.
|
||||||
self.savepoint_ids = []
|
self.savepoint_ids = []
|
||||||
# Tracks if the outermost 'atomic' block should commit on exit,
|
# Tracks if the outermost 'atomic' block should commit on exit,
|
||||||
# ie. if autocommit was active on entry.
|
# ie. if autocommit was active on entry.
|
||||||
@ -60,11 +57,11 @@ class BaseDatabaseWrapper(object):
|
|||||||
# available savepoint because of an exception in an inner block.
|
# available savepoint because of an exception in an inner block.
|
||||||
self.needs_rollback = False
|
self.needs_rollback = False
|
||||||
|
|
||||||
# Connection termination related attributes
|
# Connection termination related attributes.
|
||||||
self.close_at = None
|
self.close_at = None
|
||||||
self.errors_occurred = False
|
self.errors_occurred = False
|
||||||
|
|
||||||
# Thread-safety related attributes
|
# Thread-safety related attributes.
|
||||||
self.allow_thread_sharing = allow_thread_sharing
|
self.allow_thread_sharing = allow_thread_sharing
|
||||||
self._thread_ident = thread.get_ident()
|
self._thread_ident = thread.get_ident()
|
||||||
|
|
||||||
@ -166,7 +163,6 @@ class BaseDatabaseWrapper(object):
|
|||||||
self.validate_thread_sharing()
|
self.validate_thread_sharing()
|
||||||
self.validate_no_atomic_block()
|
self.validate_no_atomic_block()
|
||||||
self._commit()
|
self._commit()
|
||||||
self.set_clean()
|
|
||||||
|
|
||||||
def rollback(self):
|
def rollback(self):
|
||||||
"""
|
"""
|
||||||
@ -175,7 +171,6 @@ class BaseDatabaseWrapper(object):
|
|||||||
self.validate_thread_sharing()
|
self.validate_thread_sharing()
|
||||||
self.validate_no_atomic_block()
|
self.validate_no_atomic_block()
|
||||||
self._rollback()
|
self._rollback()
|
||||||
self.set_clean()
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""
|
"""
|
||||||
@ -189,7 +184,6 @@ class BaseDatabaseWrapper(object):
|
|||||||
self._close()
|
self._close()
|
||||||
finally:
|
finally:
|
||||||
self.connection = None
|
self.connection = None
|
||||||
self.set_clean()
|
|
||||||
|
|
||||||
##### Backend-specific savepoint management methods #####
|
##### Backend-specific savepoint management methods #####
|
||||||
|
|
||||||
@ -267,59 +261,6 @@ class BaseDatabaseWrapper(object):
|
|||||||
|
|
||||||
##### Generic transaction management methods #####
|
##### Generic transaction management methods #####
|
||||||
|
|
||||||
def enter_transaction_management(self, managed=True, forced=False):
|
|
||||||
"""
|
|
||||||
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).
|
|
||||||
|
|
||||||
If you switch off transaction management and there is a pending
|
|
||||||
commit/rollback, the data will be committed, unless "forced" is True.
|
|
||||||
"""
|
|
||||||
self.validate_no_atomic_block()
|
|
||||||
|
|
||||||
self.transaction_state.append(managed)
|
|
||||||
|
|
||||||
if not managed and self.is_dirty() and not forced:
|
|
||||||
self.commit()
|
|
||||||
self.set_clean()
|
|
||||||
|
|
||||||
if managed == self.get_autocommit():
|
|
||||||
self.set_autocommit(not managed)
|
|
||||||
|
|
||||||
def leave_transaction_management(self):
|
|
||||||
"""
|
|
||||||
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.)
|
|
||||||
"""
|
|
||||||
self.validate_no_atomic_block()
|
|
||||||
|
|
||||||
if self.transaction_state:
|
|
||||||
del self.transaction_state[-1]
|
|
||||||
else:
|
|
||||||
raise TransactionManagementError(
|
|
||||||
"This code isn't under transaction management")
|
|
||||||
|
|
||||||
if self.transaction_state:
|
|
||||||
managed = self.transaction_state[-1]
|
|
||||||
else:
|
|
||||||
managed = not self.settings_dict['AUTOCOMMIT']
|
|
||||||
|
|
||||||
if self._dirty:
|
|
||||||
self.rollback()
|
|
||||||
if managed == self.get_autocommit():
|
|
||||||
self.set_autocommit(not managed)
|
|
||||||
raise TransactionManagementError(
|
|
||||||
"Transaction managed block ended with pending COMMIT/ROLLBACK")
|
|
||||||
|
|
||||||
if managed == self.get_autocommit():
|
|
||||||
self.set_autocommit(not managed)
|
|
||||||
|
|
||||||
def get_autocommit(self):
|
def get_autocommit(self):
|
||||||
"""
|
"""
|
||||||
Check the autocommit state.
|
Check the autocommit state.
|
||||||
@ -368,41 +309,6 @@ class BaseDatabaseWrapper(object):
|
|||||||
"An error occurred in the current transaction. You can't "
|
"An error occurred in the current transaction. You can't "
|
||||||
"execute queries until the end of the 'atomic' block.")
|
"execute queries until the end of the 'atomic' block.")
|
||||||
|
|
||||||
def abort(self):
|
|
||||||
"""
|
|
||||||
Roll back any ongoing transaction and clean the transaction state
|
|
||||||
stack.
|
|
||||||
"""
|
|
||||||
if self._dirty:
|
|
||||||
self.rollback()
|
|
||||||
while self.transaction_state:
|
|
||||||
self.leave_transaction_management()
|
|
||||||
|
|
||||||
def is_dirty(self):
|
|
||||||
"""
|
|
||||||
Returns True if the current transaction requires a commit for changes to
|
|
||||||
happen.
|
|
||||||
"""
|
|
||||||
return self._dirty
|
|
||||||
|
|
||||||
def set_dirty(self):
|
|
||||||
"""
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
if not self.get_autocommit():
|
|
||||||
self._dirty = True
|
|
||||||
|
|
||||||
def set_clean(self):
|
|
||||||
"""
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
self._dirty = False
|
|
||||||
self.clean_savepoints()
|
|
||||||
|
|
||||||
##### Foreign key constraints checks handling #####
|
##### Foreign key constraints checks handling #####
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
@ -576,10 +482,6 @@ class BaseDatabaseFeatures(object):
|
|||||||
# at the end of each save operation?
|
# at the end of each save operation?
|
||||||
supports_forward_references = True
|
supports_forward_references = True
|
||||||
|
|
||||||
# Does a dirty transaction need to be rolled back
|
|
||||||
# before the cursor can be used again?
|
|
||||||
requires_rollback_on_dirty_transaction = False
|
|
||||||
|
|
||||||
# Does the backend allow very long model names without error?
|
# Does the backend allow very long model names without error?
|
||||||
supports_long_model_names = True
|
supports_long_model_names = True
|
||||||
|
|
||||||
@ -682,28 +584,21 @@ class BaseDatabaseFeatures(object):
|
|||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def supports_transactions(self):
|
def supports_transactions(self):
|
||||||
"Confirm support for transactions"
|
"""Confirm support for transactions."""
|
||||||
try:
|
with self.connection.cursor() as cursor:
|
||||||
# Make sure to run inside a managed transaction block,
|
cursor.execute('CREATE TABLE ROLLBACK_TEST (X INT)')
|
||||||
# otherwise autocommit will cause the confimation to
|
self.connection.set_autocommit(False)
|
||||||
# fail.
|
cursor.execute('INSERT INTO ROLLBACK_TEST (X) VALUES (8)')
|
||||||
self.connection.enter_transaction_management()
|
self.connection.rollback()
|
||||||
with self.connection.cursor() as cursor:
|
self.connection.set_autocommit(True)
|
||||||
cursor.execute('CREATE TABLE ROLLBACK_TEST (X INT)')
|
cursor.execute('SELECT COUNT(X) FROM ROLLBACK_TEST')
|
||||||
self.connection.commit()
|
count, = cursor.fetchone()
|
||||||
cursor.execute('INSERT INTO ROLLBACK_TEST (X) VALUES (8)')
|
cursor.execute('DROP TABLE ROLLBACK_TEST')
|
||||||
self.connection.rollback()
|
|
||||||
cursor.execute('SELECT COUNT(X) FROM ROLLBACK_TEST')
|
|
||||||
count, = cursor.fetchone()
|
|
||||||
cursor.execute('DROP TABLE ROLLBACK_TEST')
|
|
||||||
self.connection.commit()
|
|
||||||
finally:
|
|
||||||
self.connection.leave_transaction_management()
|
|
||||||
return count == 0
|
return count == 0
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def supports_stddev(self):
|
def supports_stddev(self):
|
||||||
"Confirm support for STDDEV and related stats functions"
|
"""Confirm support for STDDEV and related stats functions."""
|
||||||
class StdDevPop(object):
|
class StdDevPop(object):
|
||||||
sql_function = 'STDDEV_POP'
|
sql_function = 'STDDEV_POP'
|
||||||
|
|
||||||
|
@ -67,8 +67,6 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|||||||
_savepoint_commit = complain
|
_savepoint_commit = complain
|
||||||
_savepoint_rollback = ignore
|
_savepoint_rollback = ignore
|
||||||
_set_autocommit = complain
|
_set_autocommit = complain
|
||||||
set_dirty = complain
|
|
||||||
set_clean = complain
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(DatabaseWrapper, self).__init__(*args, **kwargs)
|
super(DatabaseWrapper, self).__init__(*args, **kwargs)
|
||||||
|
@ -48,7 +48,6 @@ def utc_tzinfo_factory(offset):
|
|||||||
class DatabaseFeatures(BaseDatabaseFeatures):
|
class DatabaseFeatures(BaseDatabaseFeatures):
|
||||||
needs_datetime_string_cast = False
|
needs_datetime_string_cast = False
|
||||||
can_return_id_from_insert = True
|
can_return_id_from_insert = True
|
||||||
requires_rollback_on_dirty_transaction = True
|
|
||||||
has_real_datatype = True
|
has_real_datatype = True
|
||||||
can_defer_constraint_checks = True
|
can_defer_constraint_checks = True
|
||||||
has_select_for_update = True
|
has_select_for_update = True
|
||||||
@ -181,8 +180,6 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|||||||
exc_info=sys.exc_info()
|
exc_info=sys.exc_info()
|
||||||
)
|
)
|
||||||
raise
|
raise
|
||||||
finally:
|
|
||||||
self.set_clean()
|
|
||||||
|
|
||||||
def _set_isolation_level(self, isolation_level):
|
def _set_isolation_level(self, isolation_level):
|
||||||
assert isolation_level in range(1, 5) # Use set_autocommit for level = 0
|
assert isolation_level in range(1, 5) # Use set_autocommit for level = 0
|
||||||
|
@ -44,7 +44,6 @@ class CursorWrapper(object):
|
|||||||
|
|
||||||
def callproc(self, procname, params=None):
|
def callproc(self, procname, params=None):
|
||||||
self.db.validate_no_broken_transaction()
|
self.db.validate_no_broken_transaction()
|
||||||
self.db.set_dirty()
|
|
||||||
with self.db.wrap_database_errors:
|
with self.db.wrap_database_errors:
|
||||||
if params is None:
|
if params is None:
|
||||||
return self.cursor.callproc(procname)
|
return self.cursor.callproc(procname)
|
||||||
@ -53,7 +52,6 @@ class CursorWrapper(object):
|
|||||||
|
|
||||||
def execute(self, sql, params=None):
|
def execute(self, sql, params=None):
|
||||||
self.db.validate_no_broken_transaction()
|
self.db.validate_no_broken_transaction()
|
||||||
self.db.set_dirty()
|
|
||||||
with self.db.wrap_database_errors:
|
with self.db.wrap_database_errors:
|
||||||
if params is None:
|
if params is None:
|
||||||
return self.cursor.execute(sql)
|
return self.cursor.execute(sql)
|
||||||
@ -62,7 +60,6 @@ class CursorWrapper(object):
|
|||||||
|
|
||||||
def executemany(self, sql, param_list):
|
def executemany(self, sql, param_list):
|
||||||
self.db.validate_no_broken_transaction()
|
self.db.validate_no_broken_transaction()
|
||||||
self.db.set_dirty()
|
|
||||||
with self.db.wrap_database_errors:
|
with self.db.wrap_database_errors:
|
||||||
return self.cursor.executemany(sql, param_list)
|
return self.cursor.executemany(sql, param_list)
|
||||||
|
|
||||||
|
@ -625,7 +625,7 @@ class Model(six.with_metaclass(ModelBase)):
|
|||||||
if not meta.auto_created:
|
if not meta.auto_created:
|
||||||
signals.pre_save.send(sender=origin, instance=self, raw=raw, using=using,
|
signals.pre_save.send(sender=origin, instance=self, raw=raw, using=using,
|
||||||
update_fields=update_fields)
|
update_fields=update_fields)
|
||||||
with transaction.commit_on_success_unless_managed(using=using, savepoint=False):
|
with transaction.atomic(using=using, savepoint=False):
|
||||||
if not raw:
|
if not raw:
|
||||||
self._save_parents(cls, using, update_fields)
|
self._save_parents(cls, using, update_fields)
|
||||||
updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
|
updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
|
||||||
@ -1417,7 +1417,7 @@ def method_set_order(ordered_obj, self, id_list, using=None):
|
|||||||
order_name = ordered_obj._meta.order_with_respect_to.name
|
order_name = ordered_obj._meta.order_with_respect_to.name
|
||||||
# FIXME: It would be nice if there was an "update many" version of update
|
# FIXME: It would be nice if there was an "update many" version of update
|
||||||
# for situations like this.
|
# for situations like this.
|
||||||
with transaction.commit_on_success_unless_managed(using=using):
|
with transaction.atomic(using=using, savepoint=False):
|
||||||
for i, j in enumerate(id_list):
|
for i, j in enumerate(id_list):
|
||||||
ordered_obj.objects.filter(**{'pk': j, order_name: rel_val}).update(_order=i)
|
ordered_obj.objects.filter(**{'pk': j, order_name: rel_val}).update(_order=i)
|
||||||
|
|
||||||
|
@ -249,7 +249,7 @@ class Collector(object):
|
|||||||
# end of a transaction.
|
# end of a transaction.
|
||||||
self.sort()
|
self.sort()
|
||||||
|
|
||||||
with transaction.commit_on_success_unless_managed(using=self.using):
|
with transaction.atomic(using=self.using, savepoint=False):
|
||||||
# send pre_delete signals
|
# send pre_delete signals
|
||||||
for model, obj in self.instances_with_model():
|
for model, obj in self.instances_with_model():
|
||||||
if not model._meta.auto_created:
|
if not model._meta.auto_created:
|
||||||
|
@ -686,8 +686,7 @@ def create_foreign_related_manager(superclass, rel_field, rel_model):
|
|||||||
def add(self, *objs):
|
def add(self, *objs):
|
||||||
objs = list(objs)
|
objs = list(objs)
|
||||||
db = router.db_for_write(self.model, instance=self.instance)
|
db = router.db_for_write(self.model, instance=self.instance)
|
||||||
with transaction.commit_on_success_unless_managed(
|
with transaction.atomic(using=db, savepoint=False):
|
||||||
using=db, savepoint=False):
|
|
||||||
for obj in objs:
|
for obj in objs:
|
||||||
if not isinstance(obj, self.model):
|
if not isinstance(obj, self.model):
|
||||||
raise TypeError("'%s' instance expected, got %r" %
|
raise TypeError("'%s' instance expected, got %r" %
|
||||||
@ -738,7 +737,7 @@ def create_foreign_related_manager(superclass, rel_field, rel_model):
|
|||||||
if bulk:
|
if bulk:
|
||||||
queryset.update(**{rel_field.name: None})
|
queryset.update(**{rel_field.name: None})
|
||||||
else:
|
else:
|
||||||
with transaction.commit_on_success_unless_managed(using=db, savepoint=False):
|
with transaction.atomic(using=db, savepoint=False):
|
||||||
for obj in queryset:
|
for obj in queryset:
|
||||||
setattr(obj, rel_field.name, None)
|
setattr(obj, rel_field.name, None)
|
||||||
obj.save(update_fields=[rel_field.name])
|
obj.save(update_fields=[rel_field.name])
|
||||||
|
@ -391,7 +391,7 @@ class QuerySet(object):
|
|||||||
self._for_write = True
|
self._for_write = True
|
||||||
connection = connections[self.db]
|
connection = connections[self.db]
|
||||||
fields = self.model._meta.local_concrete_fields
|
fields = self.model._meta.local_concrete_fields
|
||||||
with transaction.commit_on_success_unless_managed(using=self.db):
|
with transaction.atomic(using=self.db, savepoint=False):
|
||||||
if (connection.features.can_combine_inserts_with_and_without_auto_increment_pk
|
if (connection.features.can_combine_inserts_with_and_without_auto_increment_pk
|
||||||
and self.model._meta.has_auto_field):
|
and self.model._meta.has_auto_field):
|
||||||
self._batched_insert(objs, fields, batch_size)
|
self._batched_insert(objs, fields, batch_size)
|
||||||
@ -437,7 +437,7 @@ class QuerySet(object):
|
|||||||
for k, v in six.iteritems(defaults):
|
for k, v in six.iteritems(defaults):
|
||||||
setattr(obj, k, v)
|
setattr(obj, k, v)
|
||||||
|
|
||||||
with transaction.atomic(using=self.db):
|
with transaction.atomic(using=self.db, savepoint=False):
|
||||||
obj.save(using=self.db)
|
obj.save(using=self.db)
|
||||||
return obj, False
|
return obj, False
|
||||||
|
|
||||||
@ -574,7 +574,7 @@ class QuerySet(object):
|
|||||||
self._for_write = True
|
self._for_write = True
|
||||||
query = self.query.clone(sql.UpdateQuery)
|
query = self.query.clone(sql.UpdateQuery)
|
||||||
query.add_update_values(kwargs)
|
query.add_update_values(kwargs)
|
||||||
with transaction.commit_on_success_unless_managed(using=self.db):
|
with transaction.atomic(using=self.db, savepoint=False):
|
||||||
rows = query.get_compiler(self.db).execute_sql(CURSOR)
|
rows = query.get_compiler(self.db).execute_sql(CURSOR)
|
||||||
self._result_cache = None
|
self._result_cache = None
|
||||||
return rows
|
return rows
|
||||||
|
@ -1,26 +1,9 @@
|
|||||||
"""
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
from django.db import (
|
from django.db import (
|
||||||
connections, DEFAULT_DB_ALIAS,
|
connections, DEFAULT_DB_ALIAS,
|
||||||
DatabaseError, ProgrammingError)
|
DatabaseError, ProgrammingError)
|
||||||
from django.utils.decorators import available_attrs
|
from django.utils.decorators import available_attrs
|
||||||
from django.utils.deprecation import RemovedInDjango18Warning
|
|
||||||
|
|
||||||
|
|
||||||
class TransactionManagementError(ProgrammingError):
|
class TransactionManagementError(ProgrammingError):
|
||||||
@ -30,109 +13,16 @@ class TransactionManagementError(ProgrammingError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
################
|
|
||||||
# Private APIs #
|
|
||||||
################
|
|
||||||
|
|
||||||
def get_connection(using=None):
|
def get_connection(using=None):
|
||||||
"""
|
"""
|
||||||
Get a database connection by name, or the default database connection
|
Get a database connection by name, or the default database connection
|
||||||
if no name is provided.
|
if no name is provided. This is a private API.
|
||||||
"""
|
"""
|
||||||
if using is None:
|
if using is None:
|
||||||
using = DEFAULT_DB_ALIAS
|
using = DEFAULT_DB_ALIAS
|
||||||
return connections[using]
|
return connections[using]
|
||||||
|
|
||||||
|
|
||||||
###########################
|
|
||||||
# Deprecated private APIs #
|
|
||||||
###########################
|
|
||||||
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
get_connection(using).abort()
|
|
||||||
|
|
||||||
|
|
||||||
def enter_transaction_management(managed=True, using=None, forced=False):
|
|
||||||
"""
|
|
||||||
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).
|
|
||||||
"""
|
|
||||||
get_connection(using).enter_transaction_management(managed, forced)
|
|
||||||
|
|
||||||
|
|
||||||
def leave_transaction_management(using=None):
|
|
||||||
"""
|
|
||||||
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.)
|
|
||||||
"""
|
|
||||||
get_connection(using).leave_transaction_management()
|
|
||||||
|
|
||||||
|
|
||||||
def is_dirty(using=None):
|
|
||||||
"""
|
|
||||||
Returns True if the current transaction requires a commit for changes to
|
|
||||||
happen.
|
|
||||||
"""
|
|
||||||
return get_connection(using).is_dirty()
|
|
||||||
|
|
||||||
|
|
||||||
def set_dirty(using=None):
|
|
||||||
"""
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
get_connection(using).set_dirty()
|
|
||||||
|
|
||||||
|
|
||||||
def set_clean(using=None):
|
|
||||||
"""
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
get_connection(using).set_clean()
|
|
||||||
|
|
||||||
|
|
||||||
def is_managed(using=None):
|
|
||||||
warnings.warn("'is_managed' is deprecated.",
|
|
||||||
RemovedInDjango18Warning, stacklevel=2)
|
|
||||||
|
|
||||||
|
|
||||||
def managed(flag=True, using=None):
|
|
||||||
warnings.warn("'managed' no longer serves a purpose.",
|
|
||||||
RemovedInDjango18Warning, stacklevel=2)
|
|
||||||
|
|
||||||
|
|
||||||
def commit_unless_managed(using=None):
|
|
||||||
warnings.warn("'commit_unless_managed' is now a no-op.",
|
|
||||||
RemovedInDjango18Warning, stacklevel=2)
|
|
||||||
|
|
||||||
|
|
||||||
def rollback_unless_managed(using=None):
|
|
||||||
warnings.warn("'rollback_unless_managed' is now a no-op.",
|
|
||||||
RemovedInDjango18Warning, stacklevel=2)
|
|
||||||
|
|
||||||
|
|
||||||
###############
|
|
||||||
# Public APIs #
|
|
||||||
###############
|
|
||||||
|
|
||||||
def get_autocommit(using=None):
|
def get_autocommit(using=None):
|
||||||
"""
|
"""
|
||||||
Get the autocommit status of the connection.
|
Get the autocommit status of the connection.
|
||||||
@ -149,14 +39,14 @@ def set_autocommit(autocommit, using=None):
|
|||||||
|
|
||||||
def commit(using=None):
|
def commit(using=None):
|
||||||
"""
|
"""
|
||||||
Commits a transaction and resets the dirty flag.
|
Commits a transaction.
|
||||||
"""
|
"""
|
||||||
get_connection(using).commit()
|
get_connection(using).commit()
|
||||||
|
|
||||||
|
|
||||||
def rollback(using=None):
|
def rollback(using=None):
|
||||||
"""
|
"""
|
||||||
Rolls back a transaction and resets the dirty flag.
|
Rolls back a transaction.
|
||||||
"""
|
"""
|
||||||
get_connection(using).rollback()
|
get_connection(using).rollback()
|
||||||
|
|
||||||
@ -244,6 +134,8 @@ class Atomic(object):
|
|||||||
`with oa:` multiple times.
|
`with oa:` multiple times.
|
||||||
|
|
||||||
Since database connections are thread-local, this is thread-safe.
|
Since database connections are thread-local, this is thread-safe.
|
||||||
|
|
||||||
|
This is a private API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, using, savepoint):
|
def __init__(self, using, savepoint):
|
||||||
@ -388,149 +280,3 @@ def non_atomic_requests(using=None):
|
|||||||
if using is None:
|
if using is None:
|
||||||
using = DEFAULT_DB_ALIAS
|
using = DEFAULT_DB_ALIAS
|
||||||
return lambda view: _non_atomic_requests(view, using)
|
return lambda view: _non_atomic_requests(view, using)
|
||||||
|
|
||||||
|
|
||||||
############################################
|
|
||||||
# Deprecated decorators / context managers #
|
|
||||||
############################################
|
|
||||||
|
|
||||||
class Transaction(object):
|
|
||||||
"""
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
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_type, self.using)
|
|
||||||
|
|
||||||
def __call__(self, func):
|
|
||||||
@wraps(func)
|
|
||||||
def inner(*args, **kwargs):
|
|
||||||
with self:
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
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, indicating 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
|
|
||||||
# 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 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.
|
|
||||||
"""
|
|
||||||
warnings.warn("autocommit is deprecated in favor of set_autocommit.",
|
|
||||||
RemovedInDjango18Warning, stacklevel=2)
|
|
||||||
|
|
||||||
def entering(using):
|
|
||||||
enter_transaction_management(managed=False, using=using)
|
|
||||||
|
|
||||||
def exiting(exc_type, using):
|
|
||||||
leave_transaction_management(using=using)
|
|
||||||
|
|
||||||
return _transaction_func(entering, exiting, using)
|
|
||||||
|
|
||||||
|
|
||||||
def commit_on_success(using=None):
|
|
||||||
"""
|
|
||||||
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
|
|
||||||
control in Web apps.
|
|
||||||
"""
|
|
||||||
warnings.warn("commit_on_success is deprecated in favor of atomic.",
|
|
||||||
RemovedInDjango18Warning, stacklevel=2)
|
|
||||||
|
|
||||||
def entering(using):
|
|
||||||
enter_transaction_management(using=using)
|
|
||||||
|
|
||||||
def exiting(exc_type, using):
|
|
||||||
try:
|
|
||||||
if exc_type is not None:
|
|
||||||
if is_dirty(using=using):
|
|
||||||
rollback(using=using)
|
|
||||||
else:
|
|
||||||
if is_dirty(using=using):
|
|
||||||
try:
|
|
||||||
commit(using=using)
|
|
||||||
except:
|
|
||||||
rollback(using=using)
|
|
||||||
raise
|
|
||||||
finally:
|
|
||||||
leave_transaction_management(using=using)
|
|
||||||
|
|
||||||
return _transaction_func(entering, exiting, using)
|
|
||||||
|
|
||||||
|
|
||||||
def commit_manually(using=None):
|
|
||||||
"""
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
warnings.warn("commit_manually is deprecated in favor of set_autocommit.",
|
|
||||||
RemovedInDjango18Warning, stacklevel=2)
|
|
||||||
|
|
||||||
def entering(using):
|
|
||||||
enter_transaction_management(using=using)
|
|
||||||
|
|
||||||
def exiting(exc_type, using):
|
|
||||||
leave_transaction_management(using=using)
|
|
||||||
|
|
||||||
return _transaction_func(entering, exiting, using)
|
|
||||||
|
|
||||||
|
|
||||||
def commit_on_success_unless_managed(using=None, savepoint=False):
|
|
||||||
"""
|
|
||||||
Transitory API to preserve backwards-compatibility while refactoring.
|
|
||||||
|
|
||||||
Once the legacy transaction management is fully deprecated, this should
|
|
||||||
simply be replaced by atomic. Until then, it's necessary to guarantee that
|
|
||||||
a commit occurs on exit, which atomic doesn't do when it's nested.
|
|
||||||
|
|
||||||
Unlike atomic, savepoint defaults to False because that's closer to the
|
|
||||||
legacy behavior.
|
|
||||||
"""
|
|
||||||
connection = get_connection(using)
|
|
||||||
if connection.get_autocommit() or connection.in_atomic_block:
|
|
||||||
return atomic(using, savepoint)
|
|
||||||
else:
|
|
||||||
def entering(using):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def exiting(exc_type, using):
|
|
||||||
set_dirty(using=using)
|
|
||||||
|
|
||||||
return _transaction_func(entering, exiting, using)
|
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
import warnings
|
|
||||||
|
|
||||||
from django.core.exceptions import MiddlewareNotUsed
|
|
||||||
from django.db import connection, transaction
|
|
||||||
from django.utils.deprecation import RemovedInDjango18Warning
|
|
||||||
|
|
||||||
|
|
||||||
class TransactionMiddleware(object):
|
|
||||||
"""
|
|
||||||
Transaction middleware. If this is enabled, each view function will be run
|
|
||||||
with commit_on_response activated - that way a save() doesn't do a direct
|
|
||||||
commit, the commit is done when a successful response is created. If an
|
|
||||||
exception happens, the database is rolled back.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
warnings.warn(
|
|
||||||
"TransactionMiddleware is deprecated in favor of ATOMIC_REQUESTS.",
|
|
||||||
RemovedInDjango18Warning, stacklevel=2)
|
|
||||||
if connection.settings_dict['ATOMIC_REQUESTS']:
|
|
||||||
raise MiddlewareNotUsed
|
|
||||||
|
|
||||||
def process_request(self, request):
|
|
||||||
"""Enters transaction management"""
|
|
||||||
transaction.enter_transaction_management()
|
|
||||||
|
|
||||||
def process_exception(self, request, exception):
|
|
||||||
"""Rolls back the database and leaves transaction management"""
|
|
||||||
if transaction.is_dirty():
|
|
||||||
# This rollback might fail because of network failure for example.
|
|
||||||
# If rollback isn't possible it is impossible to clean the
|
|
||||||
# connection's state. So leave the connection in dirty state and
|
|
||||||
# let request_finished signal deal with cleaning the connection.
|
|
||||||
transaction.rollback()
|
|
||||||
transaction.leave_transaction_management()
|
|
||||||
|
|
||||||
def process_response(self, request, response):
|
|
||||||
"""Commits and leaves transaction management."""
|
|
||||||
if not transaction.get_autocommit():
|
|
||||||
if transaction.is_dirty():
|
|
||||||
# Note: it is possible that the commit fails. If the reason is
|
|
||||||
# closed connection or some similar reason, then there is
|
|
||||||
# little hope to proceed nicely. However, in some cases (
|
|
||||||
# deferred foreign key checks for exampl) it is still possible
|
|
||||||
# to rollback().
|
|
||||||
try:
|
|
||||||
transaction.commit()
|
|
||||||
except Exception:
|
|
||||||
# If the rollback fails, the transaction state will be
|
|
||||||
# messed up. It doesn't matter, the connection will be set
|
|
||||||
# to clean state after the request finishes. And, we can't
|
|
||||||
# clean the state here properly even if we wanted to, the
|
|
||||||
# connection is in transaction but we can't rollback...
|
|
||||||
transaction.rollback()
|
|
||||||
transaction.leave_transaction_management()
|
|
||||||
raise
|
|
||||||
transaction.leave_transaction_management()
|
|
||||||
return response
|
|
@ -57,9 +57,6 @@ def to_list(value):
|
|||||||
|
|
||||||
real_commit = transaction.commit
|
real_commit = transaction.commit
|
||||||
real_rollback = transaction.rollback
|
real_rollback = transaction.rollback
|
||||||
real_enter_transaction_management = transaction.enter_transaction_management
|
|
||||||
real_leave_transaction_management = transaction.leave_transaction_management
|
|
||||||
real_abort = transaction.abort
|
|
||||||
|
|
||||||
|
|
||||||
def nop(*args, **kwargs):
|
def nop(*args, **kwargs):
|
||||||
@ -69,17 +66,11 @@ def nop(*args, **kwargs):
|
|||||||
def disable_transaction_methods():
|
def disable_transaction_methods():
|
||||||
transaction.commit = nop
|
transaction.commit = nop
|
||||||
transaction.rollback = nop
|
transaction.rollback = nop
|
||||||
transaction.enter_transaction_management = nop
|
|
||||||
transaction.leave_transaction_management = nop
|
|
||||||
transaction.abort = nop
|
|
||||||
|
|
||||||
|
|
||||||
def restore_transaction_methods():
|
def restore_transaction_methods():
|
||||||
transaction.commit = real_commit
|
transaction.commit = real_commit
|
||||||
transaction.rollback = real_rollback
|
transaction.rollback = real_rollback
|
||||||
transaction.enter_transaction_management = real_enter_transaction_management
|
|
||||||
transaction.leave_transaction_management = real_leave_transaction_management
|
|
||||||
transaction.abort = real_abort
|
|
||||||
|
|
||||||
|
|
||||||
def assert_and_parse_html(self, html, user_msg, msg):
|
def assert_and_parse_html(self, html, user_msg, msg):
|
||||||
@ -772,7 +763,7 @@ class TransactionTestCase(SimpleTestCase):
|
|||||||
sql_list = conn.ops.sequence_reset_by_name_sql(
|
sql_list = conn.ops.sequence_reset_by_name_sql(
|
||||||
no_style(), conn.introspection.sequence_list())
|
no_style(), conn.introspection.sequence_list())
|
||||||
if sql_list:
|
if sql_list:
|
||||||
with transaction.commit_on_success_unless_managed(using=db_name):
|
with transaction.atomic(using=db_name):
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
for sql in sql_list:
|
for sql in sql_list:
|
||||||
cursor.execute(sql)
|
cursor.execute(sql)
|
||||||
|
@ -371,7 +371,7 @@ class PostgresNewConnectionTests(TestCase):
|
|||||||
# Fetch a new connection with the new_tz as default
|
# Fetch a new connection with the new_tz as default
|
||||||
# time zone, run a query and rollback.
|
# time zone, run a query and rollback.
|
||||||
new_connection.settings_dict['TIME_ZONE'] = new_tz
|
new_connection.settings_dict['TIME_ZONE'] = new_tz
|
||||||
new_connection.enter_transaction_management()
|
new_connection.set_autocommit(False)
|
||||||
cursor = new_connection.cursor()
|
cursor = new_connection.cursor()
|
||||||
new_connection.rollback()
|
new_connection.rollback()
|
||||||
|
|
||||||
|
@ -755,7 +755,6 @@ class ConcurrentSaveTests(TransactionTestCase):
|
|||||||
try:
|
try:
|
||||||
# Do not delete a directly - doing so alters its state.
|
# Do not delete a directly - doing so alters its state.
|
||||||
Article.objects.filter(pk=a.pk).delete()
|
Article.objects.filter(pk=a.pk).delete()
|
||||||
connections[DEFAULT_DB_ALIAS].commit_unless_managed()
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
exceptions.append(e)
|
exceptions.append(e)
|
||||||
finally:
|
finally:
|
||||||
|
@ -20,17 +20,17 @@ class DeleteLockingTest(TransactionTestCase):
|
|||||||
available_apps = ['delete_regress']
|
available_apps = ['delete_regress']
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
transaction.set_autocommit(False)
|
||||||
# Create a second connection to the default database
|
# Create a second connection to the default database
|
||||||
new_connections = ConnectionHandler(settings.DATABASES)
|
new_connections = ConnectionHandler(settings.DATABASES)
|
||||||
self.conn2 = new_connections[DEFAULT_DB_ALIAS]
|
self.conn2 = new_connections[DEFAULT_DB_ALIAS]
|
||||||
# Put both DB connections into managed transaction mode
|
self.conn2.set_autocommit(False)
|
||||||
transaction.enter_transaction_management()
|
|
||||||
self.conn2.enter_transaction_management()
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
transaction.rollback()
|
||||||
|
transaction.set_autocommit(True)
|
||||||
# Close down the second connection.
|
# Close down the second connection.
|
||||||
transaction.leave_transaction_management()
|
self.conn2.rollback()
|
||||||
self.conn2.abort()
|
|
||||||
self.conn2.close()
|
self.conn2.close()
|
||||||
|
|
||||||
@skipUnlessDBFeature('test_db_allows_multiple_connections')
|
@skipUnlessDBFeature('test_db_allows_multiple_connections')
|
||||||
@ -38,15 +38,10 @@ class DeleteLockingTest(TransactionTestCase):
|
|||||||
"Deletes on concurrent transactions don't collide and lock the database. Regression for #9479"
|
"Deletes on concurrent transactions don't collide and lock the database. Regression for #9479"
|
||||||
|
|
||||||
# Create some dummy data
|
# Create some dummy data
|
||||||
b1 = Book(id=1, pagecount=100)
|
with transaction.atomic():
|
||||||
b2 = Book(id=2, pagecount=200)
|
Book.objects.create(id=1, pagecount=100)
|
||||||
b3 = Book(id=3, pagecount=300)
|
Book.objects.create(id=2, pagecount=200)
|
||||||
b1.save()
|
Book.objects.create(id=3, pagecount=300)
|
||||||
b2.save()
|
|
||||||
b3.save()
|
|
||||||
|
|
||||||
transaction.commit()
|
|
||||||
|
|
||||||
self.assertEqual(3, Book.objects.count())
|
self.assertEqual(3, Book.objects.count())
|
||||||
|
|
||||||
# Delete something using connection 2.
|
# Delete something using connection 2.
|
||||||
@ -58,10 +53,9 @@ class DeleteLockingTest(TransactionTestCase):
|
|||||||
# deleted in connection 2. This causes an infinite loop
|
# deleted in connection 2. This causes an infinite loop
|
||||||
# under MySQL InnoDB unless we keep track of already
|
# under MySQL InnoDB unless we keep track of already
|
||||||
# deleted objects.
|
# deleted objects.
|
||||||
Book.objects.filter(pagecount__lt=250).delete()
|
with transaction.atomic():
|
||||||
transaction.commit()
|
Book.objects.filter(pagecount__lt=250).delete()
|
||||||
self.assertEqual(1, Book.objects.count())
|
self.assertEqual(1, Book.objects.count())
|
||||||
transaction.commit()
|
|
||||||
|
|
||||||
|
|
||||||
class DeleteCascadeTests(TestCase):
|
class DeleteCascadeTests(TestCase):
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
from django.db import models
|
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class Band(models.Model):
|
|
||||||
name = models.CharField(max_length=100)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ('name',)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
@ -9,22 +9,16 @@ from unittest import skipIf
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.db import (transaction, connections, DEFAULT_DB_ALIAS,
|
|
||||||
IntegrityError)
|
|
||||||
from django.http import HttpRequest, HttpResponse, StreamingHttpResponse
|
from django.http import HttpRequest, HttpResponse, StreamingHttpResponse
|
||||||
from django.middleware.clickjacking import XFrameOptionsMiddleware
|
from django.middleware.clickjacking import XFrameOptionsMiddleware
|
||||||
from django.middleware.common import CommonMiddleware, BrokenLinkEmailsMiddleware
|
from django.middleware.common import CommonMiddleware, BrokenLinkEmailsMiddleware
|
||||||
from django.middleware.http import ConditionalGetMiddleware
|
from django.middleware.http import ConditionalGetMiddleware
|
||||||
from django.middleware.gzip import GZipMiddleware
|
from django.middleware.gzip import GZipMiddleware
|
||||||
from django.middleware.transaction import TransactionMiddleware
|
from django.test import TestCase, RequestFactory, override_settings
|
||||||
from django.test import TransactionTestCase, TestCase, RequestFactory, override_settings
|
|
||||||
from django.test.utils import IgnoreDeprecationWarningsMixin
|
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.encoding import force_str
|
from django.utils.encoding import force_str
|
||||||
from django.utils.six.moves import xrange
|
from django.utils.six.moves import xrange
|
||||||
|
|
||||||
from .models import Band
|
|
||||||
|
|
||||||
|
|
||||||
class CommonMiddlewareTest(TestCase):
|
class CommonMiddlewareTest(TestCase):
|
||||||
urls = 'middleware.urls'
|
urls = 'middleware.urls'
|
||||||
@ -666,64 +660,3 @@ class ETagGZipMiddlewareTest(TestCase):
|
|||||||
nogzip_etag = response.get('ETag')
|
nogzip_etag = response.get('ETag')
|
||||||
|
|
||||||
self.assertNotEqual(gzip_etag, nogzip_etag)
|
self.assertNotEqual(gzip_etag, nogzip_etag)
|
||||||
|
|
||||||
|
|
||||||
class TransactionMiddlewareTest(IgnoreDeprecationWarningsMixin, TransactionTestCase):
|
|
||||||
"""
|
|
||||||
Test the transaction middleware.
|
|
||||||
"""
|
|
||||||
|
|
||||||
available_apps = ['middleware']
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TransactionMiddlewareTest, self).setUp()
|
|
||||||
self.request = HttpRequest()
|
|
||||||
self.request.META = {
|
|
||||||
'SERVER_NAME': 'testserver',
|
|
||||||
'SERVER_PORT': 80,
|
|
||||||
}
|
|
||||||
self.request.path = self.request.path_info = "/"
|
|
||||||
self.response = HttpResponse()
|
|
||||||
self.response.status_code = 200
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
transaction.abort()
|
|
||||||
super(TransactionMiddlewareTest, self).tearDown()
|
|
||||||
|
|
||||||
def test_request(self):
|
|
||||||
TransactionMiddleware().process_request(self.request)
|
|
||||||
self.assertFalse(transaction.get_autocommit())
|
|
||||||
|
|
||||||
def test_managed_response(self):
|
|
||||||
transaction.enter_transaction_management()
|
|
||||||
Band.objects.create(name='The Beatles')
|
|
||||||
self.assertTrue(transaction.is_dirty())
|
|
||||||
TransactionMiddleware().process_response(self.request, self.response)
|
|
||||||
self.assertFalse(transaction.is_dirty())
|
|
||||||
self.assertEqual(Band.objects.count(), 1)
|
|
||||||
|
|
||||||
def test_exception(self):
|
|
||||||
transaction.enter_transaction_management()
|
|
||||||
Band.objects.create(name='The Beatles')
|
|
||||||
self.assertTrue(transaction.is_dirty())
|
|
||||||
TransactionMiddleware().process_exception(self.request, None)
|
|
||||||
self.assertFalse(transaction.is_dirty())
|
|
||||||
self.assertEqual(Band.objects.count(), 0)
|
|
||||||
|
|
||||||
def test_failing_commit(self):
|
|
||||||
# It is possible that connection.commit() fails. Check that
|
|
||||||
# TransactionMiddleware handles such cases correctly.
|
|
||||||
try:
|
|
||||||
def raise_exception():
|
|
||||||
raise IntegrityError()
|
|
||||||
connections[DEFAULT_DB_ALIAS].commit = raise_exception
|
|
||||||
transaction.enter_transaction_management()
|
|
||||||
Band.objects.create(name='The Beatles')
|
|
||||||
self.assertTrue(transaction.is_dirty())
|
|
||||||
with self.assertRaises(IntegrityError):
|
|
||||||
TransactionMiddleware().process_response(self.request, None)
|
|
||||||
self.assertFalse(transaction.is_dirty())
|
|
||||||
self.assertEqual(Band.objects.count(), 0)
|
|
||||||
self.assertFalse(transaction.is_managed())
|
|
||||||
finally:
|
|
||||||
del connections[DEFAULT_DB_ALIAS].commit
|
|
||||||
|
@ -5,15 +5,12 @@ from datetime import datetime, timedelta
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
import time
|
import time
|
||||||
from unittest import skipIf
|
|
||||||
|
|
||||||
from django.db import connection, connections
|
|
||||||
from django.core import signals
|
|
||||||
from django.core.exceptions import SuspiciousOperation
|
from django.core.exceptions import SuspiciousOperation
|
||||||
from django.core.handlers.wsgi import WSGIRequest, LimitedStream
|
from django.core.handlers.wsgi import WSGIRequest, LimitedStream
|
||||||
from django.http import (HttpRequest, HttpResponse, parse_cookie,
|
from django.http import (HttpRequest, HttpResponse, parse_cookie,
|
||||||
build_request_repr, UnreadablePostError, RawPostDataException)
|
build_request_repr, UnreadablePostError, RawPostDataException)
|
||||||
from django.test import SimpleTestCase, TransactionTestCase, override_settings
|
from django.test import SimpleTestCase, override_settings
|
||||||
from django.test.client import FakePayload
|
from django.test.client import FakePayload
|
||||||
from django.test.utils import str_prefix
|
from django.test.utils import str_prefix
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
@ -696,60 +693,3 @@ class HostValidationTests(SimpleTestCase):
|
|||||||
msg_suggestion2 % "invalid_hostname.com",
|
msg_suggestion2 % "invalid_hostname.com",
|
||||||
request.get_host
|
request.get_host
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@skipIf(connection.vendor == 'sqlite'
|
|
||||||
and connection.settings_dict['TEST']['NAME'] in (None, '', ':memory:'),
|
|
||||||
"Cannot establish two connections to an in-memory SQLite database.")
|
|
||||||
class DatabaseConnectionHandlingTests(TransactionTestCase):
|
|
||||||
|
|
||||||
available_apps = []
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
# Use a temporary connection to avoid messing with the main one.
|
|
||||||
self._old_default_connection = connections['default']
|
|
||||||
del connections['default']
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
try:
|
|
||||||
connections['default'].close()
|
|
||||||
finally:
|
|
||||||
connections['default'] = self._old_default_connection
|
|
||||||
|
|
||||||
def test_request_finished_db_state(self):
|
|
||||||
# Force closing connection on request end
|
|
||||||
connection.settings_dict['CONN_MAX_AGE'] = 0
|
|
||||||
|
|
||||||
# The GET below will not succeed, but it will give a response with
|
|
||||||
# defined ._handler_class. That is needed for sending the
|
|
||||||
# request_finished signal.
|
|
||||||
response = self.client.get('/')
|
|
||||||
# Make sure there is an open connection
|
|
||||||
connection.ensure_connection()
|
|
||||||
connection.enter_transaction_management()
|
|
||||||
signals.request_finished.send(sender=response._handler_class)
|
|
||||||
self.assertEqual(len(connection.transaction_state), 0)
|
|
||||||
|
|
||||||
def test_request_finished_failed_connection(self):
|
|
||||||
# Force closing connection on request end
|
|
||||||
connection.settings_dict['CONN_MAX_AGE'] = 0
|
|
||||||
|
|
||||||
connection.enter_transaction_management()
|
|
||||||
connection.set_dirty()
|
|
||||||
|
|
||||||
# Test that the rollback doesn't succeed (for example network failure
|
|
||||||
# could cause this).
|
|
||||||
def fail_horribly():
|
|
||||||
raise Exception("Horrible failure!")
|
|
||||||
connection._rollback = fail_horribly
|
|
||||||
try:
|
|
||||||
with self.assertRaises(Exception):
|
|
||||||
signals.request_finished.send(sender=self.__class__)
|
|
||||||
# The connection's state wasn't cleaned up
|
|
||||||
self.assertEqual(len(connection.transaction_state), 1)
|
|
||||||
finally:
|
|
||||||
del connection._rollback
|
|
||||||
# The connection will be cleaned on next request where the conn
|
|
||||||
# works again.
|
|
||||||
signals.request_finished.send(sender=self.__class__)
|
|
||||||
self.assertEqual(len(connection.transaction_state), 0)
|
|
||||||
|
@ -31,34 +31,24 @@ class SelectForUpdateTests(TransactionTestCase):
|
|||||||
available_apps = ['select_for_update']
|
available_apps = ['select_for_update']
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
transaction.enter_transaction_management()
|
# This is executed in autocommit mode so that code in
|
||||||
|
# run_select_for_update can see this data.
|
||||||
self.person = Person.objects.create(name='Reinhardt')
|
self.person = Person.objects.create(name='Reinhardt')
|
||||||
|
|
||||||
# We have to commit here so that code in run_select_for_update can
|
# We need another database connection in transaction to test that one
|
||||||
# see this data.
|
# connection issuing a SELECT ... FOR UPDATE will block.
|
||||||
transaction.commit()
|
|
||||||
|
|
||||||
# We need another database connection to test that one connection
|
|
||||||
# issuing a SELECT ... FOR UPDATE will block.
|
|
||||||
new_connections = ConnectionHandler(settings.DATABASES)
|
new_connections = ConnectionHandler(settings.DATABASES)
|
||||||
self.new_connection = new_connections[DEFAULT_DB_ALIAS]
|
self.new_connection = new_connections[DEFAULT_DB_ALIAS]
|
||||||
self.new_connection.enter_transaction_management()
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
try:
|
|
||||||
# We don't really care if this fails - some of the tests will set
|
|
||||||
# this in the course of their run.
|
|
||||||
transaction.abort()
|
|
||||||
self.new_connection.abort()
|
|
||||||
except transaction.TransactionManagementError:
|
|
||||||
pass
|
|
||||||
self.new_connection.close()
|
|
||||||
try:
|
try:
|
||||||
self.end_blocking_transaction()
|
self.end_blocking_transaction()
|
||||||
except (DatabaseError, AttributeError):
|
except (DatabaseError, AttributeError):
|
||||||
pass
|
pass
|
||||||
|
self.new_connection.close()
|
||||||
|
|
||||||
def start_blocking_transaction(self):
|
def start_blocking_transaction(self):
|
||||||
|
self.new_connection.set_autocommit(False)
|
||||||
# Start a blocking transaction. At some point,
|
# Start a blocking transaction. At some point,
|
||||||
# end_blocking_transaction() should be called.
|
# end_blocking_transaction() should be called.
|
||||||
self.cursor = self.new_connection.cursor()
|
self.cursor = self.new_connection.cursor()
|
||||||
@ -72,6 +62,7 @@ class SelectForUpdateTests(TransactionTestCase):
|
|||||||
def end_blocking_transaction(self):
|
def end_blocking_transaction(self):
|
||||||
# Roll back the blocking transaction.
|
# Roll back the blocking transaction.
|
||||||
self.new_connection.rollback()
|
self.new_connection.rollback()
|
||||||
|
self.new_connection.set_autocommit(True)
|
||||||
|
|
||||||
def has_for_update_sql(self, tested_connection, nowait=False):
|
def has_for_update_sql(self, tested_connection, nowait=False):
|
||||||
# Examine the SQL that was executed to determine whether it
|
# Examine the SQL that was executed to determine whether it
|
||||||
@ -146,19 +137,17 @@ class SelectForUpdateTests(TransactionTestCase):
|
|||||||
try:
|
try:
|
||||||
# We need to enter transaction management again, as this is done on
|
# We need to enter transaction management again, as this is done on
|
||||||
# per-thread basis
|
# per-thread basis
|
||||||
transaction.enter_transaction_management()
|
with transaction.atomic():
|
||||||
people = list(
|
people = list(
|
||||||
Person.objects.all().select_for_update(nowait=nowait)
|
Person.objects.all().select_for_update(nowait=nowait)
|
||||||
)
|
)
|
||||||
people[0].name = 'Fred'
|
people[0].name = 'Fred'
|
||||||
people[0].save()
|
people[0].save()
|
||||||
transaction.commit()
|
|
||||||
except DatabaseError as e:
|
except DatabaseError as e:
|
||||||
status.append(e)
|
status.append(e)
|
||||||
finally:
|
finally:
|
||||||
# This method is run in a separate thread. It uses its own
|
# This method is run in a separate thread. It uses its own
|
||||||
# database connection. Close it without waiting for the GC.
|
# database connection. Close it without waiting for the GC.
|
||||||
transaction.abort()
|
|
||||||
connection.close()
|
connection.close()
|
||||||
|
|
||||||
@requires_threading
|
@requires_threading
|
||||||
@ -245,16 +234,6 @@ class SelectForUpdateTests(TransactionTestCase):
|
|||||||
self.end_blocking_transaction()
|
self.end_blocking_transaction()
|
||||||
self.assertIsInstance(status[-1], DatabaseError)
|
self.assertIsInstance(status[-1], DatabaseError)
|
||||||
|
|
||||||
@skipUnlessDBFeature('has_select_for_update')
|
|
||||||
def test_transaction_dirty_managed(self):
|
|
||||||
""" Check that a select_for_update sets the transaction to be
|
|
||||||
dirty when executed under txn management. Setting the txn dirty
|
|
||||||
means that it will be either committed or rolled back by Django,
|
|
||||||
which will release any locks held by the SELECT FOR UPDATE.
|
|
||||||
"""
|
|
||||||
list(Person.objects.select_for_update())
|
|
||||||
self.assertTrue(transaction.is_dirty())
|
|
||||||
|
|
||||||
@skipUnlessDBFeature('has_select_for_update')
|
@skipUnlessDBFeature('has_select_for_update')
|
||||||
def test_select_for_update_on_multidb(self):
|
def test_select_for_update_on_multidb(self):
|
||||||
old_routers = router.routers
|
old_routers = router.routers
|
||||||
|
@ -272,16 +272,13 @@ class SerializersTransactionTestBase(object):
|
|||||||
Tests that objects ids can be referenced before they are
|
Tests that objects ids can be referenced before they are
|
||||||
defined in the serialization data.
|
defined in the serialization data.
|
||||||
"""
|
"""
|
||||||
# The deserialization process needs to be contained
|
# The deserialization process needs to run in a transaction in order
|
||||||
# within a transaction in order to test forward reference
|
# to test forward reference handling.
|
||||||
# handling.
|
with transaction.atomic():
|
||||||
transaction.enter_transaction_management()
|
objs = serializers.deserialize(self.serializer_name, self.fwd_ref_str)
|
||||||
objs = serializers.deserialize(self.serializer_name, self.fwd_ref_str)
|
with connection.constraint_checks_disabled():
|
||||||
with connection.constraint_checks_disabled():
|
for obj in objs:
|
||||||
for obj in objs:
|
obj.save()
|
||||||
obj.save()
|
|
||||||
transaction.commit()
|
|
||||||
transaction.leave_transaction_management()
|
|
||||||
|
|
||||||
for model_cls in (Category, Author, Article):
|
for model_cls in (Category, Author, Article):
|
||||||
self.assertEqual(model_cls.objects.all().count(), 1)
|
self.assertEqual(model_cls.objects.all().count(), 1)
|
||||||
|
@ -4,8 +4,7 @@ import sys
|
|||||||
from unittest import skipIf, skipUnless
|
from unittest import skipIf, skipUnless
|
||||||
|
|
||||||
from django.db import connection, transaction, DatabaseError, IntegrityError
|
from django.db import connection, transaction, DatabaseError, IntegrityError
|
||||||
from django.test import TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature
|
from django.test import TransactionTestCase, skipIfDBFeature
|
||||||
from django.test.utils import IgnoreDeprecationWarningsMixin
|
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
|
||||||
from .models import Reporter
|
from .models import Reporter
|
||||||
@ -238,20 +237,6 @@ class AtomicWithoutAutocommitTests(AtomicTests):
|
|||||||
transaction.set_autocommit(True)
|
transaction.set_autocommit(True)
|
||||||
|
|
||||||
|
|
||||||
@skipIf(connection.features.autocommits_when_autocommit_is_off,
|
|
||||||
"This test requires a non-autocommit mode that doesn't autocommit.")
|
|
||||||
class AtomicInsideLegacyTransactionManagementTests(AtomicTests):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
transaction.enter_transaction_management()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
# The tests access the database after exercising 'atomic', making the
|
|
||||||
# connection dirty; a rollback is required to make it clean.
|
|
||||||
transaction.rollback()
|
|
||||||
transaction.leave_transaction_management()
|
|
||||||
|
|
||||||
|
|
||||||
@skipUnless(connection.features.uses_savepoints,
|
@skipUnless(connection.features.uses_savepoints,
|
||||||
"'atomic' requires transactions and savepoints.")
|
"'atomic' requires transactions and savepoints.")
|
||||||
class AtomicMergeTests(TransactionTestCase):
|
class AtomicMergeTests(TransactionTestCase):
|
||||||
@ -324,13 +309,6 @@ class AtomicErrorsTests(TransactionTestCase):
|
|||||||
with self.assertRaises(transaction.TransactionManagementError):
|
with self.assertRaises(transaction.TransactionManagementError):
|
||||||
transaction.rollback()
|
transaction.rollback()
|
||||||
|
|
||||||
def test_atomic_prevents_calling_transaction_management_methods(self):
|
|
||||||
with transaction.atomic():
|
|
||||||
with self.assertRaises(transaction.TransactionManagementError):
|
|
||||||
transaction.enter_transaction_management()
|
|
||||||
with self.assertRaises(transaction.TransactionManagementError):
|
|
||||||
transaction.leave_transaction_management()
|
|
||||||
|
|
||||||
def test_atomic_prevents_queries_in_broken_transaction(self):
|
def test_atomic_prevents_queries_in_broken_transaction(self):
|
||||||
r1 = Reporter.objects.create(first_name="Archibald", last_name="Haddock")
|
r1 = Reporter.objects.create(first_name="Archibald", last_name="Haddock")
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
@ -366,319 +344,3 @@ class AtomicMiscTests(TransactionTestCase):
|
|||||||
pass
|
pass
|
||||||
# Must not raise an exception
|
# Must not raise an exception
|
||||||
transaction.atomic(Callable())
|
transaction.atomic(Callable())
|
||||||
|
|
||||||
|
|
||||||
class TransactionTests(IgnoreDeprecationWarningsMixin, TransactionTestCase):
|
|
||||||
|
|
||||||
available_apps = ['transactions']
|
|
||||||
|
|
||||||
def create_a_reporter_then_fail(self, first, last):
|
|
||||||
a = Reporter(first_name=first, last_name=last)
|
|
||||||
a.save()
|
|
||||||
raise Exception("I meant to do that")
|
|
||||||
|
|
||||||
def remove_a_reporter(self, first_name):
|
|
||||||
r = Reporter.objects.get(first_name="Alice")
|
|
||||||
r.delete()
|
|
||||||
|
|
||||||
def manually_managed(self):
|
|
||||||
r = Reporter(first_name="Dirk", last_name="Gently")
|
|
||||||
r.save()
|
|
||||||
transaction.commit()
|
|
||||||
|
|
||||||
def manually_managed_mistake(self):
|
|
||||||
r = Reporter(first_name="Edward", last_name="Woodward")
|
|
||||||
r.save()
|
|
||||||
# Oops, I forgot to commit/rollback!
|
|
||||||
|
|
||||||
@skipUnlessDBFeature('supports_transactions')
|
|
||||||
def test_autocommit(self):
|
|
||||||
"""
|
|
||||||
The default behavior is to autocommit after each save() action.
|
|
||||||
"""
|
|
||||||
self.assertRaises(
|
|
||||||
Exception,
|
|
||||||
self.create_a_reporter_then_fail,
|
|
||||||
"Alice", "Smith"
|
|
||||||
)
|
|
||||||
|
|
||||||
# The object created before the exception still exists
|
|
||||||
self.assertEqual(Reporter.objects.count(), 1)
|
|
||||||
|
|
||||||
@skipUnlessDBFeature('supports_transactions')
|
|
||||||
def test_autocommit_decorator(self):
|
|
||||||
"""
|
|
||||||
The autocommit decorator works exactly the same as the default behavior.
|
|
||||||
"""
|
|
||||||
autocomitted_create_then_fail = transaction.autocommit(
|
|
||||||
self.create_a_reporter_then_fail
|
|
||||||
)
|
|
||||||
self.assertRaises(
|
|
||||||
Exception,
|
|
||||||
autocomitted_create_then_fail,
|
|
||||||
"Alice", "Smith"
|
|
||||||
)
|
|
||||||
# Again, the object created before the exception still exists
|
|
||||||
self.assertEqual(Reporter.objects.count(), 1)
|
|
||||||
|
|
||||||
@skipUnlessDBFeature('supports_transactions')
|
|
||||||
def test_autocommit_decorator_with_using(self):
|
|
||||||
"""
|
|
||||||
The autocommit decorator also works with a using argument.
|
|
||||||
"""
|
|
||||||
autocomitted_create_then_fail = transaction.autocommit(using='default')(
|
|
||||||
self.create_a_reporter_then_fail
|
|
||||||
)
|
|
||||||
self.assertRaises(
|
|
||||||
Exception,
|
|
||||||
autocomitted_create_then_fail,
|
|
||||||
"Alice", "Smith"
|
|
||||||
)
|
|
||||||
# Again, the object created before the exception still exists
|
|
||||||
self.assertEqual(Reporter.objects.count(), 1)
|
|
||||||
|
|
||||||
@skipUnlessDBFeature('supports_transactions')
|
|
||||||
def test_commit_on_success(self):
|
|
||||||
"""
|
|
||||||
With the commit_on_success decorator, the transaction is only committed
|
|
||||||
if the function doesn't throw an exception.
|
|
||||||
"""
|
|
||||||
committed_on_success = transaction.commit_on_success(
|
|
||||||
self.create_a_reporter_then_fail)
|
|
||||||
self.assertRaises(Exception, committed_on_success, "Dirk", "Gently")
|
|
||||||
# This time the object never got saved
|
|
||||||
self.assertEqual(Reporter.objects.count(), 0)
|
|
||||||
|
|
||||||
@skipUnlessDBFeature('supports_transactions')
|
|
||||||
def test_commit_on_success_with_using(self):
|
|
||||||
"""
|
|
||||||
The commit_on_success decorator also works with a using argument.
|
|
||||||
"""
|
|
||||||
using_committed_on_success = transaction.commit_on_success(using='default')(
|
|
||||||
self.create_a_reporter_then_fail
|
|
||||||
)
|
|
||||||
self.assertRaises(
|
|
||||||
Exception,
|
|
||||||
using_committed_on_success,
|
|
||||||
"Dirk", "Gently"
|
|
||||||
)
|
|
||||||
# This time the object never got saved
|
|
||||||
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")
|
|
||||||
remove_comitted_on_success = transaction.commit_on_success(
|
|
||||||
self.remove_a_reporter
|
|
||||||
)
|
|
||||||
remove_comitted_on_success("Alice")
|
|
||||||
self.assertEqual(list(Reporter.objects.all()), [])
|
|
||||||
|
|
||||||
@skipUnlessDBFeature('supports_transactions')
|
|
||||||
def test_commit_on_success_exit(self):
|
|
||||||
@transaction.autocommit()
|
|
||||||
def gen_reporter():
|
|
||||||
@transaction.commit_on_success
|
|
||||||
def create_reporter():
|
|
||||||
Reporter.objects.create(first_name="Bobby", last_name="Tables")
|
|
||||||
|
|
||||||
create_reporter()
|
|
||||||
# Much more formal
|
|
||||||
r = Reporter.objects.get()
|
|
||||||
r.first_name = "Robert"
|
|
||||||
r.save()
|
|
||||||
|
|
||||||
gen_reporter()
|
|
||||||
r = Reporter.objects.get()
|
|
||||||
self.assertEqual(r.first_name, "Robert")
|
|
||||||
|
|
||||||
@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.
|
|
||||||
"""
|
|
||||||
manually_managed = transaction.commit_manually(self.manually_managed)
|
|
||||||
manually_managed()
|
|
||||||
self.assertEqual(Reporter.objects.count(), 1)
|
|
||||||
|
|
||||||
@skipUnlessDBFeature('supports_transactions')
|
|
||||||
def test_manually_managed_mistake(self):
|
|
||||||
"""
|
|
||||||
If you forget, you'll get bad errors.
|
|
||||||
"""
|
|
||||||
manually_managed_mistake = transaction.commit_manually(
|
|
||||||
self.manually_managed_mistake
|
|
||||||
)
|
|
||||||
self.assertRaises(transaction.TransactionManagementError,
|
|
||||||
manually_managed_mistake)
|
|
||||||
|
|
||||||
@skipUnlessDBFeature('supports_transactions')
|
|
||||||
def test_manually_managed_with_using(self):
|
|
||||||
"""
|
|
||||||
The commit_manually function also works with a using argument.
|
|
||||||
"""
|
|
||||||
using_manually_managed_mistake = transaction.commit_manually(using='default')(
|
|
||||||
self.manually_managed_mistake
|
|
||||||
)
|
|
||||||
self.assertRaises(
|
|
||||||
transaction.TransactionManagementError,
|
|
||||||
using_manually_managed_mistake
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TransactionRollbackTests(IgnoreDeprecationWarningsMixin, TransactionTestCase):
|
|
||||||
|
|
||||||
available_apps = ['transactions']
|
|
||||||
|
|
||||||
def execute_bad_sql(self):
|
|
||||||
with connection.cursor() as cursor:
|
|
||||||
cursor.execute("INSERT INTO transactions_reporter (first_name, last_name) VALUES ('Douglas', 'Adams');")
|
|
||||||
|
|
||||||
@skipUnlessDBFeature('requires_rollback_on_dirty_transaction')
|
|
||||||
def test_bad_sql(self):
|
|
||||||
"""
|
|
||||||
Regression for #11900: If a function 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.
|
|
||||||
"""
|
|
||||||
execute_bad_sql = transaction.commit_on_success(self.execute_bad_sql)
|
|
||||||
self.assertRaises(IntegrityError, execute_bad_sql)
|
|
||||||
transaction.rollback()
|
|
||||||
|
|
||||||
|
|
||||||
class TransactionContextManagerTests(IgnoreDeprecationWarningsMixin, TransactionTestCase):
|
|
||||||
|
|
||||||
available_apps = ['transactions']
|
|
||||||
|
|
||||||
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_commit_on_success_exit(self):
|
|
||||||
with transaction.autocommit():
|
|
||||||
with transaction.commit_on_success():
|
|
||||||
Reporter.objects.create(first_name="Bobby", last_name="Tables")
|
|
||||||
|
|
||||||
# Much more formal
|
|
||||||
r = Reporter.objects.get()
|
|
||||||
r.first_name = "Robert"
|
|
||||||
r.save()
|
|
||||||
|
|
||||||
r = Reporter.objects.get()
|
|
||||||
self.assertEqual(r.first_name, "Robert")
|
|
||||||
|
|
||||||
@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():
|
|
||||||
with connection.cursor() as cursor:
|
|
||||||
cursor.execute("INSERT INTO transactions_reporter (first_name, last_name) VALUES ('Douglas', 'Adams');")
|
|
||||||
transaction.rollback()
|
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
class Mod(models.Model):
|
|
||||||
fld = models.IntegerField()
|
|
||||||
|
|
||||||
|
|
||||||
class SubMod(Mod):
|
|
||||||
cnt = models.IntegerField(unique=True)
|
|
||||||
|
|
||||||
|
|
||||||
class M2mA(models.Model):
|
|
||||||
others = models.ManyToManyField('M2mB')
|
|
||||||
|
|
||||||
|
|
||||||
class M2mB(models.Model):
|
|
||||||
fld = models.IntegerField()
|
|
@ -1,392 +0,0 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from unittest import skipIf, skipUnless, SkipTest
|
|
||||||
|
|
||||||
from django.db import (connection, connections, transaction, DEFAULT_DB_ALIAS, DatabaseError,
|
|
||||||
IntegrityError)
|
|
||||||
from django.db.transaction import commit_on_success, commit_manually, TransactionManagementError
|
|
||||||
from django.test import TransactionTestCase, override_settings, skipUnlessDBFeature
|
|
||||||
from django.test.utils import IgnoreDeprecationWarningsMixin
|
|
||||||
|
|
||||||
from .models import Mod, M2mA, M2mB, SubMod
|
|
||||||
|
|
||||||
|
|
||||||
class ModelInheritanceTests(TransactionTestCase):
|
|
||||||
|
|
||||||
available_apps = ['transactions_regress']
|
|
||||||
|
|
||||||
def test_save(self):
|
|
||||||
# First, create a SubMod, then try to save another with conflicting
|
|
||||||
# cnt field. The problem was that transactions were committed after
|
|
||||||
# every parent save when not in managed transaction. As the cnt
|
|
||||||
# conflict is in the second model, we can check if the first save
|
|
||||||
# was committed or not.
|
|
||||||
SubMod(fld=1, cnt=1).save()
|
|
||||||
# We should have committed the transaction for the above - assert this.
|
|
||||||
connection.rollback()
|
|
||||||
self.assertEqual(SubMod.objects.count(), 1)
|
|
||||||
try:
|
|
||||||
SubMod(fld=2, cnt=1).save()
|
|
||||||
except IntegrityError:
|
|
||||||
connection.rollback()
|
|
||||||
self.assertEqual(SubMod.objects.count(), 1)
|
|
||||||
self.assertEqual(Mod.objects.count(), 1)
|
|
||||||
|
|
||||||
|
|
||||||
class TestTransactionClosing(IgnoreDeprecationWarningsMixin, TransactionTestCase):
|
|
||||||
"""
|
|
||||||
Tests to make sure that transactions are properly closed
|
|
||||||
when they should be, and aren't left pending after operations
|
|
||||||
have been performed in them. Refs #9964.
|
|
||||||
"""
|
|
||||||
|
|
||||||
available_apps = [
|
|
||||||
'transactions_regress',
|
|
||||||
'django.contrib.auth',
|
|
||||||
'django.contrib.contenttypes',
|
|
||||||
]
|
|
||||||
|
|
||||||
def test_raw_committed_on_success(self):
|
|
||||||
"""
|
|
||||||
Make sure a transaction consisting of raw SQL execution gets
|
|
||||||
committed by the commit_on_success decorator.
|
|
||||||
"""
|
|
||||||
@commit_on_success
|
|
||||||
def raw_sql():
|
|
||||||
"Write a record using raw sql under a commit_on_success decorator"
|
|
||||||
with connection.cursor() as cursor:
|
|
||||||
cursor.execute("INSERT into transactions_regress_mod (fld) values (18)")
|
|
||||||
|
|
||||||
raw_sql()
|
|
||||||
# Rollback so that if the decorator didn't commit, the record is unwritten
|
|
||||||
transaction.rollback()
|
|
||||||
self.assertEqual(Mod.objects.count(), 1)
|
|
||||||
# Check that the record is in the DB
|
|
||||||
obj = Mod.objects.all()[0]
|
|
||||||
self.assertEqual(obj.fld, 18)
|
|
||||||
|
|
||||||
def test_commit_manually_enforced(self):
|
|
||||||
"""
|
|
||||||
Make sure that under commit_manually, even "read-only" transaction require closure
|
|
||||||
(commit or rollback), and a transaction left pending is treated as an error.
|
|
||||||
"""
|
|
||||||
@commit_manually
|
|
||||||
def non_comitter():
|
|
||||||
"Execute a managed transaction with read-only operations and fail to commit"
|
|
||||||
Mod.objects.count()
|
|
||||||
|
|
||||||
self.assertRaises(TransactionManagementError, non_comitter)
|
|
||||||
|
|
||||||
def test_commit_manually_commit_ok(self):
|
|
||||||
"""
|
|
||||||
Test that under commit_manually, a committed transaction is accepted by the transaction
|
|
||||||
management mechanisms
|
|
||||||
"""
|
|
||||||
@commit_manually
|
|
||||||
def committer():
|
|
||||||
"""
|
|
||||||
Perform a database query, then commit the transaction
|
|
||||||
"""
|
|
||||||
Mod.objects.count()
|
|
||||||
transaction.commit()
|
|
||||||
|
|
||||||
try:
|
|
||||||
committer()
|
|
||||||
except TransactionManagementError:
|
|
||||||
self.fail("Commit did not clear the transaction state")
|
|
||||||
|
|
||||||
def test_commit_manually_rollback_ok(self):
|
|
||||||
"""
|
|
||||||
Test that under commit_manually, a rolled-back transaction is accepted by the transaction
|
|
||||||
management mechanisms
|
|
||||||
"""
|
|
||||||
@commit_manually
|
|
||||||
def roller_back():
|
|
||||||
"""
|
|
||||||
Perform a database query, then rollback the transaction
|
|
||||||
"""
|
|
||||||
Mod.objects.count()
|
|
||||||
transaction.rollback()
|
|
||||||
|
|
||||||
try:
|
|
||||||
roller_back()
|
|
||||||
except TransactionManagementError:
|
|
||||||
self.fail("Rollback did not clear the transaction state")
|
|
||||||
|
|
||||||
def test_commit_manually_enforced_after_commit(self):
|
|
||||||
"""
|
|
||||||
Test that under commit_manually, if a transaction is committed and an operation is
|
|
||||||
performed later, we still require the new transaction to be closed
|
|
||||||
"""
|
|
||||||
@commit_manually
|
|
||||||
def fake_committer():
|
|
||||||
"Query, commit, then query again, leaving with a pending transaction"
|
|
||||||
Mod.objects.count()
|
|
||||||
transaction.commit()
|
|
||||||
Mod.objects.count()
|
|
||||||
|
|
||||||
self.assertRaises(TransactionManagementError, fake_committer)
|
|
||||||
|
|
||||||
@skipUnlessDBFeature('supports_transactions')
|
|
||||||
def test_reuse_cursor_reference(self):
|
|
||||||
"""
|
|
||||||
Make sure transaction closure is enforced even when the queries are performed
|
|
||||||
through a single cursor reference retrieved in the beginning
|
|
||||||
(this is to show why it is wrong to set the transaction dirty only when a cursor
|
|
||||||
is fetched from the connection).
|
|
||||||
"""
|
|
||||||
@commit_on_success
|
|
||||||
def reuse_cursor_ref():
|
|
||||||
"""
|
|
||||||
Fetch a cursor, perform an query, rollback to close the transaction,
|
|
||||||
then write a record (in a new transaction) using the same cursor object
|
|
||||||
(reference). All this under commit_on_success, so the second insert should
|
|
||||||
be committed.
|
|
||||||
"""
|
|
||||||
with connection.cursor() as cursor:
|
|
||||||
cursor.execute("INSERT into transactions_regress_mod (fld) values (2)")
|
|
||||||
transaction.rollback()
|
|
||||||
cursor.execute("INSERT into transactions_regress_mod (fld) values (2)")
|
|
||||||
|
|
||||||
reuse_cursor_ref()
|
|
||||||
# Rollback so that if the decorator didn't commit, the record is unwritten
|
|
||||||
transaction.rollback()
|
|
||||||
self.assertEqual(Mod.objects.count(), 1)
|
|
||||||
obj = Mod.objects.all()[0]
|
|
||||||
self.assertEqual(obj.fld, 2)
|
|
||||||
|
|
||||||
def test_failing_query_transaction_closed(self):
|
|
||||||
"""
|
|
||||||
Make sure that under commit_on_success, a transaction is rolled back even if
|
|
||||||
the first database-modifying operation fails.
|
|
||||||
This is prompted by http://code.djangoproject.com/ticket/6669 (and based on sample
|
|
||||||
code posted there to exemplify the problem): Before Django 1.3,
|
|
||||||
transactions were only marked "dirty" by the save() function after it successfully
|
|
||||||
wrote the object to the database.
|
|
||||||
"""
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
|
|
||||||
@transaction.commit_on_success
|
|
||||||
def create_system_user():
|
|
||||||
"Create a user in a transaction"
|
|
||||||
user = User.objects.create_user(username='system', password='iamr00t',
|
|
||||||
email='root@SITENAME.com')
|
|
||||||
# Redundant, just makes sure the user id was read back from DB
|
|
||||||
Mod.objects.create(fld=user.pk)
|
|
||||||
|
|
||||||
# Create a user
|
|
||||||
create_system_user()
|
|
||||||
|
|
||||||
with self.assertRaises(DatabaseError):
|
|
||||||
# The second call to create_system_user should fail for violating
|
|
||||||
# a unique constraint (it's trying to re-create the same user)
|
|
||||||
create_system_user()
|
|
||||||
|
|
||||||
# Try to read the database. If the last transaction was indeed closed,
|
|
||||||
# this should cause no problems
|
|
||||||
User.objects.all()[0]
|
|
||||||
|
|
||||||
@override_settings(DEBUG=True)
|
|
||||||
def test_failing_query_transaction_closed_debug(self):
|
|
||||||
"""
|
|
||||||
Regression for #6669. Same test as above, with DEBUG=True.
|
|
||||||
"""
|
|
||||||
self.test_failing_query_transaction_closed()
|
|
||||||
|
|
||||||
|
|
||||||
@skipIf(connection.vendor == 'sqlite'
|
|
||||||
and connection.settings_dict['TEST']['NAME'] in (None, '', ':memory:'),
|
|
||||||
"Cannot establish two connections to an in-memory SQLite database.")
|
|
||||||
class TestNewConnection(IgnoreDeprecationWarningsMixin, TransactionTestCase):
|
|
||||||
"""
|
|
||||||
Check that new connections don't have special behavior.
|
|
||||||
"""
|
|
||||||
|
|
||||||
available_apps = ['transactions_regress']
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self._old_backend = connections[DEFAULT_DB_ALIAS]
|
|
||||||
settings = self._old_backend.settings_dict.copy()
|
|
||||||
new_backend = self._old_backend.__class__(settings, DEFAULT_DB_ALIAS)
|
|
||||||
connections[DEFAULT_DB_ALIAS] = new_backend
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
try:
|
|
||||||
connections[DEFAULT_DB_ALIAS].abort()
|
|
||||||
connections[DEFAULT_DB_ALIAS].close()
|
|
||||||
finally:
|
|
||||||
connections[DEFAULT_DB_ALIAS] = self._old_backend
|
|
||||||
|
|
||||||
def test_commit(self):
|
|
||||||
"""
|
|
||||||
Users are allowed to commit and rollback connections.
|
|
||||||
"""
|
|
||||||
connection.set_autocommit(False)
|
|
||||||
try:
|
|
||||||
# The starting value is False, not None.
|
|
||||||
self.assertIs(connection._dirty, False)
|
|
||||||
list(Mod.objects.all())
|
|
||||||
self.assertTrue(connection.is_dirty())
|
|
||||||
connection.commit()
|
|
||||||
self.assertFalse(connection.is_dirty())
|
|
||||||
list(Mod.objects.all())
|
|
||||||
self.assertTrue(connection.is_dirty())
|
|
||||||
connection.rollback()
|
|
||||||
self.assertFalse(connection.is_dirty())
|
|
||||||
finally:
|
|
||||||
connection.set_autocommit(True)
|
|
||||||
|
|
||||||
def test_enter_exit_management(self):
|
|
||||||
orig_dirty = connection._dirty
|
|
||||||
connection.enter_transaction_management()
|
|
||||||
connection.leave_transaction_management()
|
|
||||||
self.assertEqual(orig_dirty, connection._dirty)
|
|
||||||
|
|
||||||
|
|
||||||
@skipUnless(connection.vendor == 'postgresql',
|
|
||||||
"This test only valid for PostgreSQL")
|
|
||||||
class TestPostgresAutocommitAndIsolation(IgnoreDeprecationWarningsMixin, TransactionTestCase):
|
|
||||||
"""
|
|
||||||
Tests to make sure psycopg2's autocommit mode and isolation level
|
|
||||||
is restored after entering and leaving transaction management.
|
|
||||||
Refs #16047, #18130.
|
|
||||||
"""
|
|
||||||
|
|
||||||
available_apps = ['transactions_regress']
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
from psycopg2.extensions import (ISOLATION_LEVEL_AUTOCOMMIT,
|
|
||||||
ISOLATION_LEVEL_SERIALIZABLE,
|
|
||||||
TRANSACTION_STATUS_IDLE)
|
|
||||||
self._autocommit = ISOLATION_LEVEL_AUTOCOMMIT
|
|
||||||
self._serializable = ISOLATION_LEVEL_SERIALIZABLE
|
|
||||||
self._idle = TRANSACTION_STATUS_IDLE
|
|
||||||
|
|
||||||
# We want a clean backend with autocommit = True, so
|
|
||||||
# first we need to do a bit of work to have that.
|
|
||||||
self._old_backend = connections[DEFAULT_DB_ALIAS]
|
|
||||||
settings = self._old_backend.settings_dict.copy()
|
|
||||||
opts = settings['OPTIONS'].copy()
|
|
||||||
opts['isolation_level'] = ISOLATION_LEVEL_SERIALIZABLE
|
|
||||||
settings['OPTIONS'] = opts
|
|
||||||
new_backend = self._old_backend.__class__(settings, DEFAULT_DB_ALIAS)
|
|
||||||
connections[DEFAULT_DB_ALIAS] = new_backend
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
try:
|
|
||||||
connections[DEFAULT_DB_ALIAS].abort()
|
|
||||||
finally:
|
|
||||||
connections[DEFAULT_DB_ALIAS].close()
|
|
||||||
connections[DEFAULT_DB_ALIAS] = self._old_backend
|
|
||||||
|
|
||||||
def test_initial_autocommit_state(self):
|
|
||||||
# Autocommit is activated when the connection is created.
|
|
||||||
connection.cursor().close()
|
|
||||||
self.assertTrue(connection.autocommit)
|
|
||||||
|
|
||||||
def test_transaction_management(self):
|
|
||||||
transaction.enter_transaction_management()
|
|
||||||
self.assertFalse(connection.autocommit)
|
|
||||||
self.assertEqual(connection.isolation_level, self._serializable)
|
|
||||||
|
|
||||||
transaction.leave_transaction_management()
|
|
||||||
self.assertTrue(connection.autocommit)
|
|
||||||
|
|
||||||
def test_transaction_stacking(self):
|
|
||||||
transaction.enter_transaction_management()
|
|
||||||
self.assertFalse(connection.autocommit)
|
|
||||||
self.assertEqual(connection.isolation_level, self._serializable)
|
|
||||||
|
|
||||||
transaction.enter_transaction_management()
|
|
||||||
self.assertFalse(connection.autocommit)
|
|
||||||
self.assertEqual(connection.isolation_level, self._serializable)
|
|
||||||
|
|
||||||
transaction.leave_transaction_management()
|
|
||||||
self.assertFalse(connection.autocommit)
|
|
||||||
self.assertEqual(connection.isolation_level, self._serializable)
|
|
||||||
|
|
||||||
transaction.leave_transaction_management()
|
|
||||||
self.assertTrue(connection.autocommit)
|
|
||||||
|
|
||||||
def test_enter_autocommit(self):
|
|
||||||
transaction.enter_transaction_management()
|
|
||||||
self.assertFalse(connection.autocommit)
|
|
||||||
self.assertEqual(connection.isolation_level, self._serializable)
|
|
||||||
list(Mod.objects.all())
|
|
||||||
self.assertTrue(transaction.is_dirty())
|
|
||||||
# Enter autocommit mode again.
|
|
||||||
transaction.enter_transaction_management(False)
|
|
||||||
self.assertFalse(transaction.is_dirty())
|
|
||||||
self.assertEqual(
|
|
||||||
connection.connection.get_transaction_status(),
|
|
||||||
self._idle)
|
|
||||||
list(Mod.objects.all())
|
|
||||||
self.assertFalse(transaction.is_dirty())
|
|
||||||
transaction.leave_transaction_management()
|
|
||||||
self.assertFalse(connection.autocommit)
|
|
||||||
self.assertEqual(connection.isolation_level, self._serializable)
|
|
||||||
transaction.leave_transaction_management()
|
|
||||||
self.assertTrue(connection.autocommit)
|
|
||||||
|
|
||||||
|
|
||||||
class TestManyToManyAddTransaction(IgnoreDeprecationWarningsMixin, TransactionTestCase):
|
|
||||||
|
|
||||||
available_apps = ['transactions_regress']
|
|
||||||
|
|
||||||
def test_manyrelated_add_commit(self):
|
|
||||||
"Test for https://code.djangoproject.com/ticket/16818"
|
|
||||||
a = M2mA.objects.create()
|
|
||||||
b = M2mB.objects.create(fld=10)
|
|
||||||
a.others.add(b)
|
|
||||||
|
|
||||||
# We're in a TransactionTestCase and have not changed transaction
|
|
||||||
# behavior from default of "autocommit", so this rollback should not
|
|
||||||
# actually do anything. If it does in fact undo our add, that's a bug
|
|
||||||
# that the bulk insert was not auto-committed.
|
|
||||||
transaction.rollback()
|
|
||||||
self.assertEqual(a.others.count(), 1)
|
|
||||||
|
|
||||||
|
|
||||||
class SavepointTest(IgnoreDeprecationWarningsMixin, TransactionTestCase):
|
|
||||||
|
|
||||||
available_apps = ['transactions_regress']
|
|
||||||
|
|
||||||
@skipIf(connection.vendor == 'sqlite',
|
|
||||||
"SQLite doesn't support savepoints in managed mode")
|
|
||||||
@skipUnlessDBFeature('uses_savepoints')
|
|
||||||
def test_savepoint_commit(self):
|
|
||||||
@commit_manually
|
|
||||||
def work():
|
|
||||||
mod = Mod.objects.create(fld=1)
|
|
||||||
pk = mod.pk
|
|
||||||
sid = transaction.savepoint()
|
|
||||||
Mod.objects.filter(pk=pk).update(fld=10)
|
|
||||||
transaction.savepoint_commit(sid)
|
|
||||||
mod2 = Mod.objects.get(pk=pk)
|
|
||||||
transaction.commit()
|
|
||||||
self.assertEqual(mod2.fld, 10)
|
|
||||||
|
|
||||||
work()
|
|
||||||
|
|
||||||
@skipIf(connection.vendor == 'sqlite',
|
|
||||||
"SQLite doesn't support savepoints in managed mode")
|
|
||||||
@skipUnlessDBFeature('uses_savepoints')
|
|
||||||
def test_savepoint_rollback(self):
|
|
||||||
# _mysql_storage_engine issues a query and as such can't be applied in
|
|
||||||
# a skipIf decorator since that would execute the query on module load.
|
|
||||||
if (connection.vendor == 'mysql' and
|
|
||||||
connection.features._mysql_storage_engine == 'MyISAM'):
|
|
||||||
raise SkipTest("MyISAM MySQL storage engine doesn't support savepoints")
|
|
||||||
|
|
||||||
@commit_manually
|
|
||||||
def work():
|
|
||||||
mod = Mod.objects.create(fld=1)
|
|
||||||
pk = mod.pk
|
|
||||||
sid = transaction.savepoint()
|
|
||||||
Mod.objects.filter(pk=pk).update(fld=20)
|
|
||||||
transaction.savepoint_rollback(sid)
|
|
||||||
mod2 = Mod.objects.get(pk=pk)
|
|
||||||
transaction.commit()
|
|
||||||
self.assertEqual(mod2.fld, 1)
|
|
||||||
|
|
||||||
work()
|
|
Loading…
Reference in New Issue
Block a user