magic-removal: added transaction support to Django! see transactions.txt (in magic-removal) for the details.
git-svn-id: http://code.djangoproject.com/svn/django/branches/magic-removal@2457 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
7901c93328
commit
2a65b00381
|
@ -1,6 +1,6 @@
|
|||
"Daily cleanup file"
|
||||
|
||||
from django.db import backend, connection
|
||||
from django.db import backend, connection, transaction
|
||||
|
||||
DOCUMENTATION_DIRECTORY = '/home/html/documentation/'
|
||||
|
||||
|
@ -11,7 +11,7 @@ def clean_up():
|
|||
(backend.quote_name('core_sessions'), backend.quote_name('expire_date')))
|
||||
cursor.execute("DELETE FROM %s WHERE %s < NOW() - INTERVAL '1 week'" % \
|
||||
(backend.quote_name('registration_challenges'), backend.quote_name('request_date')))
|
||||
connection.commit()
|
||||
transaction.commit_unless_managed()
|
||||
|
||||
if __name__ == "__main__":
|
||||
clean_up()
|
||||
|
|
|
@ -194,6 +194,10 @@ TIME_FORMAT = 'P'
|
|||
# http://psyco.sourceforge.net/
|
||||
ENABLE_PSYCO = False
|
||||
|
||||
# Do you want to manage transactions manually?
|
||||
# Hint: you really don't!
|
||||
TRANSACTIONS_MANAGED = False
|
||||
|
||||
##############
|
||||
# MIDDLEWARE #
|
||||
##############
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"Database cache backend."
|
||||
|
||||
from django.core.cache.backends.base import BaseCache
|
||||
from django.db import connection
|
||||
from django.db import connection, transaction
|
||||
import base64, time
|
||||
from datetime import datetime
|
||||
try:
|
||||
|
@ -33,7 +33,7 @@ class CacheClass(BaseCache):
|
|||
now = datetime.now()
|
||||
if row[2] < now:
|
||||
cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % self._table, [key])
|
||||
connection.commit()
|
||||
transaction.commit_unless_managed()
|
||||
return default
|
||||
return pickle.loads(base64.decodestring(row[1]))
|
||||
|
||||
|
@ -58,12 +58,12 @@ class CacheClass(BaseCache):
|
|||
# To be threadsafe, updates/inserts are allowed to fail silently
|
||||
pass
|
||||
else:
|
||||
connection.commit()
|
||||
transaction.commit_unless_managed()
|
||||
|
||||
def delete(self, key):
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % self._table, [key])
|
||||
connection.commit()
|
||||
transaction.commit_unless_managed()
|
||||
|
||||
def has_key(self, key):
|
||||
cursor = connection.cursor()
|
||||
|
|
|
@ -218,7 +218,7 @@ get_sql_create.args = APP_ARGS
|
|||
|
||||
def get_sql_delete(app):
|
||||
"Returns a list of the DROP TABLE SQL statements for the given app."
|
||||
from django.db import backend, connection, models
|
||||
from django.db import backend, connection, models, transaction
|
||||
|
||||
try:
|
||||
cursor = connection.cursor()
|
||||
|
@ -233,7 +233,7 @@ def get_sql_delete(app):
|
|||
cursor.execute("SELECT 1 FROM %s LIMIT 1" % backend.quote_name('django_admin_log'))
|
||||
except:
|
||||
# The table doesn't exist, so it doesn't need to be dropped.
|
||||
connection.rollback()
|
||||
transaction.rollback_unless_managed()
|
||||
admin_log_exists = False
|
||||
else:
|
||||
admin_log_exists = True
|
||||
|
@ -252,7 +252,7 @@ def get_sql_delete(app):
|
|||
cursor.execute("SELECT 1 FROM %s LIMIT 1" % backend.quote_name(klass._meta.db_table))
|
||||
except:
|
||||
# The table doesn't exist, so it doesn't need to be dropped.
|
||||
connection.rollback()
|
||||
transaction.rollback_unless_managed()
|
||||
else:
|
||||
opts = klass._meta
|
||||
for f in opts.fields:
|
||||
|
@ -268,7 +268,7 @@ def get_sql_delete(app):
|
|||
cursor.execute("SELECT 1 FROM %s LIMIT 1" % backend.quote_name(klass._meta.db_table))
|
||||
except:
|
||||
# The table doesn't exist, so it doesn't need to be dropped.
|
||||
connection.rollback()
|
||||
transaction.rollback_unless_managed()
|
||||
else:
|
||||
output.append("DROP TABLE %s;" % backend.quote_name(klass._meta.db_table))
|
||||
if backend.supports_constraints and references_to_delete.has_key(klass):
|
||||
|
@ -290,7 +290,7 @@ def get_sql_delete(app):
|
|||
if cursor is not None:
|
||||
cursor.execute("SELECT 1 FROM %s LIMIT 1" % backend.quote_name(f.m2m_db_table()))
|
||||
except:
|
||||
connection.rollback()
|
||||
transaction.rollback_unless_managed()
|
||||
else:
|
||||
output.append("DROP TABLE %s;" % backend.quote_name(f.m2m_db_table()))
|
||||
|
||||
|
@ -403,7 +403,7 @@ get_sql_all.args = APP_ARGS
|
|||
# TODO: Check for model validation errors before executing SQL
|
||||
def syncdb():
|
||||
"Creates the database tables for all apps in INSTALLED_APPS whose tables haven't already been created."
|
||||
from django.db import backend, connection, models, get_creation_module, get_introspection_module
|
||||
from django.db import backend, connection, transaction, models, get_creation_module, get_introspection_module
|
||||
introspection_module = get_introspection_module()
|
||||
data_types = get_creation_module().DATA_TYPES
|
||||
|
||||
|
@ -470,7 +470,7 @@ def syncdb():
|
|||
backend.quote_name(r_col), backend.quote_name(table), backend.quote_name(col))
|
||||
cursor.execute(sql)
|
||||
|
||||
connection.commit()
|
||||
transaction.commit_unless_managed()
|
||||
syncdb.args = ''
|
||||
|
||||
def get_admin_index(app):
|
||||
|
@ -499,7 +499,7 @@ get_admin_index.args = APP_ARGS
|
|||
|
||||
def install(app):
|
||||
"Executes the equivalent of 'get_sql_all' in the current database."
|
||||
from django.db import connection
|
||||
from django.db import connection, transaction
|
||||
from cStringIO import StringIO
|
||||
app_name = app.__name__[app.__name__.rindex('.')+1:]
|
||||
app_label = app_name.split('.')[-1]
|
||||
|
@ -526,15 +526,15 @@ def install(app):
|
|||
Hint: Look at the output of 'django-admin.py sqlall %s'. That's the SQL this command wasn't able to run.
|
||||
The full error: %s\n""" % \
|
||||
(app_name, app_label, e))
|
||||
connection.rollback()
|
||||
transaction.rollback_unless_managed()
|
||||
sys.exit(1)
|
||||
connection.commit()
|
||||
transaction.commit_unless_managed()
|
||||
install.help_doc = "Executes ``sqlall`` for the given app(s) in the current database."
|
||||
install.args = APP_ARGS
|
||||
|
||||
def reset(app):
|
||||
"Executes the equivalent of 'get_sql_reset' in the current database."
|
||||
from django.db import connection
|
||||
from django.db import connection, transaction
|
||||
from cStringIO import StringIO
|
||||
app_name = app.__name__[app.__name__.rindex('.')+1:]
|
||||
app_label = app_name.split('.')[-1]
|
||||
|
@ -568,9 +568,9 @@ Type 'yes' to continue, or 'no' to cancel: """)
|
|||
Hint: Look at the output of 'django-admin.py sqlreset %s'. That's the SQL this command wasn't able to run.
|
||||
The full error: %s\n""" % \
|
||||
(app_name, app_label, e))
|
||||
connection.rollback()
|
||||
transaction.rollback_unless_managed()
|
||||
sys.exit(1)
|
||||
connection.commit()
|
||||
transaction.commit_unless_managed()
|
||||
else:
|
||||
print "Reset cancelled."
|
||||
reset.help_doc = "Executes ``sqlreset`` for the given app(s) in the current database."
|
||||
|
@ -1035,7 +1035,7 @@ runserver.args = '[optional port number, or ipaddr:port]'
|
|||
|
||||
def createcachetable(tablename):
|
||||
"Creates the table needed to use the SQL cache backend"
|
||||
from django.db import backend, get_creation_module, models
|
||||
from django.db import backend, connection, transaction, get_creation_module, models
|
||||
data_types = get_creation_module().DATA_TYPES
|
||||
fields = (
|
||||
# "key" is a reserved word in MySQL, so use "cache_key" instead.
|
||||
|
@ -1066,7 +1066,7 @@ def createcachetable(tablename):
|
|||
curs.execute("\n".join(full_statement))
|
||||
for statement in index_output:
|
||||
curs.execute(statement)
|
||||
connection.commit()
|
||||
transaction.commit_unless_managed()
|
||||
createcachetable.args = "[tablename]"
|
||||
|
||||
def run_shell(use_plain=False):
|
||||
|
|
|
@ -38,4 +38,7 @@ dispatcher.connect(reset_queries, signal=signals.request_started)
|
|||
|
||||
# Register an event that rolls back the connection
|
||||
# when a Django request has an exception.
|
||||
dispatcher.connect(lambda: connection.rollback(), signal=signals.got_request_exception)
|
||||
def _rollback_on_exception():
|
||||
from django.db import transaction
|
||||
transaction.rollback_unless_managed()
|
||||
dispatcher.connect(_rollback_on_exception, signal=signals.got_request_exception)
|
||||
|
|
|
@ -64,10 +64,10 @@ class DatabaseWrapper:
|
|||
return base.CursorDebugWrapper(cursor, self)
|
||||
return cursor
|
||||
|
||||
def commit(self):
|
||||
def _commit(self):
|
||||
return self.connection.commit()
|
||||
|
||||
def rollback(self):
|
||||
def _rollback(self):
|
||||
if self.connection:
|
||||
return self.connection.rollback()
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@ class DatabaseError(Exception):
|
|||
|
||||
class DatabaseWrapper:
|
||||
cursor = complain
|
||||
commit = complain
|
||||
rollback = complain
|
||||
_commit = complain
|
||||
_rollback = complain
|
||||
|
||||
def close(self):
|
||||
pass # close()
|
||||
|
|
|
@ -71,10 +71,10 @@ class DatabaseWrapper:
|
|||
return util.CursorDebugWrapper(MysqlDebugWrapper(cursor), self)
|
||||
return cursor
|
||||
|
||||
def commit(self):
|
||||
def _commit(self):
|
||||
self.connection.commit()
|
||||
|
||||
def rollback(self):
|
||||
def _rollback(self):
|
||||
if self.connection:
|
||||
try:
|
||||
self.connection.rollback()
|
||||
|
|
|
@ -37,10 +37,10 @@ class DatabaseWrapper:
|
|||
return util.CursorDebugWrapper(cursor, self)
|
||||
return cursor
|
||||
|
||||
def commit(self):
|
||||
def _commit(self):
|
||||
return self.connection.commit()
|
||||
|
||||
def rollback(self):
|
||||
def _rollback(self):
|
||||
if self.connection:
|
||||
return self.connection.rollback()
|
||||
|
||||
|
|
|
@ -39,10 +39,10 @@ class DatabaseWrapper:
|
|||
else:
|
||||
return cursor
|
||||
|
||||
def commit(self):
|
||||
def _commit(self):
|
||||
self.connection.commit()
|
||||
|
||||
def rollback(self):
|
||||
def _rollback(self):
|
||||
if self.connection:
|
||||
self.connection.rollback()
|
||||
|
||||
|
@ -67,7 +67,6 @@ class SQLiteCursorWrapper(Database.Cursor):
|
|||
return Database.Cursor.executemany(self, query, params)
|
||||
|
||||
def convert_query(self, query, num_params):
|
||||
# XXX this seems too simple to be correct... is this right?
|
||||
return query % tuple("?" * num_params)
|
||||
|
||||
supports_constraints = False
|
||||
|
|
|
@ -5,7 +5,7 @@ from django.db.models.fields.related import OneToOne, ManyToOne
|
|||
from django.db.models.related import RelatedObject
|
||||
from django.db.models.query import orderlist2sql, delete_objects
|
||||
from django.db.models.options import Options, AdminOptions
|
||||
from django.db import connection, backend
|
||||
from django.db import connection, backend, transaction
|
||||
from django.db.models import signals
|
||||
from django.db.models.loading import register_models
|
||||
from django.dispatch import dispatcher
|
||||
|
@ -184,7 +184,7 @@ class Model(object):
|
|||
','.join(placeholders)), db_values)
|
||||
if self._meta.has_auto_field and not pk_set:
|
||||
setattr(self, self._meta.pk.attname, backend.get_last_insert_id(cursor, self._meta.db_table, self._meta.pk.column))
|
||||
connection.commit()
|
||||
transaction.commit_unless_managed()
|
||||
|
||||
# Run any post-save hooks.
|
||||
dispatcher.send(signal=signals.post_save, sender=self.__class__, instance=self)
|
||||
|
@ -340,7 +340,7 @@ class Model(object):
|
|||
backend.quote_name(rel_field.m2m_column_name()),
|
||||
backend.quote_name(rel_field.m2m_reverse_name()))
|
||||
cursor.executemany(sql, [(this_id, i) for i in id_list])
|
||||
connection.commit()
|
||||
transaction.commit_unless_managed()
|
||||
|
||||
############################################
|
||||
# HELPER FUNCTIONS (CURRIED MODEL METHODS) #
|
||||
|
@ -357,7 +357,7 @@ def method_set_order(ordered_obj, self, id_list):
|
|||
backend.quote_name(ordered_obj.pk.column))
|
||||
rel_val = getattr(self, ordered_obj.order_with_respect_to.rel.field_name)
|
||||
cursor.executemany(sql, [(i, rel_val, j) for i, j in enumerate(id_list)])
|
||||
connection.commit()
|
||||
transaction.commit_unless_managed()
|
||||
|
||||
def method_get_order(ordered_obj, self):
|
||||
cursor = connection.cursor()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from django.db import backend, connection
|
||||
from django.db import backend, connection, transaction
|
||||
from django.db.models import signals
|
||||
from django.db.models.fields import AutoField, Field, IntegerField
|
||||
from django.db.models.related import RelatedObject
|
||||
|
@ -231,7 +231,7 @@ def _add_m2m_items(rel_manager_inst, managerclass, rel_model, join_table, source
|
|||
cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
|
||||
(join_table, source_col_name, target_col_name),
|
||||
[source_pk_val, obj_id])
|
||||
connection.commit()
|
||||
transaction.commit_unless_managed()
|
||||
|
||||
def _remove_m2m_items(rel_model, join_table, source_col_name,
|
||||
target_col_name, source_pk_val, *objs):
|
||||
|
@ -255,7 +255,7 @@ def _remove_m2m_items(rel_model, join_table, source_col_name,
|
|||
cursor.execute("DELETE FROM %s WHERE %s = %%s AND %s = %%s" % \
|
||||
(join_table, source_col_name, target_col_name),
|
||||
[source_pk_val, obj._get_pk_val()])
|
||||
connection.commit()
|
||||
transaction.commit_unless_managed()
|
||||
|
||||
def _clear_m2m_items(join_table, source_col_name, source_pk_val):
|
||||
# Utility function used by the ManyRelatedObjectsDescriptors
|
||||
|
@ -268,7 +268,7 @@ def _clear_m2m_items(join_table, source_col_name, source_pk_val):
|
|||
cursor.execute("DELETE FROM %s WHERE %s = %%s" % \
|
||||
(join_table, source_col_name),
|
||||
[source_pk_val])
|
||||
connection.commit()
|
||||
transaction.commit_unless_managed()
|
||||
|
||||
class ManyRelatedObjectsDescriptor(object):
|
||||
# This class provides the functionality that makes the related-object
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from django.db import backend, connection
|
||||
from django.db import backend, connection, transaction
|
||||
from django.db.models.fields import DateField, FieldDoesNotExist
|
||||
from django.db.models import signals
|
||||
from django.dispatch import dispatcher
|
||||
|
@ -846,4 +846,4 @@ def delete_objects(seen_objs):
|
|||
setattr(instance, cls._meta.pk.attname, None)
|
||||
dispatcher.send(signal=signals.post_delete, sender=cls, instance=instance)
|
||||
|
||||
connection.commit()
|
||||
transaction.commit_unless_managed()
|
||||
|
|
|
@ -0,0 +1,225 @@
|
|||
"""
|
||||
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 thread
|
||||
from django.db import connection
|
||||
from django.conf import settings
|
||||
|
||||
class TransactionManagementError(Exception):
|
||||
"""
|
||||
This is the exception that is thrown when
|
||||
something bad happens with transaction management.
|
||||
"""
|
||||
pass
|
||||
|
||||
# The state is a dictionary of lists. The key to the dict is the current
|
||||
# thread and the list is handled as a stack of values.
|
||||
state = {}
|
||||
|
||||
# The dirty flag is set by *_unless_managed functions to denote that the
|
||||
# code under transaction management has changed things to require a
|
||||
# database commit.
|
||||
dirty = {}
|
||||
|
||||
def enter_transaction_management():
|
||||
"""
|
||||
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 allways false
|
||||
when no current block is running).
|
||||
"""
|
||||
thread_ident = thread.get_ident()
|
||||
if state.has_key(thread_ident) and state[thread_ident]:
|
||||
state[thread_ident].append(state[thread_ident][-1])
|
||||
else:
|
||||
state[thread_ident] = []
|
||||
state[thread_ident].append(settings.TRANSACTIONS_MANAGED)
|
||||
if not dirty.has_key(thread_ident):
|
||||
dirty[thread_ident] = False
|
||||
|
||||
def leave_transaction_management():
|
||||
"""
|
||||
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).
|
||||
"""
|
||||
thread_ident = thread.get_ident()
|
||||
if state.has_key(thread_ident) and state[thread_ident]:
|
||||
del state[thread_ident][-1]
|
||||
else:
|
||||
raise TransactionManagementError("This code isn't under transaction management")
|
||||
if dirty.get(thread_ident, False):
|
||||
# I fixed it for you this time, but don't do it again!
|
||||
rollback()
|
||||
raise TransactionManagementError("Transaction managed block ended with pending COMMIT/ROLLBACK")
|
||||
dirty[thread_ident] = False
|
||||
|
||||
def is_dirty():
|
||||
"""
|
||||
Checks if the current transaction requires a commit for changes to happen.
|
||||
"""
|
||||
return dirty.get(thread.get_ident(), False)
|
||||
|
||||
def set_dirty():
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
thread_ident = thread.get_ident()
|
||||
if dirty.has_key(thread_ident):
|
||||
dirty[thread_ident] = True
|
||||
else:
|
||||
raise TransactionManagementError("This code isn't under transaction management")
|
||||
|
||||
def set_clean():
|
||||
"""
|
||||
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 there should happen a
|
||||
commit or rollback.
|
||||
"""
|
||||
thread_ident = thread.get_ident()
|
||||
if dirty.has_key(thread_ident):
|
||||
dirty[thread_ident] = False
|
||||
else:
|
||||
raise TransactionManagementError("This code isn't under transaction management")
|
||||
|
||||
def is_managed():
|
||||
"""
|
||||
Checks whether the transaction manager is in manual or in auto state.
|
||||
"""
|
||||
thread_ident = thread.get_ident()
|
||||
if state.has_key(thread_ident):
|
||||
if state[thread_ident]:
|
||||
return state[thread_ident][-1]
|
||||
return settings.TRANSACTIONS_MANAGED
|
||||
|
||||
def managed(flag=True):
|
||||
"""
|
||||
Puts the transaction manager into a manual state - managed transactions have
|
||||
to be committed explicitely by the user. If you switch off transaction
|
||||
management and there is a pending commit/rollback, the data will be
|
||||
commited.
|
||||
"""
|
||||
thread_ident = thread.get_ident()
|
||||
top = state.get(thread_ident, None)
|
||||
if top:
|
||||
top[-1] = flag
|
||||
if not flag and is_dirty():
|
||||
connection._commit()
|
||||
set_clean()
|
||||
else:
|
||||
raise TransactionManagementError("This code isn't under transaction management")
|
||||
|
||||
def commit_unless_managed():
|
||||
"""
|
||||
Commits changes if the system is not in managed transaction mode.
|
||||
"""
|
||||
if not is_managed():
|
||||
connection._commit()
|
||||
else:
|
||||
set_dirty()
|
||||
|
||||
def rollback_unless_managed():
|
||||
"""
|
||||
Rolls back changes if the system is not in managed transaction mode.
|
||||
"""
|
||||
if not is_managed():
|
||||
connection._rollback()
|
||||
else:
|
||||
set_dirty()
|
||||
|
||||
def commit():
|
||||
"""
|
||||
Does the commit itself and resets the dirty flag.
|
||||
"""
|
||||
connection._commit()
|
||||
set_clean()
|
||||
|
||||
def rollback():
|
||||
"""
|
||||
This function does the rollback itself and resets the dirty flag.
|
||||
"""
|
||||
connection._rollback()
|
||||
set_clean()
|
||||
|
||||
##############
|
||||
# DECORATORS #
|
||||
##############
|
||||
|
||||
def autocommit(func):
|
||||
"""
|
||||
Decorator that activates commit on save. This is Django's default behavour;
|
||||
this decorator is useful if you globally activated transaction management in
|
||||
your settings file and want the default behaviour in some view functions.
|
||||
"""
|
||||
|
||||
def _autocommit(*args, **kw):
|
||||
try:
|
||||
enter_transaction_management()
|
||||
managed(False)
|
||||
return func(*args, **kw)
|
||||
finally:
|
||||
leave_transaction_management()
|
||||
|
||||
return _autocommit
|
||||
|
||||
def commit_on_success(func):
|
||||
"""
|
||||
This decorator activates commit on response. This way if the viewfunction
|
||||
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.
|
||||
"""
|
||||
|
||||
def _commit_on_success(*args, **kw):
|
||||
try:
|
||||
enter_transaction_management()
|
||||
managed(True)
|
||||
try:
|
||||
res = func(*args, **kw)
|
||||
except Exception, e:
|
||||
if is_dirty():
|
||||
rollback()
|
||||
raise e
|
||||
else:
|
||||
if is_dirty():
|
||||
commit()
|
||||
return res
|
||||
finally:
|
||||
leave_transaction_management()
|
||||
|
||||
return _commit_on_success
|
||||
|
||||
def commit_manually(func):
|
||||
"""
|
||||
Decorator that activates manual transaction control. It just disables
|
||||
automatic transaction control and doesn't do any commit/rollback of it's own
|
||||
- it's up to the user to call the commit and rollback functions themselves.
|
||||
"""
|
||||
|
||||
def _commit_manually(*args, **kw):
|
||||
try:
|
||||
enter_transaction_management()
|
||||
managed(True)
|
||||
return func(*args, **kw)
|
||||
finally:
|
||||
leave_transaction_management()
|
||||
|
||||
return _commit_manually
|
||||
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
|
||||
class TransactionMiddleware:
|
||||
"""
|
||||
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 successfull response is created. If an
|
||||
exception happens, the database is rolled back.
|
||||
"""
|
||||
|
||||
def process_request(self, request):
|
||||
"""Enters transaction management"""
|
||||
transaction.enter_transaction_management()
|
||||
transaction.managed(True)
|
||||
|
||||
def process_exception(self, request, exception):
|
||||
"""Rolls back the database and leaves transaction management"""
|
||||
if transaction.is_dirty():
|
||||
transaction.rollback()
|
||||
transaction.leave_transaction_management()
|
||||
|
||||
def process_response(self, request, response):
|
||||
"""Commits and leaves transaction management."""
|
||||
if transaction.is_managed():
|
||||
if transaction.is_dirty():
|
||||
transaction.commit()
|
||||
transaction.leave_transaction_management()
|
||||
return response
|
|
@ -102,6 +102,23 @@ Enables session support. See the `session documentation`_.
|
|||
|
||||
.. _`session documentation`: http://www.djangoproject.com/documentation/sessions/
|
||||
|
||||
django.middleware.transaction.TransactionMiddleware
|
||||
---------------------------------------------------
|
||||
|
||||
Binds commit and rollback to the request/response phase. If a view function runs
|
||||
successfully, a commit is done. If it fails with an exception, a rollback is
|
||||
done.
|
||||
|
||||
The order of this middleware in the stack is important: middleware modules
|
||||
running outside of it run with commit-on-save - the default Django behavior.
|
||||
Middleware modules running inside it (coming later in the stack) will be under
|
||||
the same transaction control as the view functions.
|
||||
|
||||
See the `transaction management documentation`_.
|
||||
|
||||
.. _`transaction management documentation`: http://www.djangoproject.com/documentation/transaction/
|
||||
|
||||
|
||||
Writing your own middleware
|
||||
===========================
|
||||
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
==============================
|
||||
Managing database transactions
|
||||
==============================
|
||||
|
||||
Django gives you a few ways to control how database transactions are managed.
|
||||
|
||||
Django's default transaction behavior
|
||||
=====================================
|
||||
|
||||
The default behavior of Django is to commit on special model functions. If you
|
||||
call ``model.save()`` or ``model.delete()``, that change will be committed immediately.
|
||||
|
||||
This is much like the auto-commit setting for most databases: as soon as you
|
||||
perform an action that needs to write to the database, Django produces the
|
||||
insert/update/delete statements and then does the commit. There is no implicit
|
||||
rollback in Django.
|
||||
|
||||
Tying transactions to HTTP requests
|
||||
===================================
|
||||
|
||||
A useful way to handle transactions is to tie them to the request and response
|
||||
phases.
|
||||
|
||||
When a request starts, you start a transaction. If the response is produced
|
||||
without problems, any transactions are committed. If the view function produces
|
||||
and exception, a rollback happens. This is one of the more intuitive ways to
|
||||
handle transactions. To activate this feature, just add the
|
||||
``TransactionMiddleware`` middleware to your stack::
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.sessions.SessionMiddleware",
|
||||
"django.middleware.cache.CacheMiddleware",
|
||||
"django.middleware.transaction.TransactionMiddleware",
|
||||
)
|
||||
|
||||
The order is quite important: the transaction middleware will be relevant not
|
||||
only for the view functions called, but for all middleware modules that come
|
||||
after it. So if you use the session middleware after the transaction middleware,
|
||||
session creation will be part of the transaction.
|
||||
|
||||
The cache middleware isn't affected as it uses it's own database cursor (that is
|
||||
mapped to it's own database connection internally) and only the database based
|
||||
cache is affected.
|
||||
|
||||
Controlling transaction management in views
|
||||
===========================================
|
||||
|
||||
For many people, implicit request-based transactions will work wonderfully.
|
||||
However, if you need to control the way that transactions are managed,
|
||||
there are a set of decorators that you can apply to a function to change
|
||||
the way transactions are handled.
|
||||
|
||||
.. note::
|
||||
|
||||
Although the examples below use view functions as examples, these
|
||||
decorators can be applied to non-view functions as well.
|
||||
|
||||
``autocommit``
|
||||
--------------
|
||||
|
||||
You can use the ``autocommit`` decorator to switch a view function to the
|
||||
default commit behavior of Django, regardless of the global setting. Just use
|
||||
the decorator like this::
|
||||
|
||||
from django.db.transaction import autocommit
|
||||
|
||||
@transaction.autocommit
|
||||
def viewfunc(request):
|
||||
....
|
||||
|
||||
Within ``viewfunc`` transactions will be comitted as soon as you call
|
||||
``model.save()``, ``model.delete()``, or any similar function that writes to the
|
||||
database.
|
||||
|
||||
``commit_on_success``
|
||||
---------------------
|
||||
|
||||
You can use the ``commit_on_success`` decorator to use a single transaction for
|
||||
all the work done in a function::
|
||||
|
||||
from django.db.transaction import commit_on_success
|
||||
|
||||
@commit_on_success
|
||||
def viewfunc(request):
|
||||
....
|
||||
|
||||
If the function returns successfully then all work done will be committed. If an
|
||||
exception is raised beyond the function, however, the transaction will be rolled
|
||||
back.
|
||||
|
||||
``commit_manually``
|
||||
-------------------
|
||||
|
||||
Sometimes you need full control over your transactions. In that case, you can use the
|
||||
``commit_manually`` decorator which will make you run your own transaction management.
|
||||
|
||||
If you don't commit or rollback and did change data (so that the current transaction
|
||||
is marked as dirty), you will get a ``TransactionManagementError`` exception saying so.
|
||||
|
||||
Manual transaction management looks like::
|
||||
|
||||
from django.db import transaction
|
||||
|
||||
@transaction.commit_manually
|
||||
def viewfunc(request):
|
||||
...
|
||||
transaction.commit()
|
||||
...
|
||||
try:
|
||||
...
|
||||
except:
|
||||
transaction.rollback()
|
||||
else:
|
||||
transaction.commit()
|
||||
|
||||
..admonition:: An important note to users of earlier django releases:
|
||||
|
||||
The database ``connection.commit`` and ``connection.rollback`` functions
|
||||
(also called ``db.commit`` and ``db.rollback`` in 0.91 and earlier), no
|
||||
longer exist and have been replaced by the ``transaction.commit`` and
|
||||
``transaction.rollback`` commands.
|
||||
|
||||
How to globally deactivate transaction management
|
||||
=================================================
|
||||
|
||||
Control freaks can totally disable all transaction management by setting
|
||||
``DISABLE_TRANSACTION_MANAGEMENT`` to ``True`` in your settings file.
|
||||
|
||||
If you do this, there will be no management whatsoever. The middleware will no
|
||||
longer implicitly commit transactions, and you'll need to roll management
|
||||
yourself. This even will require you to commit changes done by middleware
|
||||
somewhere else.
|
||||
|
||||
Thus, this is best used in situations where you want to run your own transaction
|
||||
controlling middleware or do something really strange. In almost all situations
|
||||
you'll be better off using the default behavior or the transaction middleware
|
||||
and only modify selected functions as needed.
|
|
@ -0,0 +1,91 @@
|
|||
"""
|
||||
XXX. Transactions
|
||||
|
||||
Django handles transactions in three different ways. The default is to commit
|
||||
each transaction upon a write, but you can decorate a function to get
|
||||
commit-on-sucess behavior, or else you can manage the transaction manually.
|
||||
"""
|
||||
|
||||
from django.db import models
|
||||
|
||||
class Reporter(models.Model):
|
||||
first_name = models.CharField(maxlength=30)
|
||||
last_name = models.CharField(maxlength=30)
|
||||
email = models.EmailField()
|
||||
|
||||
def __repr__(self):
|
||||
return "%s %s" % (self.first_name, self.last_name)
|
||||
|
||||
API_TESTS = """
|
||||
>>> from django.db import connection, transaction
|
||||
|
||||
# the default behavior is to autocommit after each save() action
|
||||
>>> def create_a_reporter_then_fail(first, last):
|
||||
... a = Reporter(first_name=first, last_name=last)
|
||||
... a.save()
|
||||
... raise Exception("I meant to do that")
|
||||
...
|
||||
>>> create_a_reporter_then_fail("Alice", "Smith")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
Exception: I meant to do that
|
||||
|
||||
# The object created before the exception still exists
|
||||
>>> Reporter.objects.all()
|
||||
[Alice Smith]
|
||||
|
||||
# the autocommit decorator works exactly the same as the default behavior
|
||||
>>> autocomitted_create_then_fail = transaction.autocommit(create_a_reporter_then_fail)
|
||||
>>> autocomitted_create_then_fail("Ben", "Jones")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
Exception: I meant to do that
|
||||
|
||||
# Same behavior as before
|
||||
>>> Reporter.objects.all()
|
||||
[Alice Smith, Ben Jones]
|
||||
|
||||
# With the commit_on_success decorator, the transaction is only comitted if the
|
||||
# function doesn't throw an exception
|
||||
>>> committed_on_success = transaction.commit_on_success(create_a_reporter_then_fail)
|
||||
>>> committed_on_success("Carol", "Doe")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
Exception: I meant to do that
|
||||
|
||||
# This time the object never got saved
|
||||
>>> Reporter.objects.all()
|
||||
[Alice Smith, Ben Jones]
|
||||
|
||||
# If there aren't any exceptions, the data will get saved
|
||||
>>> def remove_a_reporter():
|
||||
... r = Reporter.objects.get(first_name="Alice")
|
||||
... r.delete()
|
||||
...
|
||||
>>> remove_comitted_on_success = transaction.commit_on_success(remove_a_reporter)
|
||||
>>> remove_comitted_on_success()
|
||||
>>> Reporter.objects.all()
|
||||
[Ben Jones]
|
||||
|
||||
# You can manually manage transactions if you really want to, but you
|
||||
# have to remember to commit/rollback
|
||||
>>> def manually_managed():
|
||||
... r = Reporter(first_name="Carol", last_name="Doe")
|
||||
... r.save()
|
||||
... transaction.commit()
|
||||
>>> manually_managed = transaction.commit_manually(manually_managed)
|
||||
>>> manually_managed()
|
||||
>>> Reporter.objects.all()
|
||||
[Ben Jones, Carol Doe]
|
||||
|
||||
# If you forget, you'll get bad errors
|
||||
>>> def manually_managed_mistake():
|
||||
... r = Reporter(first_name="David", last_name="Davidson")
|
||||
... r.save()
|
||||
... # oops, I forgot to commit/rollback!
|
||||
>>> manually_managed_mistake = transaction.commit_manually(manually_managed_mistake)
|
||||
>>> manually_managed_mistake()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TransactionManagementError: Transaction managed block ended with pending COMMIT/ROLLBACK
|
||||
"""
|
|
@ -39,13 +39,13 @@ class DjangoDoctestRunner(doctest.DocTestRunner):
|
|||
"Code: %r\nLine: %s\nExpected: %r\nGot: %r" % (example.source.strip(), example.lineno, example.want, got))
|
||||
|
||||
def report_unexpected_exception(self, out, test, example, exc_info):
|
||||
from django.db import connection
|
||||
from django.db import transaction
|
||||
tb = ''.join(traceback.format_exception(*exc_info)[1:])
|
||||
log_error(test.name, "API test raised an exception",
|
||||
"Code: %r\nLine: %s\nException: %s" % (example.source.strip(), example.lineno, tb))
|
||||
# Rollback, in case of database errors. Otherwise they'd have
|
||||
# side effects on other tests.
|
||||
connection.rollback()
|
||||
transaction.rollback_unless_managed()
|
||||
|
||||
normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s)
|
||||
|
||||
|
|
Loading…
Reference in New Issue