summaryrefslogtreecommitdiff
path: root/django/db/models/query.py
diff options
context:
space:
mode:
authorBrian Rosner <brosner@gmail.com>2008-06-23 21:05:02 +0000
committerBrian Rosner <brosner@gmail.com>2008-06-23 21:05:02 +0000
commit420f19aa353ee6568c93f34dfe2e6eef24bc528d (patch)
tree12f182ec4f958ac08a57f05786fdbefabae9910c /django/db/models/query.py
parentc929440fcd001b97b805f2229fb85deae3f86e05 (diff)
newforms-admin: Merged from trunk up to [7729].
git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@7730 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django/db/models/query.py')
-rw-r--r--django/db/models/query.py116
1 files changed, 106 insertions, 10 deletions
diff --git a/django/db/models/query.py b/django/db/models/query.py
index fb6d116a6e..8714cffb7f 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -16,6 +16,92 @@ ITER_CHUNK_SIZE = CHUNK_SIZE
# Pull into this namespace for backwards compatibility
EmptyResultSet = sql.EmptyResultSet
+class CyclicDependency(Exception):
+ pass
+
+class CollectedObjects(object):
+ """
+ A container that stores keys and lists of values along with
+ remembering the parent objects for all the keys.
+
+ This is used for the database object deletion routines so that we
+ can calculate the 'leaf' objects which should be deleted first.
+ """
+
+ def __init__(self):
+ self.data = {}
+ self.children = {}
+
+ def add(self, model, pk, obj, parent_model, nullable=False):
+ """
+ Adds an item.
+ model is the class of the object being added,
+ pk is the primary key, obj is the object itself,
+ parent_model is the model of the parent object
+ that this object was reached through, nullable should
+ be True if this relation is nullable.
+
+ If the item already existed in the structure,
+ returns true, otherwise false.
+ """
+ d = self.data.setdefault(model, SortedDict())
+ retval = pk in d
+ d[pk] = obj
+ # Nullable relationships can be ignored -- they
+ # are nulled out before deleting, and therefore
+ # do not affect the order in which objects have
+ # to be deleted.
+ if parent_model is not None and not nullable:
+ self.children.setdefault(parent_model, []).append(model)
+
+ return retval
+
+ def __contains__(self, key):
+ return self.data.__contains__(key)
+
+ def __getitem__(self, key):
+ return self.data[key]
+
+ def __nonzero__(self):
+ return bool(self.data)
+
+ def iteritems(self):
+ for k in self.ordered_keys():
+ yield k, self[k]
+
+ def items(self):
+ return list(self.iteritems())
+
+ def keys(self):
+ return self.ordered_keys()
+
+ def ordered_keys(self):
+ """
+ Returns the models in the order that they should be
+ dealth with i.e. models with no dependencies first.
+ """
+ dealt_with = SortedDict()
+ # Start with items that have no children
+ models = self.data.keys()
+ while len(dealt_with) < len(models):
+ found = False
+ for model in models:
+ children = self.children.setdefault(model, [])
+ if len([c for c in children if c not in dealt_with]) == 0:
+ dealt_with[model] = None
+ found = True
+ if not found:
+ raise CyclicDependency("There is a cyclic dependency of items to be processed.")
+
+ return dealt_with.keys()
+
+ def unordered_keys(self):
+ """
+ Fallback for the case where is a cyclic dependency but we
+ don't care.
+ """
+ return self.data.keys()
+
class QuerySet(object):
"Represents a lazy database lookup for a set of objects"
def __init__(self, model=None, query=None):
@@ -275,7 +361,7 @@ class QuerySet(object):
while 1:
# Collect all the objects to be deleted in this chunk, and all the
# objects that are related to the objects that are to be deleted.
- seen_objs = SortedDict()
+ seen_objs = CollectedObjects()
for object in del_query[:CHUNK_SIZE]:
object._collect_sub_objects(seen_objs)
@@ -682,19 +768,27 @@ def delete_objects(seen_objs):
Iterate through a list of seen classes, and remove any instances that are
referred to.
"""
- ordered_classes = seen_objs.keys()
- ordered_classes.reverse()
+ try:
+ ordered_classes = seen_objs.keys()
+ except CyclicDependency:
+ # if there is a cyclic dependency, we cannot in general delete
+ # the objects. However, if an appropriate transaction is set
+ # up, or if the database is lax enough, it will succeed.
+ # So for now, we go ahead and try anway.
+ ordered_classes = seen_objs.unordered_keys()
+ obj_pairs = {}
for cls in ordered_classes:
- seen_objs[cls] = seen_objs[cls].items()
- seen_objs[cls].sort()
+ items = seen_objs[cls].items()
+ items.sort()
+ obj_pairs[cls] = items
# Pre notify all instances to be deleted
- for pk_val, instance in seen_objs[cls]:
+ for pk_val, instance in items:
dispatcher.send(signal=signals.pre_delete, sender=cls,
instance=instance)
- pk_list = [pk for pk,instance in seen_objs[cls]]
+ pk_list = [pk for pk,instance in items]
del_query = sql.DeleteQuery(cls, connection)
del_query.delete_batch_related(pk_list)
@@ -705,15 +799,17 @@ def delete_objects(seen_objs):
# Now delete the actual data
for cls in ordered_classes:
- seen_objs[cls].reverse()
- pk_list = [pk for pk,instance in seen_objs[cls]]
+ items = obj_pairs[cls]
+ items.reverse()
+
+ pk_list = [pk for pk,instance in items]
del_query = sql.DeleteQuery(cls, connection)
del_query.delete_batch(pk_list)
# Last cleanup; set NULLs where there once was a reference to the
# object, NULL the primary key of the found objects, and perform
# post-notification.
- for pk_val, instance in seen_objs[cls]:
+ for pk_val, instance in items:
for field in cls._meta.fields:
if field.rel and field.null and field.rel.to in seen_objs:
setattr(instance, field.attname, None)