From 4dbd1b2dd8d997f439b0116748994fd538ff893a Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 8 Mar 2013 11:35:54 +0100 Subject: [PATCH] Used commit_on_success_unless_managed to make ORM operations atomic. --- django/db/models/deletion.py | 82 +++++++++++++-------------------- django/db/models/query.py | 24 +--------- docs/topics/db/transactions.txt | 9 ++-- 3 files changed, 38 insertions(+), 77 deletions(-) diff --git a/django/db/models/deletion.py b/django/db/models/deletion.py index 27184c9350..a04f05c73b 100644 --- a/django/db/models/deletion.py +++ b/django/db/models/deletion.py @@ -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 @@ -260,7 +242,6 @@ class Collector(object): 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(): @@ -271,40 +252,41 @@ class Collector(object): # 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): diff --git a/django/db/models/query.py b/django/db/models/query.py index 834fe363b4..22c7cfba32 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -442,12 +442,7 @@ class QuerySet(object): 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) @@ -458,11 +453,6 @@ class QuerySet(object): 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 @@ -579,18 +569,8 @@ class QuerySet(object): 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 diff --git a/docs/topics/db/transactions.txt b/docs/topics/db/transactions.txt index d5c22e17f5..b8fc0d4efa 100644 --- a/docs/topics/db/transactions.txt +++ b/docs/topics/db/transactions.txt @@ -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 `. -.. - Django uses transactions or savepoints automatically to guarantee the - integrity of ORM operations that require multiple queries, especially - :ref:`delete() ` and :ref:`update() - ` queries. +Django uses transactions or savepoints automatically to guarantee the +integrity of ORM operations that require multiple queries, especially +:ref:`delete() ` and :ref:`update() +` queries. .. versionchanged:: 1.6 Previous version of Django featured :ref:`a more complicated default