diff --git a/django/db/models/base.py b/django/db/models/base.py
index 6304e00..3a4d5c2 100644
a
|
b
|
class Model(object):
|
544 | 544 | |
545 | 545 | save_base.alters_data = True |
546 | 546 | |
547 | | def _collect_sub_objects(self, seen_objs, parent=None, nullable=False): |
| 547 | def _collect_sub_objects(self, seen_objs, parent=None, nullable=False, pre_signal=None): |
548 | 548 | """ |
549 | 549 | Recursively populates seen_objs with all objects related to this |
550 | 550 | object. |
… |
… |
class Model(object):
|
553 | 553 | [(model_class, {pk_val: obj, pk_val: obj, ...}), |
554 | 554 | (model_class, {pk_val: obj, pk_val: obj, ...}), ...] |
555 | 555 | """ |
| 556 | if not pre_signal is None and not self.__class__._meta.auto_created: |
| 557 | pre_signal.send(sender=self.__class__, instance=self) |
556 | 558 | pk_val = self._get_pk_val() |
557 | 559 | if seen_objs.add(self.__class__, pk_val, self, |
558 | 560 | type(parent), parent, nullable): |
… |
… |
class Model(object):
|
566 | 568 | except ObjectDoesNotExist: |
567 | 569 | pass |
568 | 570 | else: |
569 | | sub_obj._collect_sub_objects(seen_objs, self, related.field.null) |
| 571 | sub_obj._collect_sub_objects(seen_objs, self, related.field.null, pre_signal) |
570 | 572 | else: |
571 | 573 | # To make sure we can access all elements, we can't use the |
572 | 574 | # normal manager on the related object. So we work directly |
… |
… |
class Model(object):
|
584 | 586 | continue |
585 | 587 | delete_qs = rel_descriptor.delete_manager(self).all() |
586 | 588 | for sub_obj in delete_qs: |
587 | | sub_obj._collect_sub_objects(seen_objs, self, related.field.null) |
| 589 | sub_obj._collect_sub_objects(seen_objs, self, related.field.null, pre_signal) |
588 | 590 | |
589 | 591 | for related in self._meta.get_all_related_many_to_many_objects(): |
590 | 592 | if related.field.rel.through: |
… |
… |
class Model(object):
|
594 | 596 | nullable = opts.get_field(reverse_field_name).null |
595 | 597 | filters = {reverse_field_name: self} |
596 | 598 | for sub_obj in related.field.rel.through._base_manager.using(db).filter(**filters): |
597 | | sub_obj._collect_sub_objects(seen_objs, self, nullable) |
| 599 | sub_obj._collect_sub_objects(seen_objs, self, nullable, pre_signal) |
598 | 600 | |
599 | 601 | for f in self._meta.many_to_many: |
600 | 602 | if f.rel.through: |
… |
… |
class Model(object):
|
604 | 606 | nullable = opts.get_field(field_name).null |
605 | 607 | filters = {field_name: self} |
606 | 608 | for sub_obj in f.rel.through._base_manager.using(db).filter(**filters): |
607 | | sub_obj._collect_sub_objects(seen_objs, self, nullable) |
| 609 | sub_obj._collect_sub_objects(seen_objs, self, nullable, pre_signal) |
608 | 610 | else: |
609 | 611 | # m2m-ish but with no through table? GenericRelation: cascade delete |
610 | 612 | for sub_obj in f.value_from_object(self).all(): |
611 | 613 | # Generic relations not enforced by db constraints, thus we can set |
612 | 614 | # nullable=True, order does not matter |
613 | | sub_obj._collect_sub_objects(seen_objs, self, True) |
| 615 | sub_obj._collect_sub_objects(seen_objs, self, True, pre_signal) |
614 | 616 | |
615 | 617 | # Handle any ancestors (for the model-inheritance case). We do this by |
616 | 618 | # traversing to the most remote parent classes -- those with no parents |
… |
… |
class Model(object):
|
625 | 627 | continue |
626 | 628 | # At this point, parent_obj is base class (no ancestor models). So |
627 | 629 | # delete it and all its descendents. |
628 | | parent_obj._collect_sub_objects(seen_objs) |
| 630 | parent_obj._collect_sub_objects(seen_objs, pre_signal=pre_signal) |
629 | 631 | |
630 | 632 | def delete(self, using=None): |
631 | 633 | using = using or router.db_for_write(self.__class__, instance=self) |
… |
… |
class Model(object):
|
633 | 635 | |
634 | 636 | # Find all the objects than need to be deleted. |
635 | 637 | seen_objs = CollectedObjects() |
636 | | self._collect_sub_objects(seen_objs) |
| 638 | self._collect_sub_objects(seen_objs, pre_signal=signals.pre_delete) |
637 | 639 | |
638 | 640 | # Actually delete the objects. |
639 | 641 | delete_objects(seen_objs, using) |
diff --git a/django/db/models/query.py b/django/db/models/query.py
index d9fbd9b..b9947b7 100644
a
|
b
|
class QuerySet(object):
|
438 | 438 | # need to maintain the query cache on del_query (see #12328) |
439 | 439 | seen_objs = CollectedObjects(seen_objs) |
440 | 440 | for i, obj in izip(xrange(CHUNK_SIZE), del_itr): |
441 | | obj._collect_sub_objects(seen_objs) |
| 441 | obj._collect_sub_objects(seen_objs, pre_signal=signals.pre_delete) |
442 | 442 | |
443 | 443 | if not seen_objs: |
444 | 444 | break |
… |
… |
def delete_objects(seen_objs, using):
|
1308 | 1308 | items.sort() |
1309 | 1309 | obj_pairs[cls] = items |
1310 | 1310 | |
1311 | | # Pre-notify all instances to be deleted. |
1312 | | for pk_val, instance in items: |
1313 | | if not cls._meta.auto_created: |
1314 | | signals.pre_delete.send(sender=cls, instance=instance) |
1315 | | |
1316 | 1311 | pk_list = [pk for pk,instance in items] |
1317 | 1312 | |
1318 | 1313 | update_query = sql.UpdateQuery(cls) |