diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py
--- a/django/contrib/admin/util.py
+++ b/django/contrib/admin/util.py
@@ -87,13 +87,13 @@
     nh = _nest_help # Bind to local variable for performance
     if current_depth > 16:
         return # Avoid recursing too deep.
-    opts_seen = []
+    rels_seen = []
     for related in opts.get_all_related_objects():
         has_admin = related.model in admin_site._registry
-        if related.opts in opts_seen:
+        rel_opts_name = related.get_accessor_name()
+        if rel_opts_name in rels_seen:
             continue
-        opts_seen.append(related.opts)
-        rel_opts_name = related.get_accessor_name()
+        rels_seen.append(rel_opts_name)
         if isinstance(related.field.rel, models.OneToOneRel):
             try:
                 sub_obj = getattr(obj, rel_opts_name)
@@ -150,10 +150,10 @@
                     perms_needed.add(related.opts.verbose_name)
     for related in opts.get_all_related_many_to_many_objects():
         has_admin = related.model in admin_site._registry
-        if related.opts in opts_seen:
+        rel_opts_name = related.get_accessor_name()
+        if rel_opts_name in rels_seen:
             continue
-        opts_seen.append(related.opts)
-        rel_opts_name = related.get_accessor_name()
+        rels_seen.append(rel_opts_name)
         has_related_objs = False
 
         # related.get_accessor_name() could return None for symmetrical relationships
diff --git a/tests/regressiontests/admin_views/fixtures/deleted-objects.xml b/tests/regressiontests/admin_views/fixtures/deleted-objects.xml
new file mode 100644
--- /dev/null
+++ b/tests/regressiontests/admin_views/fixtures/deleted-objects.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+    <object pk="1" model="admin_views.villain">
+        <field type="CharField" name="name">Adam</field>
+    </object>
+    <object pk="2" model="admin_views.villain">
+        <field type="CharField" name="name">Sue</field>
+    </object>
+    <object pk="1" model="admin_views.plot">
+        <field type="CharField" name="name">World Domination</field>
+        <field type="ForeignKey" name="team_leader">1</field>
+        <field type="ForeignKey" name="contact">2</field>
+    </object>
+    <object pk="2" model="admin_views.plot">
+        <field type="CharField" name="name">World Peace</field>
+        <field type="ForeignKey" name="team_leader">2</field>
+        <field type="ForeignKey" name="contact">2</field>
+    </object>
+    <object pk="1" model="admin_views.plotdetails">
+        <field type="CharField" name="details">almost finished</field>
+        <field type="ForeignKey" name="plot">1</field>
+    </object>
+    <object pk="1" model="admin_views.secrethideout">
+        <field type="CharField" name="location">underground bunker</field>
+        <field type="ForeignKey" name="villain">1</field>
+    </object>
+    <object pk="1" model="admin_views.cyclicone">
+        <field type="CharField" name="name">I am recursive</field>
+        <field type="ForeignKey" name="two">1</field>
+    </object>
+    <object pk="1" model="admin_views.cyclictwo">
+        <field type="CharField" name="name">I am recursive too</field>
+        <field type="ForeignKey" name="one">1</field>
+    </object>
+</django-objects>
diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py
--- a/tests/regressiontests/admin_views/models.py
+++ b/tests/regressiontests/admin_views/models.py
@@ -478,6 +478,49 @@
     def get_changelist(self, request, **kwargs):
         return CustomChangeList
 
+class Villain(models.Model):
+    name = models.CharField(max_length=100)
+
+    def __unicode__(self):
+        return self.name
+
+class Plot(models.Model):
+    name = models.CharField(max_length=100)
+    team_leader = models.ForeignKey(Villain, related_name='lead_plots')
+    contact = models.ForeignKey(Villain, related_name='contact_plots')
+
+    def __unicode__(self):
+        return self.name
+
+class PlotDetails(models.Model):
+    details = models.CharField(max_length=100)
+    plot = models.OneToOneField(Plot)
+
+    def __unicode__(self):
+        return self.details
+
+class SecretHideout(models.Model):
+    """ Secret! Not registered with the admin! """
+    location = models.CharField(max_length=100)
+    villain = models.ForeignKey(Villain)
+
+    def __unicode__(self):
+        return self.location
+
+class CyclicOne(models.Model):
+    name = models.CharField(max_length=25)
+    two = models.ForeignKey('CyclicTwo')
+
+    def __unicode__(self):
+        return self.name
+
+class CyclicTwo(models.Model):
+    name = models.CharField(max_length=25)
+    one = models.ForeignKey(CyclicOne)
+
+    def __unicode__(self):
+        return self.name
+
 admin.site.register(Article, ArticleAdmin)
 admin.site.register(CustomArticle, CustomArticleAdmin)
 admin.site.register(Section, save_as=True, inlines=[ArticleInline])
@@ -503,6 +546,11 @@
 admin.site.register(Category, CategoryAdmin)
 admin.site.register(Post, PostAdmin)
 admin.site.register(Gadget, GadgetAdmin)
+admin.site.register(Villain)
+admin.site.register(Plot)
+admin.site.register(PlotDetails)
+admin.site.register(CyclicOne)
+admin.site.register(CyclicTwo)
 
 # We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2.
 # That way we cover all four cases:
diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
--- a/tests/regressiontests/admin_views/tests.py
+++ b/tests/regressiontests/admin_views/tests.py
@@ -20,7 +20,7 @@
     ExternalSubscriber, FooAccount, Gallery, ModelWithStringPrimaryKey, \
     Person, Persona, Picture, Podcast, Section, Subscriber, Vodcast, \
     Language, Collector, Widget, Grommet, DooHickey, FancyDoodad, Whatsit, \
-    Category, Post
+    Category, Post, Plot
 
 
 class AdminViewBasicTest(TestCase):
@@ -615,6 +615,80 @@
         response = self.client.get('/test_admin/admin/secure-view/')
         self.assertContains(response, 'id="login-form"')
 
+
+class AdminViewDeletedObjectsTest(TestCase):
+    fixtures = ['admin-views-users.xml', 'deleted-objects.xml']
+
+    def setUp(self):
+        self.client.login(username='super', password='secret')
+
+    def tearDown(self):
+        self.client.logout()
+
+    def test_nesting(self):
+        """
+        Objects should be nested to display the relationships that
+        cause them to be scheduled for deletion.
+        """
+        pattern = re.compile(r"""<li>Plot: <a href="\.\./\.\./\.\./\.\./admin_views/plot/1/">World Domination</a>\s*<ul>\s*<li>Plot details: <a href="\.\./\.\./\.\./\.\./admin_views/plotdetails/1/">almost finished</a>""")
+        response = self.client.get('/test_admin/admin/admin_views/villain/%s/delete/' % quote(1))
+        self.failUnless(pattern.search(response.content))
+
+    def test_cyclic(self):
+        """
+        Cyclic relationships should still cause each object to only be
+        listed once.
+
+        """
+        one = """<li>Cyclic one: <a href="../../../../admin_views/cyclicone/1/">I am recursive</a>"""
+        two = """<li>Cyclic two: <a href="../../../../admin_views/cyclictwo/1/">I am recursive too</a>"""
+        response = self.client.get('/test_admin/admin/admin_views/cyclicone/%s/delete/' % quote(1))
+        self.assertContains(response, one, 1)
+        self.assertContains(response, two, 1)
+
+    def test_perms_needed(self):
+        self.client.logout()
+        delete_user = User.objects.get(username='deleteuser')
+        delete_user.user_permissions.add(get_perm(Plot,
+            Plot._meta.get_delete_permission()))
+
+        self.failUnless(self.client.login(username='deleteuser',
+                                          password='secret'))
+
+        response = self.client.get('/test_admin/admin/admin_views/plot/%s/delete/' % quote(1))
+        self.assertContains(response, "your account doesn't have permission to delete the following types of objects")
+        self.assertContains(response, "<li>plot details</li>")
+
+
+    def test_not_registered(self):
+        should_contain = """<li>Secret hideout: underground bunker"""
+        response = self.client.get('/test_admin/admin/admin_views/villain/%s/delete/' % quote(1))
+        self.assertContains(response, should_contain, 1)
+
+    def test_multiple_fkeys_to_same_model(self):
+        """
+        If a deleted object has two relationships from another model,
+        both of those should be followed in looking for related
+        objects to delete.
+
+        """
+        should_contain = """<li>Plot: <a href="../../../../admin_views/plot/1/">World Domination</a>"""
+        response = self.client.get('/test_admin/admin/admin_views/villain/%s/delete/' % quote(1))
+        self.assertContains(response, should_contain)
+        response = self.client.get('/test_admin/admin/admin_views/villain/%s/delete/' % quote(2))
+        self.assertContains(response, should_contain)
+
+    def test_multiple_fkeys_to_same_instance(self):
+        """
+        If a deleted object has two relationships pointing to it from
+        another object, the other object should still only be listed
+        once.
+
+        """
+        should_contain = """<li>Plot: <a href="../../../../admin_views/plot/2/">World Peace</a></li>"""
+        response = self.client.get('/test_admin/admin/admin_views/villain/%s/delete/' % quote(2))
+        self.assertContains(response, should_contain, 1)
+
 class AdminViewStringPrimaryKeyTest(TestCase):
     fixtures = ['admin-views-users.xml', 'string-primary-key.xml']
 
