diff --git a/django/db/models/base.py b/django/db/models/base.py
index 6304e00..3a4d5c2 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -544,7 +544,7 @@ class Model(object):
 
     save_base.alters_data = True
 
-    def _collect_sub_objects(self, seen_objs, parent=None, nullable=False):
+    def _collect_sub_objects(self, seen_objs, parent=None, nullable=False, pre_signal=None):
         """
         Recursively populates seen_objs with all objects related to this
         object.
@@ -553,6 +553,8 @@ class Model(object):
             [(model_class, {pk_val: obj, pk_val: obj, ...}),
              (model_class, {pk_val: obj, pk_val: obj, ...}), ...]
         """
+        if not pre_signal is None and not self.__class__._meta.auto_created:
+            pre_signal.send(sender=self.__class__, instance=self)
         pk_val = self._get_pk_val()
         if seen_objs.add(self.__class__, pk_val, self,
                          type(parent), parent, nullable):
@@ -566,7 +568,7 @@ class Model(object):
                 except ObjectDoesNotExist:
                     pass
                 else:
-                    sub_obj._collect_sub_objects(seen_objs, self, related.field.null)
+                    sub_obj._collect_sub_objects(seen_objs, self, related.field.null, pre_signal)
             else:
                 # To make sure we can access all elements, we can't use the
                 # normal manager on the related object. So we work directly
@@ -584,7 +586,7 @@ class Model(object):
                         continue
                 delete_qs = rel_descriptor.delete_manager(self).all()
                 for sub_obj in delete_qs:
-                    sub_obj._collect_sub_objects(seen_objs, self, related.field.null)
+                    sub_obj._collect_sub_objects(seen_objs, self, related.field.null, pre_signal)
 
         for related in self._meta.get_all_related_many_to_many_objects():
             if related.field.rel.through:
@@ -594,7 +596,7 @@ class Model(object):
                 nullable = opts.get_field(reverse_field_name).null
                 filters = {reverse_field_name: self}
                 for sub_obj in related.field.rel.through._base_manager.using(db).filter(**filters):
-                    sub_obj._collect_sub_objects(seen_objs, self, nullable)
+                    sub_obj._collect_sub_objects(seen_objs, self, nullable, pre_signal)
 
         for f in self._meta.many_to_many:
             if f.rel.through:
@@ -604,13 +606,13 @@ class Model(object):
                 nullable = opts.get_field(field_name).null
                 filters = {field_name: self}
                 for sub_obj in f.rel.through._base_manager.using(db).filter(**filters):
-                    sub_obj._collect_sub_objects(seen_objs, self, nullable)
+                    sub_obj._collect_sub_objects(seen_objs, self, nullable, pre_signal)
             else:
                 # m2m-ish but with no through table? GenericRelation: cascade delete
                 for sub_obj in f.value_from_object(self).all():
                     # Generic relations not enforced by db constraints, thus we can set
                     # nullable=True, order does not matter
-                    sub_obj._collect_sub_objects(seen_objs, self, True)
+                    sub_obj._collect_sub_objects(seen_objs, self, True, pre_signal)
 
         # Handle any ancestors (for the model-inheritance case). We do this by
         # traversing to the most remote parent classes -- those with no parents
@@ -625,7 +627,7 @@ class Model(object):
                 continue
             # At this point, parent_obj is base class (no ancestor models). So
             # delete it and all its descendents.
-            parent_obj._collect_sub_objects(seen_objs)
+            parent_obj._collect_sub_objects(seen_objs, pre_signal=pre_signal)
 
     def delete(self, using=None):
         using = using or router.db_for_write(self.__class__, instance=self)
@@ -633,7 +635,7 @@ class Model(object):
 
         # Find all the objects than need to be deleted.
         seen_objs = CollectedObjects()
-        self._collect_sub_objects(seen_objs)
+        self._collect_sub_objects(seen_objs, pre_signal=signals.pre_delete)
 
         # Actually delete the objects.
         delete_objects(seen_objs, using)
diff --git a/django/db/models/query.py b/django/db/models/query.py
index d9fbd9b..b9947b7 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -438,7 +438,7 @@ class QuerySet(object):
             # need to maintain the query cache on del_query (see #12328)
             seen_objs = CollectedObjects(seen_objs)
             for i, obj in izip(xrange(CHUNK_SIZE), del_itr):
-                obj._collect_sub_objects(seen_objs)
+                obj._collect_sub_objects(seen_objs, pre_signal=signals.pre_delete)
 
             if not seen_objs:
                 break
@@ -1308,11 +1308,6 @@ def delete_objects(seen_objs, using):
             items.sort()
             obj_pairs[cls] = items
 
-            # Pre-notify all instances to be deleted.
-            for pk_val, instance in items:
-                if not cls._meta.auto_created:
-                    signals.pre_delete.send(sender=cls, instance=instance)
-
             pk_list = [pk for pk,instance in items]
 
             update_query = sql.UpdateQuery(cls)
diff --git a/tests/modeltests/signals/models.py b/tests/modeltests/signals/models.py
index ea8137f..936c476 100644
--- a/tests/modeltests/signals/models.py
+++ b/tests/modeltests/signals/models.py
@@ -11,6 +11,18 @@ class Person(models.Model):
     def __unicode__(self):
         return u"%s %s" % (self.first_name, self.last_name)
 
+class BlogPost(models.Model):
+    author = models.ForeignKey(Person, related_name="posts", null=True)
+    post = models.TextField()
+    
+    def __unicode__(self):
+        max_len = 128
+        if len(self.post) > max_len:
+            post_unicode = self.post[:max_len-len('...')] + '...'
+        else:
+            post_unicode = self.post
+        return u"%s: %s" % (unicode(self.author), post_unicode)
+
 def pre_save_test(signal, sender, instance, **kwargs):
     print 'pre_save signal,', instance
     if kwargs.get('raw'):
diff --git a/tests/modeltests/signals/tests.py b/tests/modeltests/signals/tests.py
index 329636c..2a8f748 100644
--- a/tests/modeltests/signals/tests.py
+++ b/tests/modeltests/signals/tests.py
@@ -1,6 +1,6 @@
 from django.db.models import signals
 from django.test import TestCase
-from modeltests.signals.models import Person
+from modeltests.signals.models import Person, BlogPost
 
 class MyReceiver(object):
     def __init__(self, param):
@@ -11,6 +11,25 @@ class MyReceiver(object):
         self._run = True
         signal.disconnect(receiver=self, sender=sender)
 
+def clear_nullable_related(signal, sender, instance, **kwargs):
+    """
+    Clears any nullable foreign key fields on related objects.
+    This simulates ON DELETE SET NULL behavior manually.
+    """
+    model = instance #didactic rename
+    for related in model._meta.get_all_related_objects():
+        if related.field.null:
+            accessor = related.get_accessor_name()
+            try:
+                related_set = getattr(model, accessor)
+            except: # some version of DoesNotExist
+                pass
+            else:
+                related_set.clear()
+
+def is_deleted(model):
+    return model.__class__.objects.filter(pk=model.pk).count() == 0
+
 class SignalTests(TestCase):
     def test_disconnect_in_dispatch(self):
         """
@@ -25,4 +44,16 @@ class SignalTests(TestCase):
         self.failUnless(a._run)
         self.failUnless(b._run)
         self.assertEqual(signals.post_save.receivers, [])
-        
+
+    def test_pre_delete_sent_before_related_objects_cached(self):
+        """
+        Test that the pre_delete signal is sent for a model before its related
+        objects are cached for deletion.  The most common usage for this is to
+        allow the listener to clear related models so that they are not
+        cascade deleted.
+        """
+        signals.pre_delete.connect(sender=Person, receiver=clear_nullable_related)
+        person = Person.objects.create(first_name='Zeddicus', last_name='Zorander')
+        blog_post = BlogPost.objects.create(author=person, post="The life and times of an ornery wizard.")
+        person.delete()
+        self.assertTrue(not is_deleted(blog_post))
