diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py
index c513787..1cdc572 100644
--- a/django/contrib/contenttypes/generic.py
+++ b/django/contrib/contenttypes/generic.py
@@ -62,7 +62,9 @@ class GenericForeignKey(object):
             # This should never happen. I love comments like this, don't you?
             raise Exception("Impossible arguments to GFK.get_content_type!")
 
-    def get_prefetch_query_set(self, instances):
+    def get_prefetch_query_set(self, instances, custom_qs=None):
+        if custom_qs is not None:
+            raise ValueError("Custom queryset can't be used for this lookup")
         # For efficiency, group the instances by content type and then do one
         # query per model
         fk_dict = defaultdict(set)
@@ -320,20 +322,34 @@ def create_generic_related_manager(superclass):
                 db = self._db or router.db_for_read(self.model, instance=self.instance)
                 return super(GenericRelatedObjectManager, self).get_query_set().using(db).filter(**self.core_filters)
 
-        def get_prefetch_query_set(self, instances):
-            db = self._db or router.db_for_read(self.model)
+        def get_prefetch_query_set(self, instances, custom_qs=None):
+            if not instances:
+                return self.model._default_manager.none()
             query = {
                 '%s__pk' % self.content_type_field_name: self.content_type.id,
                 '%s__in' % self.object_id_field_name:
                     set(obj._get_pk_val() for obj in instances)
-                }
-            qs = super(GenericRelatedObjectManager, self).get_query_set().using(db).filter(**query)
+            }
+            if custom_qs is not None:
+                qs = custom_qs.filter(**query)
+            else:
+                db = self._db or router.db_for_read(self.model, instance=instances[0])
+                qs = super(GenericRelatedObjectManager, self).get_query_set()\
+                         .using(db).filter(**query)
             return (qs,
                     attrgetter(self.object_id_field_name),
                     lambda obj: obj._get_pk_val(),
                     False,
                     self.prefetch_cache_name)
 
+
+        def all(self):
+            try:
+                return self.instance._prefetched_objects_cache[self.prefetch_cache_name]
+            except (AttributeError, KeyError):
+                return super(GenericRelatedObjectManager, self).all()
+
+
         def add(self, *objs):
             for obj in objs:
                 if not isinstance(obj, self.model):
diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py
index 3582720..fd07ca6 100644
--- a/django/db/models/__init__.py
+++ b/django/db/models/__init__.py
@@ -4,14 +4,17 @@ from django.db import connection
 from django.db.models.loading import get_apps, get_app, get_models, get_model, register_models
 from django.db.models.query import Q
 from django.db.models.expressions import F
+from django.db.models.related import R
 from django.db.models.manager import Manager
 from django.db.models.base import Model
 from django.db.models.aggregates import *
 from django.db.models.fields import *
 from django.db.models.fields.subclassing import SubfieldBase
 from django.db.models.fields.files import FileField, ImageField
-from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel
-from django.db.models.deletion import CASCADE, PROTECT, SET, SET_NULL, SET_DEFAULT, DO_NOTHING, ProtectedError
+from django.db.models.fields.related import (ForeignKey, OneToOneField,
+        ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel)
+from django.db.models.deletion import (CASCADE, PROTECT, SET, SET_NULL,
+        SET_DEFAULT, DO_NOTHING, ProtectedError)
 from django.db.models import signals
 from django.utils.decorators import wraps
 
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 8c054e7..a0748a1 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -236,7 +236,13 @@ class SingleRelatedObjectDescriptor(object):
         db = router.db_for_read(self.related.model, **db_hints)
         return self.related.model._base_manager.using(db)
 
-    def get_prefetch_query_set(self, instances):
+    def get_prefetch_query_set(self, instances, custom_qs=None):
+        if custom_qs is not None:
+            # TODO: This error message is too SQLish, and might be downright
+            # wrong.
+            raise ValueError(
+                "Custom querysets can't be used for one-to-one relations")
+
         vals = set(instance._get_pk_val() for instance in instances)
         params = {'%s__pk__in' % self.related.field.name: vals}
         return (self.get_query_set(),
@@ -315,7 +321,13 @@ class ReverseSingleRelatedObjectDescriptor(object):
         else:
             return QuerySet(self.field.rel.to).using(db)
 
-    def get_prefetch_query_set(self, instances):
+    def get_prefetch_query_set(self, instances, custom_qs=None):
+        if custom_qs is not None:
+            # TODO: This error message is too SQLish, and I am not even sure
+            # this desriptor is used for m2o...
+            raise ValueError(
+                "Custom querysets can't be used for many-to-one relations")
+
         vals = set(getattr(instance, self.field.attname) for instance in instances)
         other_field = self.field.rel.get_related_field()
         if other_field.rel:
@@ -460,17 +472,31 @@ class ForeignRelatedObjectsDescriptor(object):
                     db = self._db or router.db_for_read(self.model, instance=self.instance)
                     return super(RelatedManager, self).get_query_set().using(db).filter(**self.core_filters)
 
-            def get_prefetch_query_set(self, instances):
-                db = self._db or router.db_for_read(self.model)
+            def get_prefetch_query_set(self, instances, custom_qs=None):
+                """
+                Return a queryset that does the bulk lookup needed
+                by prefetch_related functionality.
+                """
                 query = {'%s__%s__in' % (rel_field.name, attname):
-                             set(getattr(obj, attname) for obj in instances)}
-                qs = super(RelatedManager, self).get_query_set().using(db).filter(**query)
+                            set(getattr(obj, attname) for obj in instances)}
+                if custom_qs is not None:
+                    qs = custom_qs.filter(**query)
+                else:
+                    db = self._db or router.db_for_read(self.model)
+                    qs = super(RelatedManager, self).get_query_set().\
+                                    using(db).filter(**query)
                 return (qs,
                         attrgetter(rel_field.get_attname()),
                         attrgetter(attname),
                         False,
                         rel_field.related_query_name())
 
+            def all(self):
+                try:
+                    return self.instance._prefetched_objects_cache[rel_field.related_query_name()]
+                except (AttributeError, KeyError):
+                    return super(RelatedManager, self).all()
+
             def add(self, *objs):
                 for obj in objs:
                     if not isinstance(obj, self.model):
@@ -542,24 +568,39 @@ def create_many_related_manager(superclass, rel):
                 db = self._db or router.db_for_read(self.instance.__class__, instance=self.instance)
                 return super(ManyRelatedManager, self).get_query_set().using(db)._next_is_sticky().filter(**self.core_filters)
 
-        def get_prefetch_query_set(self, instances):
+        def get_prefetch_query_set(self, instances, custom_qs=None):
             from django.db import connections
             db = self._db or router.db_for_read(self.model)
             query = {'%s__pk__in' % self.query_field_name:
-                         set(obj._get_pk_val() for obj in instances)}
-            qs = super(ManyRelatedManager, self).get_query_set().using(db)._next_is_sticky().filter(**query)
+                                  set(obj._get_pk_val() for obj in instances)}
+
+            if custom_qs is not None:
+                qs = custom_qs._next_is_sticky().filter(**query)
+            else:
+                qs = (super(ManyRelatedManager, self).get_query_set().using(db)
+                      ._next_is_sticky().filter(**query))
 
             # M2M: need to annotate the query in order to get the primary model
-            # that the secondary model was actually related to. We know that
-            # there will already be a join on the join table, so we can just add
-            # the select.
+            # that the secondary model was actually related to.
+
+            # We know that there will already be a join on the join table, so we
+            # can just add the select.
 
             # For non-autocreated 'through' models, can't assume we are
             # dealing with PK values.
+
+            # TODO: This is at the wrong level of abstraction. We should not
+            # be generating SQL here, but instead maybe pass this information
+            # to the connection. NoSQL camp will have problems with this, for
+            # example.
             fk = self.through._meta.get_field(self.source_field_name)
             source_col = fk.column
             join_table = self.through._meta.db_table
-            connection = connections[db]
+            if custom_qs is not None:
+                connection = connections[custom_qs.db]
+            else:
+                connection = connections[db]
+
             qn = connection.ops.quote_name
             qs = qs.extra(select={'_prefetch_related_val':
                                       '%s.%s' % (qn(join_table), qn(source_col))})
@@ -570,6 +611,12 @@ def create_many_related_manager(superclass, rel):
                     False,
                     self.prefetch_cache_name)
 
+        def all(self):
+            try:
+                return self.instance._prefetched_objects_cache[self.prefetch_cache_name]
+            except (AttributeError, KeyError):
+                return super(ManyRelatedManager, self).all()
+
         # If the ManyToMany relation has an intermediary model,
         # the add and remove methods do not exist.
         if rel.through._meta.auto_created:
diff --git a/django/db/models/query.py b/django/db/models/query.py
index be42d02..f5cf2ae 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -11,6 +11,7 @@ from django.db.models.query_utils import (Q, select_related_descend,
     deferred_class_factory, InvalidQuery)
 from django.db.models.deletion import Collector
 from django.db.models import sql
+from django.db.models.related import R
 from django.utils.functional import partition
 
 # Used to control how many objects are worked with at once in some cases (e.g.
@@ -1555,6 +1556,22 @@ def insert_query(model, objs, fields, return_id=False, raw=False, using=None):
     query.insert_values(fields, objs, raw=raw)
     return query.get_compiler(using=using).execute_sql(return_id)
 
+def prl_to_r_objs(lookups, prefix=None):
+    """
+    This little helper function will convert a list containing R objects or
+    normal lookups into all R objects list.
+    """
+    from django.db.models.sql.constants import LOOKUP_SEP
+    if prefix is None:
+        return [isinstance(lup, R) and lup or R(lup) for lup in lookups]
+    ret = []
+    for lup in lookups:
+        if isinstance(lup, R):
+            r_obj = lup._new_prefixed(prefix)
+        else:
+            r_obj = R(prefix + LOOKUP_SEP + lup)
+        ret.append(r_obj)
+    return ret
 
 def prefetch_related_objects(result_cache, related_lookups):
     """
@@ -1567,31 +1584,38 @@ def prefetch_related_objects(result_cache, related_lookups):
 
     if len(result_cache) == 0:
         return # nothing to do
-
+    r_objs = prl_to_r_objs(related_lookups)
     model = result_cache[0].__class__
 
     # We need to be able to dynamically add to the list of prefetch_related
     # lookups that we look up (see below).  So we need some book keeping to
     # ensure we don't do duplicate work.
-    done_lookups = set() # list of lookups like foo__bar__baz
+    seen_lookups = set() # list of lookups like foo__bar__baz
     done_queries = {}    # dictionary of things like 'foo__bar': [results]
 
-    manual_lookups = list(related_lookups)
+    manual_lookups = list(r_objs)
     auto_lookups = [] # we add to this as we go through.
     followed_descriptors = set() # recursion protection
 
-    related_lookups = itertools.chain(manual_lookups, auto_lookups)
-    for lookup in related_lookups:
-        if lookup in done_lookups:
+    # For R-objects, we have two different lookups:
+    #   - lookup: This is the related object attribute name
+    #   - lookup_refpath: This is to be used when this R-object is referenced
+    #     in chained prefetches.
+    # One way to explain these would be to say lookup is how we go forward,
+    # lookup_refpath is what happened in the past.
+
+    r_objs = itertools.chain(manual_lookups, auto_lookups)
+    for r_obj in r_objs:
+        if r_obj.lookup_refpath in seen_lookups:
             # We've done exactly this already, skip the whole thing
             continue
-        done_lookups.add(lookup)
+        seen_lookups.add(r_obj.lookup_refpath)
 
         # Top level, the list of objects to decorate is the the result cache
         # from the primary QuerySet. It won't be for deeper levels.
         obj_list = result_cache
 
-        attrs = lookup.split(LOOKUP_SEP)
+        attrs = r_obj.lookup.split(LOOKUP_SEP)
         for level, attr in enumerate(attrs):
             # Prepare main instances
             if len(obj_list) == 0:
@@ -1619,46 +1643,63 @@ def prefetch_related_objects(result_cache, related_lookups):
 
             # We assume that objects retrieved are homogenous (which is the premise
             # of prefetch_related), so what applies to first object applies to all.
+            # TODO: Make sure this is really true for objects coming from generic
+            # relations.
             first_obj = obj_list[0]
-            prefetcher, descriptor, attr_found, is_fetched = get_prefetcher(first_obj, attr)
+            prefetcher, descriptor, attr_found, is_fetched = \
+                get_prefetcher(first_obj, attr)
 
             if not attr_found:
                 raise AttributeError("Cannot find '%s' on %s object, '%s' is an invalid "
                                      "parameter to prefetch_related()" %
-                                     (attr, first_obj.__class__.__name__, lookup))
+                                     (attr, first_obj.__class__.__name__,
+                                      r_obj.lookup))
 
             if level == len(attrs) - 1 and prefetcher is None:
                 # Last one, this *must* resolve to something that supports
                 # prefetching, otherwise there is no point adding it and the
                 # developer asking for it has made a mistake.
-                raise ValueError("'%s' does not resolve to a item that supports "
+                raise ValueError("'%s' does not resolve to an item that supports "
                                  "prefetching - this is an invalid parameter to "
-                                 "prefetch_related()." % lookup)
+                                 "prefetch_related()." % r_obj.lookup)
 
             if prefetcher is not None and not is_fetched:
-                # Check we didn't do this already
-                current_lookup = LOOKUP_SEP.join(attrs[0:level+1])
+                current_lookup = r_obj.get_current_lookup(level)
                 if current_lookup in done_queries:
                     obj_list = done_queries[current_lookup]
                 else:
-                    obj_list, additional_prl = prefetch_one_level(obj_list, prefetcher, attr)
+                    obj_list, additional_prl = prefetch_one_level(
+                            obj_list, prefetcher, r_obj, level)
                     # We need to ensure we don't keep adding lookups from the
                     # same relationships to stop infinite recursion. So, if we
                     # are already on an automatically added lookup, don't add
                     # the new lookups from relationships we've seen already.
-                    if not (lookup in auto_lookups and
+                    if not (r_obj in auto_lookups and
                             descriptor in followed_descriptors):
-                        for f in additional_prl:
-                            new_prl = LOOKUP_SEP.join([current_lookup, f])
-                            auto_lookups.append(new_prl)
                         done_queries[current_lookup] = obj_list
+                        additional_prl = prl_to_r_objs(additional_prl,
+                                                       current_lookup)
+                        auto_lookups.extend(additional_prl)
                     followed_descriptors.add(descriptor)
+
+            elif isinstance(getattr(obj_list[0], attr), list):
+                # The current part of the lookup relates to a r_obj.to_attr
+                # defined previous fetch. This means that obj.attr is a list
+                # of related objects, and thus we must turn the obj.attr lists
+                # into a single related object list.
+                new_list = []
+                for obj in obj_list:
+                    new_list.extend(getattr(obj, attr))
+                obj_list = new_list
             else:
                 # Either a singly related object that has already been fetched
                 # (e.g. via select_related), or hopefully some other property
                 # that doesn't support prefetching but needs to be traversed.
 
                 # We replace the current list of parent objects with that list.
+                # TODO: Check what happens if attr resolves to local field?
+                # User typoing rel_attr_id instead of rel_attr? AND there are
+                # multiple parts in the path left.
                 obj_list = [getattr(obj, attr) for obj in obj_list]
 
                 # Filter out 'None' so that we can continue with nullable
@@ -1688,6 +1729,12 @@ def get_prefetcher(instance, attr):
         try:
             rel_obj = getattr(instance, attr)
             attr_found = True
+            # If we are following a r_obj lookup path which leads us through
+            # a previous fetch with to_attr, then we might end up into a list
+            # instead of related qs. This means the objects are already
+            # fetched.
+            if isinstance(rel_obj, list):
+                is_fetched = True
         except AttributeError:
             pass
     else:
@@ -1709,7 +1756,7 @@ def get_prefetcher(instance, attr):
     return prefetcher, rel_obj_descriptor, attr_found, is_fetched
 
 
-def prefetch_one_level(instances, prefetcher, attname):
+def prefetch_one_level(instances, prefetcher, r_obj, level):
     """
     Helper function for prefetch_related_objects
 
@@ -1733,7 +1780,8 @@ def prefetch_one_level(instances, prefetcher, attname):
     # in a dictionary.
 
     rel_qs, rel_obj_attr, instance_attr, single, cache_name =\
-        prefetcher.get_prefetch_query_set(instances)
+        prefetcher.get_prefetch_query_set(instances, custom_qs=r_obj.qs)
+
     # We have to handle the possibility that the default manager itself added
     # prefetch_related lookups to the QuerySet we just got back. We don't want to
     # trigger the prefetch_related functionality by evaluating the query.
@@ -1754,7 +1802,16 @@ def prefetch_one_level(instances, prefetcher, attname):
             rel_obj_cache[rel_attr_val] = []
         rel_obj_cache[rel_attr_val].append(rel_obj)
 
+
+    # to_attr is the name of the attribute we will be fetching into, to_list
+    # is False if to_attr refers to related manager. If it refers to related
+    # manager, we will be caching in rel_manager.all(), otherwise in a list.
+    to_attr, to_list = r_obj.get_to_attr(level)
     for obj in instances:
+        # TODO: in this case we could set the reverse attribute if the relation
+        # is o2o. Both this and the TODO below are handled by select_related
+        # in the get_cached_row iterator construction. Maybe that code could
+        # be generalized and shared.
         instance_attr_val = instance_attr(obj)
         vals = rel_obj_cache.get(instance_attr_val, [])
         if single:
@@ -1764,10 +1821,17 @@ def prefetch_one_level(instances, prefetcher, attname):
         else:
             # Multi, attribute represents a manager with an .all() method that
             # returns a QuerySet
-            qs = getattr(obj, attname).all()
-            qs._result_cache = vals
-            # We don't want the individual qs doing prefetch_related now, since we
-            # have merged this into the current work.
-            qs._prefetch_done = True
-            obj._prefetched_objects_cache[cache_name] = qs
+            # TODO: we could set the reverse relation, so that if user does
+            # access the just fetched relation in the reverse order, we would
+            # not need to do a query. We can't do this for m2m, of course.
+            if to_list:
+                setattr(obj, to_attr, vals)
+            else:
+                # Cache in the QuerySet.all().
+                qs = getattr(obj, to_attr).all()
+                qs._result_cache = vals
+                # We don't want the individual qs doing prefetch_related now,
+                # since we have merged this into the current work.
+                qs._prefetch_done = True
+                obj._prefetched_objects_cache[cache_name] = qs
     return all_related_objects, additional_prl
diff --git a/django/db/models/related.py b/django/db/models/related.py
index 90995d7..2c1e954 100644
--- a/django/db/models/related.py
+++ b/django/db/models/related.py
@@ -1,5 +1,6 @@
 from django.utils.encoding import smart_unicode
 from django.db.models.fields import BLANK_CHOICE_DASH
+from django.db.models.sql.constants import LOOKUP_SEP
 
 class BoundRelatedObject(object):
     def __init__(self, related_object, field_mapping, original):
@@ -36,7 +37,7 @@ class RelatedObject(object):
                 {'%s__isnull' % self.parent_model._meta.module_name: False})
         lst = [(x._get_pk_val(), smart_unicode(x)) for x in queryset]
         return first_choice + lst
-        
+
     def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False):
         # Defer to the actual field definition for db prep
         return self.field.get_db_prep_lookup(lookup_type, value,
@@ -67,3 +68,86 @@ class RelatedObject(object):
 
     def get_cache_name(self):
         return "_%s_cache" % self.get_accessor_name()
+
+# Not knowing a better place for this, I just planted R here.
+# Feel free to move this to a better place or remove this comment.
+class R(object):
+    """
+    A class used for passing options to .prefetch_related. Note that instances
+    of this class should be considered immutable.
+    """
+
+    # For R-objects, we have two different internal lookup paths:
+    #   - lookup: This is the related object attribute name
+    #   - lookup_refpath: This is to be used when this R-object is referenced
+    #     in chained prefetches.
+    # Check out the source of R-objects to see what is happening there.
+    #
+    # The difference is needed, because when we chain R-objects with to_attr
+    # defined, the lookup_path (how we got here) and lookup_refpath (how to
+    # get forward from here) will be different. For example:
+    # R('foo', to_attr='foolst') -> lookup_path = foo, that is we are going
+    # to prefetch through relation foo.
+    #
+    # If there would be another qs produced by R, the lookup_refpath would
+    # need to be 'foolst__nextpart'. Otherwise we can't distinguish between
+    # two different prefetch_related lookups to 'foo' (perhaps with custom
+    # querysets).
+    #
+    # Luckily the user does not need to know anything about this.
+
+    def __init__(self, lookup, to_attr=None, qs=None):
+        if qs is not None and not to_attr:
+            raise ValueError('When custom qs is defined, to_attr '
+                             'must also be defined')
+        self.lookup = lookup
+        self.to_attr = to_attr
+        self.qs = qs
+
+    def _new_prefixed(self, prefix):
+        """
+        _new_internal is to be used when prefetches are chained internally.
+        The returned R-object is identical to self, except lookup_path
+        is prefixed with prefix.
+        """
+        new_lookup = prefix + LOOKUP_SEP + self.lookup
+        return R(new_lookup, to_attr=self.to_attr, qs=self.qs)
+
+    def __unicode__(self):
+        return ("lookup: %s, to_attr: %s, qs: %s" %
+            (self.lookup, self.to_attr or None, self.qs))
+
+    def __repr__(self):
+        return '<%s: %s>' % (self.__class__.__name__, unicode(self))
+
+    def __eq__(self, other):
+        if isinstance(other, R):
+            return self.lookup_refpath == other.lookup_refpath
+        return False
+
+    def _lookup_refpath(self):
+        if self.to_attr is None:
+            return self.lookup
+        else:
+            path, sep, last_part = self.lookup.rpartition(LOOKUP_SEP)
+            return path + sep + self.to_attr
+    lookup_refpath = property(_lookup_refpath)
+
+    def get_current_lookup(self, level):
+        """
+        Returns the first level + 1 parts of the self.lookup_refpath
+        """
+        parts = self.lookup_refpath.split(LOOKUP_SEP)
+        return LOOKUP_SEP.join(parts[0:level + 1])
+
+    def get_to_attr(self, level):
+        """
+        Returns information about into what attribute should the results be
+        fetched, and if that attribute is related object manager, or will the
+        objects be fetched into a list.
+        """
+        parts = self.lookup_refpath.split(LOOKUP_SEP)
+        if self.to_attr is None or level < len(parts) - 1:
+            return parts[level], False
+        else:
+            return self.to_attr, True
diff --git a/tests/modeltests/prefetch_related/models.py b/tests/modeltests/prefetch_related/models.py
index 1c14c88..3a996db 100644
--- a/tests/modeltests/prefetch_related/models.py
+++ b/tests/modeltests/prefetch_related/models.py
@@ -58,6 +58,32 @@ class BookWithYear(Book):
         AuthorWithAge, related_name='books_with_year')
 
 
+class AuthorDefManager(models.Manager):
+    # Default manager with possibly recursive results.
+    def get_query_set(self):
+        qs = super(AuthorDefManager, self).get_query_set()
+        return qs.prefetch_related('best_friend_reverse', 'books')
+
+class AuthorWithDefPrefetch(models.Model):
+    name = models.TextField()
+    best_friend = models.ForeignKey(
+         'self', related_name='best_friend_reverse', null=True)
+    objects = AuthorDefManager()
+
+class BookDefManager(models.Manager):
+    # No need for guard here, author's manager will take care of that.
+    def get_query_set(self):
+        return (super(BookDefManager, self).get_query_set()
+                .prefetch_related('authors'))
+
+class BookWithDefPrefetch(models.Model):
+    name = models.TextField()
+    authors = models.ManyToManyField(AuthorWithDefPrefetch,
+                                     related_name='books')
+
+    objects = BookDefManager()
+ 
+
 class Reader(models.Model):
     name = models.CharField(max_length=50)
     books_read = models.ManyToManyField(Book, related_name='read_by')
@@ -155,7 +181,7 @@ class Person(models.Model):
         ordering = ['id']
 
 
-## Models for nullable FK tests
+## Models for nullable FK tests and recursive prefetch_related tests.
 
 class Employee(models.Model):
     name = models.CharField(max_length=50)
diff --git a/tests/modeltests/prefetch_related/tests.py b/tests/modeltests/prefetch_related/tests.py
index bdbb056..f424fc1 100644
--- a/tests/modeltests/prefetch_related/tests.py
+++ b/tests/modeltests/prefetch_related/tests.py
@@ -1,13 +1,32 @@
 from __future__ import with_statement
 
 from django.contrib.contenttypes.models import ContentType
+from django.db.models import R
 from django.test import TestCase
 from django.utils import unittest
 
 from models import (Author, Book, Reader, Qualification, Teacher, Department,
                     TaggedItem, Bookmark, AuthorAddress, FavoriteAuthors,
                     AuthorWithAge, BookWithYear, Person, House, Room,
-                    Employee)
+                    Employee, AuthorWithDefPrefetch, BookWithDefPrefetch)
+
+def traverse_qs(obj_iter, path):
+    """
+    Helper method that returns a list containing a list of the objects in the
+    obj_iter. Then for each object in the obj_iter, the path will be
+    recursively travelled and the found objects are added to the return value.
+    """
+    ret_val = []
+    if hasattr(obj_iter, 'all'):
+        obj_iter = obj_iter.all()
+    for obj in obj_iter:
+        rel_objs = []
+        for part in path:
+            if not part:
+                continue
+            rel_objs.extend(traverse_qs(getattr(obj, part[0]), [part[1:]]))
+        ret_val.append((obj, rel_objs))
+    return ret_val
 
 
 class PrefetchRelatedTests(TestCase):
@@ -39,6 +58,15 @@ class PrefetchRelatedTests(TestCase):
         self.reader1.books_read.add(self.book1, self.book4)
         self.reader2.books_read.add(self.book2, self.book4)
 
+    def test_metatest_traverse_qs(self):
+        qs = Book.objects.prefetch_related('authors')
+        related_objs_normal = [list(b.authors.all()) for b in qs],
+        related_objs_from_traverse = [[inner[0] for inner in o[1]]
+                                      for o in traverse_qs(qs, [['authors']])]
+        self.assertEquals(related_objs_normal, (related_objs_from_traverse,))
+        self.assertFalse(related_objs_from_traverse == traverse_qs(qs.filter(pk=1),
+                         [['authors']]))
+
     def test_m2m_forward(self):
         with self.assertNumQueries(2):
             lists = [list(b.authors.all()) for b in Book.objects.prefetch_related('authors')]
@@ -472,3 +500,117 @@ class NullableTest(TestCase):
                         for e in qs2]
 
         self.assertEqual(co_serfs, co_serfs2)
+
+
+class RObjectTest(TestCase):
+    def setUp(self):
+        self.person1 = Person.objects.create(name="Joe")
+        self.person2 = Person.objects.create(name="Mary")
+
+        self.house1 = House.objects.create(address="123 Main St")
+        self.house2 = House.objects.create(address="45 Side St")
+        self.house3 = House.objects.create(address="6 Downing St")
+        self.house4 = House.objects.create(address="7 Regents St")
+
+        self.room1_1 = Room.objects.create(name="Dining room", house=self.house1)
+        self.room1_2 = Room.objects.create(name="Lounge", house=self.house1)
+        self.room1_3 = Room.objects.create(name="Kitchen", house=self.house1)
+
+        self.room2_1 = Room.objects.create(name="Dining room", house=self.house2)
+        self.room2_2 = Room.objects.create(name="Lounge", house=self.house2)
+
+        self.room3_1 = Room.objects.create(name="Dining room", house=self.house3)
+        self.room3_2 = Room.objects.create(name="Lounge", house=self.house3)
+        self.room3_3 = Room.objects.create(name="Kitchen", house=self.house3)
+
+        self.room4_1 = Room.objects.create(name="Dining room", house=self.house4)
+        self.room4_2 = Room.objects.create(name="Lounge", house=self.house4)
+
+        self.person1.houses.add(self.house1, self.house2)
+        self.person2.houses.add(self.house3, self.house4)
+
+    def test_robj_basics(self):
+        # Test different combinations of R and non-R lookups
+        with self.assertNumQueries(2):
+            lst1 = traverse_qs(Person.objects.prefetch_related('houses'),
+                               [['houses']])
+        with self.assertNumQueries(2):
+            lst2 = traverse_qs(Person.objects.prefetch_related(R('houses')),
+                               [['houses']])
+        self.assertEquals(lst1, lst2)
+        with self.assertNumQueries(3):
+            lst1 = traverse_qs(Person.objects.prefetch_related('houses', 'houses__rooms'),
+                               [['houses', 'rooms']])
+        with self.assertNumQueries(3):
+            lst2 = traverse_qs(Person.objects.prefetch_related(R('houses'), R('houses__rooms')),
+                               [['houses', 'rooms']])
+        self.assertEquals(lst1, lst2)
+        with self.assertNumQueries(3):
+            lst1 = traverse_qs(Person.objects.prefetch_related('houses', 'houses__rooms'),
+                               [['houses', 'rooms']])
+        with self.assertNumQueries(3):
+            lst2 = traverse_qs(Person.objects.prefetch_related(R('houses'), 'houses__rooms'),
+                               [['houses', 'rooms']])
+        self.assertEquals(lst1, lst2)
+        # Test to_attr
+        with self.assertNumQueries(3):
+            lst1 = traverse_qs(Person.objects.prefetch_related('houses', 'houses__rooms'),
+                               [['houses', 'rooms']])
+        with self.assertNumQueries(3):
+            lst2 = traverse_qs(Person.objects.prefetch_related(
+                                  R('houses', to_attr='houses_lst'),
+                                  'houses_lst__rooms'),
+                               [['houses_lst', 'rooms']])
+        self.assertEquals(lst1, lst2)
+
+        with self.assertNumQueries(4):
+            qs = list(Person.objects.prefetch_related(
+                    R('houses', to_attr='houses_lst'),
+                    R('houses__rooms', to_attr='rooms_lst')
+            ))
+            with self.assertRaises(AttributeError):
+                qs[0].houses_lst2[0].rooms_lst
+            qs[0].houses.all()[0].rooms_lst
+            lst2 = traverse_qs(
+                qs, [['houses', 'rooms_lst']]
+            )
+            self.assertEquals(lst1, lst2)
+            self.assertEquals(
+                traverse_qs(qs, [['houses']]),
+                traverse_qs(qs, [['houses_lst']])
+            )
+
+    def test_custom_qs(self):
+        person_qs = Person.objects.all()
+        houses_qs = House.objects.all()
+        with self.assertNumQueries(2):
+             lst1 = list(person_qs.prefetch_related('houses'))
+        with self.assertNumQueries(2):
+             lst2 = list(person_qs.prefetch_related(
+                 R('houses', qs=houses_qs, to_attr='houses_lst')
+             ))
+        self.assertEquals(
+            traverse_qs(lst1, [['houses']]),
+            traverse_qs(lst2, [['houses_lst']])
+        )
+        with self.assertNumQueries(2):
+            lst2 = list(person_qs.prefetch_related(
+                R('houses', qs=houses_qs.filter(pk__in=[self.house1.pk, self.house3.pk]),
+                  to_attr='hlst')
+            ))
+        self.assertEquals(len(lst2[0].hlst), 1)
+        self.assertEquals(lst2[0].hlst[0], self.house1)
+        self.assertEquals(len(lst2[1].hlst), 1)
+        self.assertEquals(lst2[1].hlst[0], self.house3)
+
+        inner_rooms_qs = Room.objects.filter(pk__in=[self.room1_1.pk, self.room1_2.pk])
+        houses_qs_prf = houses_qs.prefetch_related(
+            R('rooms', qs=inner_rooms_qs, to_attr='rooms_lst'))
+        with self.assertNumQueries(3):
+            lst2 = list(person_qs.prefetch_related(
+                       R('houses', qs=houses_qs_prf.filter(pk=self.house1.pk), to_attr='hlst'),
+                   ))
+        self.assertEquals(len(lst2[0].hlst[0].rooms_lst), 2)
+        self.assertEquals(lst2[0].hlst[0].rooms_lst[0], self.room1_1)
+        self.assertEquals(lst2[0].hlst[0].rooms_lst[1], self.room1_2)
+        self.assertEquals(len(lst2[1].hlst), 0)
