Skip to content

Commit

Permalink
Used commit_on_success_unless_managed to make ORM operations atomic.
Browse files Browse the repository at this point in the history
  • Loading branch information
aaugustin committed Mar 11, 2013
1 parent 86fd920 commit 4dbd1b2
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 77 deletions.
82 changes: 32 additions & 50 deletions django/db/models/deletion.py
Expand Up @@ -50,24 +50,6 @@ def DO_NOTHING(collector, field, sub_objs, using):
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):
def __init__(self, using):
self.using = using
Expand Down Expand Up @@ -260,7 +242,6 @@ def sort(self):
self.data = SortedDict([(model, self.data[model])
for model in sorted_models])

@force_managed
def delete(self):
# sort instance collections
for model, instances in self.data.items():
Expand All @@ -271,40 +252,41 @@ def delete(self):
# end of a transaction.
self.sort()

# send pre_delete signals
for model, obj in self.instances_with_model():
if not model._meta.auto_created:
signals.pre_delete.send(
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(
with transaction.commit_on_success_unless_managed(using=self.using):
# send pre_delete signals
for model, obj in self.instances_with_model():
if not model._meta.auto_created:
signals.pre_delete.send(
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
for model, instances_for_fieldvalues in six.iteritems(self.field_updates):
for (field, value), instances in six.iteritems(instances_for_fieldvalues):
Expand Down
24 changes: 2 additions & 22 deletions django/db/models/query.py
Expand Up @@ -442,12 +442,7 @@ def bulk_create(self, objs, batch_size=None):
self._for_write = True
connection = connections[self.db]
fields = self.model._meta.local_fields
if transaction.get_autocommit(using=self.db):
transaction.enter_transaction_management(using=self.db, forced=True)
forced_managed = True
else:
forced_managed = False
try:
with transaction.commit_on_success_unless_managed(using=self.db):
if (connection.features.can_combine_inserts_with_and_without_auto_increment_pk
and self.model._meta.has_auto_field):
self._batched_insert(objs, fields, batch_size)
Expand All @@ -458,11 +453,6 @@ def bulk_create(self, objs, batch_size=None):
if objs_without_pk:
fields= [f for f in fields if not isinstance(f, AutoField)]
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

Expand Down Expand Up @@ -579,18 +569,8 @@ def update(self, **kwargs):
self._for_write = True
query = self.query.clone(sql.UpdateQuery)
query.add_update_values(kwargs)
if transaction.get_autocommit(using=self.db):
transaction.enter_transaction_management(using=self.db, forced=True)
forced_managed = True
else:
forced_managed = False
try:
with transaction.commit_on_success_unless_managed(using=self.db):
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
return rows
update.alters_data = True
Expand Down
9 changes: 4 additions & 5 deletions docs/topics/db/transactions.txt
Expand Up @@ -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
<autocommit-details>`.

..
Django uses transactions or savepoints automatically to guarantee the
integrity of ORM operations that require multiple queries, especially
:ref:`delete() <topics-db-queries-delete>` and :ref:`update()
<topics-db-queries-update>` queries.
Django uses transactions or savepoints automatically to guarantee the
integrity of ORM operations that require multiple queries, especially
:ref:`delete() <topics-db-queries-delete>` and :ref:`update()
<topics-db-queries-update>` queries.

.. versionchanged:: 1.6
Previous version of Django featured :ref:`a more complicated default
Expand Down

0 comments on commit 4dbd1b2

Please sign in to comment.