diff --git a/django/contrib/contenttypes/models.py b/django/contrib/contenttypes/models.py
index 1bb07e0..bc9de56 100644
--- a/django/contrib/contenttypes/models.py
+++ b/django/contrib/contenttypes/models.py
@@ -16,11 +16,10 @@ class ContentTypeManager(models.Manager):
         return ct
 
     def _get_opts(self, model):
-        opts = model._meta
-        while opts.proxy:
-            model = opts.proxy_for_model
-            opts = model._meta
-        return opts
+        """
+        Returns the options for the concrete parent of the model.
+        """
+        return model._meta.concrete_class._meta
 
     def _get_from_cache(self, opts):
         key = (opts.app_label, opts.object_name.lower())
diff --git a/django/db/models/base.py b/django/db/models/base.py
index b5ce39e..033bf37 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -75,6 +75,8 @@ class ModelBase(type):
                     new_class._meta.get_latest_by = base_meta.get_latest_by
 
         is_proxy = new_class._meta.proxy
+        if not is_proxy and not abstract:
+            new_class._meta.concrete_class = new_class
 
         if getattr(new_class, '_default_manager', None):
             if not is_proxy:
@@ -122,9 +124,12 @@ class ModelBase(type):
             if (new_class._meta.local_fields or
                     new_class._meta.local_many_to_many):
                 raise FieldError("Proxy model '%s' contains model fields." % name)
-            while base._meta.proxy:
-                base = base._meta.proxy_for_model
             new_class._meta.setup_proxy(base)
+            # Add this class to 'proxying_models' for every proxied parent of
+            # this class.
+            while base:
+                base._meta.proxying_models.append(new_class)
+                base = base._meta.proxy_for_model
 
         # Do the appropriate setup for any model parents.
         o2o_map = dict([(f.rel.to, f) for f in new_class._meta.local_fields
@@ -149,9 +154,7 @@ class ModelBase(type):
                                         (field.name, name, base.__name__))
             if not base._meta.abstract:
                 # Concrete classes...
-                while base._meta.proxy:
-                    # Skip over a proxy class to the "real" base it proxies.
-                    base = base._meta.proxy_for_model
+                base = base._meta.concrete_class
                 if base in o2o_map:
                     field = o2o_map[base]
                 elif not is_proxy:
diff --git a/django/db/models/deletion.py b/django/db/models/deletion.py
index 9a6d499..d119534 100644
--- a/django/db/models/deletion.py
+++ b/django/db/models/deletion.py
@@ -145,6 +145,11 @@ class Collector(object):
         if not new_objs:
             return
         model = new_objs[0].__class__
+        # Always use the concrete model class for deletion. We will handle
+        # all the proxy-child classes later on. We do this because when
+        # we are deleting a model, we are in fact deleting the whole proxy
+        # equivalence class.
+        model = model._meta.concrete_class
 
         # Recursively collect parent models, but not their related objects.
         # These will be found by meta.get_all_related_objects()
@@ -157,27 +162,47 @@ class Collector(object):
                              reverse_dependency=True)
 
         if collect_related:
-            for related in model._meta.get_all_related_objects(include_hidden=True):
-                field = related.field
-                if related.model._meta.auto_created:
-                    self.add_batch(related.model, field, new_objs)
-                else:
-                    sub_objs = self.related_objects(related, new_objs)
-                    if not sub_objs:
+            # Collect all related objects to this model and for models proxying
+            # this model. Note that at this point the model is always the concrete
+            # parent in the proxy chain.
+            # When deleting a model we are also deleting every model that
+            # is in proxy-relation to this model. Would this be better handled by
+            # get_all_related_objects kwarg include_proxy? Are we continuously
+            # finding the same relations (proxy has all the same relations out as
+            # the parent).
+            proxy_equivalance = [model] + model._meta.proxying_models
+
+            # keep track of duplicate related models. These can be seen because of
+            # proxy models.
+            seen_related = set()
+            for base in proxy_equivalance:
+                for related in base._meta.get_all_related_objects(include_hidden=True):
+                    if related in seen_related:
                         continue
-                    field.rel.on_delete(self, field, sub_objs, self.using)
-
-            # TODO This entire block is only needed as a special case to
-            # support cascade-deletes for GenericRelation. It should be
-            # removed/fixed when the ORM gains a proper abstraction for virtual
-            # or composite fields, and GFKs are reworked to fit into that.
-            for relation in model._meta.many_to_many:
-                if not relation.rel.through:
-                    sub_objs = relation.bulk_related_objects(new_objs, self.using)
-                    self.collect(sub_objs,
-                                 source=model,
-                                 source_attr=relation.rel.related_name,
-                                 nullable=True)
+                    seen_related.add(related)
+                    field = related.field
+                    if related.model._meta.auto_created:
+                        self.add_batch(related.model, field, new_objs)
+                    else:
+                        sub_objs = self.related_objects(related, new_objs)
+                        if not sub_objs:
+                            continue
+                        field.rel.on_delete(self, field, sub_objs, self.using)
+
+                # TODO This entire block is only needed as a special case to
+                # support cascade-deletes for GenericRelation. It should be
+                # removed/fixed when the ORM gains a proper abstraction for virtual
+                # or composite fields, and GFKs are reworked to fit into that.
+                for relation in base._meta.many_to_many:
+                    if not relation.rel.through:
+                        if relation in seen_related:
+                            continue
+                        seen_related.add(relation)
+                        sub_objs = relation.bulk_related_objects(new_objs, self.using)
+                        self.collect(sub_objs,
+                                     source=model,
+                                     source_attr=relation.rel.related_name,
+                                     nullable=True)
 
     def related_objects(self, related, objs):
         """
diff --git a/django/db/models/options.py b/django/db/models/options.py
index 0cd52a3..853a616 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -40,7 +40,14 @@ class Options(object):
         self.abstract = False
         self.managed = True
         self.proxy = False
+        # None for abstract classes, own class for concrete models
+        # and the first concrete parent for proxy models. This does
+        # not track multitable parents.
+        self.concrete_class = None
         self.proxy_for_model = None
+        # We need to keep track of which models are proxying this model
+        # so that we can fetch all references to this model when deleting.
+        self.proxying_models = []
         self.parents = SortedDict()
         self.duplicate_targets = {}
         self.auto_created = False
@@ -183,6 +190,7 @@ class Options(object):
         """
         self.pk = target._meta.pk
         self.proxy_for_model = target
+        self.concrete_class = target._meta.concrete_class
         self.db_table = target._meta.db_table
 
     def __repr__(self):
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index 61fd2be..b40c4c8 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -569,10 +569,7 @@ class Query(object):
             return
         orig_opts = self.model._meta
         seen = {}
-        if orig_opts.proxy:
-            must_include = {orig_opts.proxy_for_model: set([orig_opts.pk])}
-        else:
-            must_include = {self.model: set([orig_opts.pk])}
+        must_include = {orig_opts.concrete_class: set([orig_opts.pk])}
         for field_name in field_names:
             parts = field_name.split(LOOKUP_SEP)
             cur_model = self.model
@@ -1949,9 +1946,4 @@ def add_to_dict(data, key, value):
         data[key] = set([value])
 
 def get_proxied_model(opts):
-    int_opts = opts
-    proxied_model = None
-    while int_opts.proxy:
-        proxied_model = int_opts.proxy_for_model
-        int_opts = proxied_model._meta
-    return proxied_model
+    return opts.concrete_class
diff --git a/tests/regressiontests/proxy_bug/__init__.py b/tests/regressiontests/proxy_bug/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/regressiontests/proxy_bug/models.py b/tests/regressiontests/proxy_bug/models.py
new file mode 100644
index 0000000..7389ffe
--- /dev/null
+++ b/tests/regressiontests/proxy_bug/models.py
@@ -0,0 +1,22 @@
+from django.db import models
+
+class File(models.Model):
+    pass
+
+class Image(File):
+    class Meta:
+        proxy = True
+
+class Photo(Image):
+    class Meta:
+        proxy = True
+
+class FooImage(models.Model):
+    my_image = models.ForeignKey(Image)
+    
+class FooFile(models.Model):
+    my_file = models.ForeignKey(File)
+
+class FooPhoto(models.Model):
+    my_photo = models.ForeignKey(Photo)
+
diff --git a/tests/regressiontests/proxy_bug/tests.py b/tests/regressiontests/proxy_bug/tests.py
new file mode 100644
index 0000000..5704d35
--- /dev/null
+++ b/tests/regressiontests/proxy_bug/tests.py
@@ -0,0 +1,91 @@
+from django.test import TestCase
+#from django.test.utils import override_settings
+from .models import Image, File, Photo, FooFile, FooImage, FooPhoto
+
+
+class ProxyDeleteImageTest(TestCase):
+    '''
+    Tests on_delete behaviour for proxy models. Deleting the *proxy*
+    instance bubbles through to its non-proxy and *all* referring objects
+    are deleted.
+    '''
+
+    def setUp(self):
+        # Create an Image
+        self.test_image = Image()
+        self.test_image.save()
+        foo_image = FooImage(my_image=self.test_image)
+        foo_image.save()
+
+        # Get the Image instance as a File
+        test_file = File.objects.get(pk=self.test_image.pk)
+        foo_file = FooFile(my_file=test_file)
+        foo_file.save()
+
+    #@override_settings(DEBUG=True)
+    def test_delete(self):
+        self.assertTrue(Image.objects.all().delete() is None)
+        # An Image deletion == File deletion
+        self.assertEqual(len(Image.objects.all()), 0)
+        self.assertEqual(len(File.objects.all()), 0)
+        # The Image deletion cascaded and *all* references to it are deleted.
+        self.assertEqual(len(FooImage.objects.all()), 0)
+        self.assertEqual(len(FooFile.objects.all()), 0)
+        #from django.db import connection
+        #for q in connection.queries:
+        #    print q
+
+
+class ProxyDeletePhotoTest(ProxyDeleteImageTest):
+    '''
+    Tests on_delete behaviour for proxy-of-proxy models. Deleting the *proxy*
+    instance should bubble through to its proxy and non-proxy variants.
+    Deleting *all* referring objects. For some reason it seems that the 
+    intermediate proxy model isn't cleaned up.
+    '''
+
+    def setUp(self):
+        # Create the Image, FooImage and FooFile instances
+        super(ProxyDeletePhotoTest, self).setUp()
+        # Get the Image as a Photo
+        test_photo = Photo.objects.get(pk=self.test_image.pk)
+        foo_photo = FooPhoto(my_photo=test_photo)
+        foo_photo.save()
+
+
+    #@override_settings(DEBUG=True)
+    def test_delete(self):
+        self.assertTrue(Photo.objects.all().delete() is None)
+        # A Photo deletion == Image deletion == File deletion
+        self.assertEqual(len(Photo.objects.all()), 0)
+        self.assertEqual(len(Image.objects.all()), 0)
+        self.assertEqual(len(File.objects.all()), 0)
+        # The Photo deletion should have cascaded and deleted *all*
+        # references to it.
+        self.assertEqual(len(FooPhoto.objects.all()), 0)
+        self.assertEqual(len(FooFile.objects.all()), 0)
+        self.assertEqual(len(FooImage.objects.all()), 0)
+        #from django.db import connection
+        #for q in connection.queries:
+        #    print q
+        #print len(connection.queries)
+
+
+class ProxyDeleteFileTest(ProxyDeleteImageTest):
+    '''
+    Tests on_delete cascade behaviour for proxy models. Deleting the
+    *non-proxy* instance of a model should somehow notify it's proxy.
+    Currently it doesn't, making this test fail.
+    '''
+
+    def test_delete(self):
+        # This should *not* raise an IntegrityError on databases that support 
+        # FK constraints.
+        self.assertTrue(File.objects.all().delete() is None)
+        # A File deletion == Image deletion
+        self.assertEqual(len(File.objects.all()), 0)
+        self.assertEqual(len(Image.objects.all()), 0)
+        # The File deletion should have cascaded and deleted *all* references
+        # to it.
+        self.assertEqual(len(FooFile.objects.all()), 0)
+        self.assertEqual(len(FooImage.objects.all()), 0)
