Used commit_on_success_unless_managed to make ORM operations atomic.

This commit is contained in:
Aymeric Augustin 2013-03-08 11:35:54 +01:00
parent 86fd920f67
commit 4dbd1b2dd8
3 changed files with 38 additions and 77 deletions

View File

@ -50,24 +50,6 @@ def DO_NOTHING(collector, field, sub_objs, using):
pass pass
def force_managed(func):
@wraps(func)
def decorated(self, *args, **kwargs):
if transaction.get_autocommit(using=self.using):
transaction.enter_transaction_management(using=self.using, forced=True)
forced_managed = True
else:
forced_managed = False
try:
func(self, *args, **kwargs)
if forced_managed:
transaction.commit(using=self.using)
finally:
if forced_managed:
transaction.leave_transaction_management(using=self.using)
return decorated
class Collector(object): class Collector(object):
def __init__(self, using): def __init__(self, using):
self.using = using self.using = using
@ -260,7 +242,6 @@ class Collector(object):
self.data = SortedDict([(model, self.data[model]) self.data = SortedDict([(model, self.data[model])
for model in sorted_models]) for model in sorted_models])
@force_managed
def delete(self): def delete(self):
# sort instance collections # sort instance collections
for model, instances in self.data.items(): for model, instances in self.data.items():
@ -271,40 +252,41 @@ class Collector(object):
# end of a transaction. # end of a transaction.
self.sort() self.sort()
# send pre_delete signals with transaction.commit_on_success_unless_managed(using=self.using):
for model, obj in self.instances_with_model(): # send pre_delete signals
if not model._meta.auto_created: for model, obj in self.instances_with_model():
signals.pre_delete.send( if not model._meta.auto_created:
sender=model, instance=obj, using=self.using signals.pre_delete.send(
)
# fast deletes
for qs in self.fast_deletes:
qs._raw_delete(using=self.using)
# update fields
for model, instances_for_fieldvalues in six.iteritems(self.field_updates):
query = sql.UpdateQuery(model)
for (field, value), instances in six.iteritems(instances_for_fieldvalues):
query.update_batch([obj.pk for obj in instances],
{field.name: value}, self.using)
# reverse instance collections
for instances in six.itervalues(self.data):
instances.reverse()
# delete instances
for model, instances in six.iteritems(self.data):
query = sql.DeleteQuery(model)
pk_list = [obj.pk for obj in instances]
query.delete_batch(pk_list, self.using)
if not model._meta.auto_created:
for obj in instances:
signals.post_delete.send(
sender=model, instance=obj, using=self.using sender=model, instance=obj, using=self.using
) )
# fast deletes
for qs in self.fast_deletes:
qs._raw_delete(using=self.using)
# update fields
for model, instances_for_fieldvalues in six.iteritems(self.field_updates):
query = sql.UpdateQuery(model)
for (field, value), instances in six.iteritems(instances_for_fieldvalues):
query.update_batch([obj.pk for obj in instances],
{field.name: value}, self.using)
# reverse instance collections
for instances in six.itervalues(self.data):
instances.reverse()
# delete instances
for model, instances in six.iteritems(self.data):
query = sql.DeleteQuery(model)
pk_list = [obj.pk for obj in instances]
query.delete_batch(pk_list, self.using)
if not model._meta.auto_created:
for obj in instances:
signals.post_delete.send(
sender=model, instance=obj, using=self.using
)
# update collected instances # update collected instances
for model, instances_for_fieldvalues in six.iteritems(self.field_updates): for model, instances_for_fieldvalues in six.iteritems(self.field_updates):
for (field, value), instances in six.iteritems(instances_for_fieldvalues): for (field, value), instances in six.iteritems(instances_for_fieldvalues):

View File

@ -442,12 +442,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_fields fields = self.model._meta.local_fields
if transaction.get_autocommit(using=self.db): with transaction.commit_on_success_unless_managed(using=self.db):
transaction.enter_transaction_management(using=self.db, forced=True)
forced_managed = True
else:
forced_managed = False
try:
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)
@ -458,11 +453,6 @@ class QuerySet(object):
if objs_without_pk: if objs_without_pk:
fields= [f for f in fields if not isinstance(f, AutoField)] fields= [f for f in fields if not isinstance(f, AutoField)]
self._batched_insert(objs_without_pk, fields, batch_size) self._batched_insert(objs_without_pk, fields, batch_size)
if forced_managed:
transaction.commit(using=self.db)
finally:
if forced_managed:
transaction.leave_transaction_management(using=self.db)
return objs return objs
@ -579,18 +569,8 @@ 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)
if transaction.get_autocommit(using=self.db): with transaction.commit_on_success_unless_managed(using=self.db):
transaction.enter_transaction_management(using=self.db, forced=True)
forced_managed = True
else:
forced_managed = False
try:
rows = query.get_compiler(self.db).execute_sql(None) rows = query.get_compiler(self.db).execute_sql(None)
if forced_managed:
transaction.commit(using=self.db)
finally:
if forced_managed:
transaction.leave_transaction_management(using=self.db)
self._result_cache = None self._result_cache = None
return rows return rows
update.alters_data = True update.alters_data = True

View File

@ -16,11 +16,10 @@ Django's default behavior is to run in autocommit mode. Each query is
immediately committed to the database. :ref:`See below for details immediately committed to the database. :ref:`See below for details
<autocommit-details>`. <autocommit-details>`.
.. Django uses transactions or savepoints automatically to guarantee the
Django uses transactions or savepoints automatically to guarantee the integrity of ORM operations that require multiple queries, especially
integrity of ORM operations that require multiple queries, especially :ref:`delete() <topics-db-queries-delete>` and :ref:`update()
:ref:`delete() <topics-db-queries-delete>` and :ref:`update() <topics-db-queries-update>` queries.
<topics-db-queries-update>` queries.
.. versionchanged:: 1.6 .. versionchanged:: 1.6
Previous version of Django featured :ref:`a more complicated default Previous version of Django featured :ref:`a more complicated default