diff options
| author | Brian Rosner <brosner@gmail.com> | 2008-06-23 21:05:02 +0000 |
|---|---|---|
| committer | Brian Rosner <brosner@gmail.com> | 2008-06-23 21:05:02 +0000 |
| commit | 420f19aa353ee6568c93f34dfe2e6eef24bc528d (patch) | |
| tree | 12f182ec4f958ac08a57f05786fdbefabae9910c /django/db/models/query.py | |
| parent | c929440fcd001b97b805f2229fb85deae3f86e05 (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.py | 116 |
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) |
