Browse Source

Used commit_on_success_unless_managed to make ORM operations atomic.

Aymeric Augustin 12 years ago
parent
commit
4dbd1b2dd8
3 changed files with 38 additions and 77 deletions
  1. 32 50
      django/db/models/deletion.py
  2. 2 22
      django/db/models/query.py
  3. 4 5
      docs/topics/db/transactions.txt

+ 32 - 50
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):

+ 2 - 22
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

+ 4 - 5
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
 <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