diff --git a/django/db/models/base.py b/django/db/models/base.py index f2dd924381..bffd504f53 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -190,11 +190,16 @@ class Model(object): save.alters_data = True def __collect_sub_objects(self, seen_objs): + """ + Recursively populates seen_objs with all objects related to this object. + When done, seen_objs will be in the format: + {model_class: {pk_val: obj, pk_val: obj, ...}, + model_class: {pk_val: obj, pk_val: obj, ...}, ...} + """ pk_val = self._get_pk_val() - if pk_val in seen_objs.setdefault(self.__class__, {}): return - seen_objs.setdefault(self.__class__, {})[pk_val] = (self, True) + seen_objs.setdefault(self.__class__, {})[pk_val] = self for related in self._meta.get_all_related_objects(): rel_opts_name = related.get_method_name_part() @@ -209,63 +214,48 @@ class Model(object): for sub_obj in getattr(self, 'get_%s_list' % rel_opts_name)(): sub_obj.__collect_sub_objects(seen_objs) - def delete(self, ignore_objects=None): - assert self._get_pk_val() is not None, "%r can't be deleted because it doesn't have an ID." - seen_objs = {} - if ignore_objects: - for obj in ignore_objects: - ignore_objs.setdefault(self.__class__,{})[obj._get_pk_val()] = (obj, False) - + def delete(self): + assert self._get_pk_val() is not None, "%s object can't be deleted because its %s attribute is set to None." % (self._meta.object_name, self._meta.pk.attname) seen_objs = {} self.__collect_sub_objects(seen_objs) - #TODO: create a total class ordering rather than this sorting, which is - # only a partial ordering, and also is done each delete.. - seen_cls = set(seen_objs.keys()) - cls_order = list(seen_cls) - cls_order.sort(cmp_cls) + seen_classes = set(seen_objs.keys()) + ordered_classes = list(seen_classes) + ordered_classes.sort(cmp_cls) cursor = connection.cursor() - for cls in cls_order: + for cls in ordered_classes: seen_objs[cls] = seen_objs[cls].items() seen_objs[cls].sort() - for pk_val, (instance, do_delete) in seen_objs[cls]: + for pk_val, instance in seen_objs[cls]: + dispatcher.send(signal=signals.pre_delete, sender=cls, instance=instance) - # Run any pre-delete hooks. - if do_delete: - dispatcher.send(signal=signals.pre_delete, sender=cls, instance=instance) - - for related in cls._meta.get_all_related_many_to_many_objects(): - cursor.execute("DELETE FROM %s WHERE %s=%%s" % \ - (backend.quote_name(related.field.get_m2m_db_table(related.opts)), - backend.quote_name(cls._meta.object_name.lower() + '_id')), - [pk_val]) - for f in cls._meta.many_to_many: - cursor.execute("DELETE FROM %s WHERE %s=%%s" % \ - (backend.quote_name(f.get_m2m_db_table(cls._meta)), - backend.quote_name(cls._meta.object_name.lower() + '_id')), - [pk_val]) - - for field in cls._meta.fields: - if field.rel and field.null and field.rel.to in seen_cls: - cursor.execute("UPDATE %s SET %s = NULL WHERE %s=%%s" % \ - (backend.quote_name(cls._meta.db_table), backend.quote_name(field.column), - backend.quote_name(cls._meta.pk.column)), [pk_val]) - setattr(instance, field.attname, None) - - for cls in cls_order: - seen_objs[cls].reverse() - - for pk_val, (instance, do_delete) in seen_objs[cls]: - if do_delete: + for related in cls._meta.get_all_related_many_to_many_objects(): cursor.execute("DELETE FROM %s WHERE %s=%%s" % \ - (backend.quote_name(cls._meta.db_table), backend.quote_name(cls._meta.pk.column)), + (backend.quote_name(related.field.get_m2m_db_table(related.opts)), + backend.quote_name(cls._meta.object_name.lower() + '_id')), [pk_val]) + for f in cls._meta.many_to_many: + cursor.execute("DELETE FROM %s WHERE %s=%%s" % \ + (backend.quote_name(f.get_m2m_db_table(cls._meta)), + backend.quote_name(cls._meta.object_name.lower() + '_id')), + [pk_val]) + for field in cls._meta.fields: + if field.rel and field.null and field.rel.to in seen_classes: + cursor.execute("UPDATE %s SET %s=NULL WHERE %s=%%s" % \ + (backend.quote_name(cls._meta.db_table), backend.quote_name(field.column), + backend.quote_name(cls._meta.pk.column)), [pk_val]) + setattr(instance, field.attname, None) - setattr(instance, cls._meta.pk.attname, None) - - dispatcher.send(signal=signals.post_delete, sender=cls, instance=instance) + for cls in ordered_classes: + seen_objs[cls].reverse() + for pk_val, instance in seen_objs[cls]: + cursor.execute("DELETE FROM %s WHERE %s=%%s" % \ + (backend.quote_name(cls._meta.db_table), backend.quote_name(cls._meta.pk.column)), + [pk_val]) + setattr(instance, cls._meta.pk.attname, None) + dispatcher.send(signal=signals.post_delete, sender=cls, instance=instance) connection.commit()