Index: django/contrib/admin/validation.py
===================================================================
--- django/contrib/admin/validation.py	(revision 14496)
+++ django/contrib/admin/validation.py	(working copy)
@@ -4,6 +4,7 @@
     _get_foreign_key)
 from django.contrib.admin.options import flatten_fieldsets, BaseModelAdmin
 from django.contrib.admin.options import HORIZONTAL, VERTICAL
+from django.contrib.admin.util import lookup_field
 
 
 __all__ = ['validate']
@@ -132,14 +133,17 @@
                             raise ImproperlyConfigured("%s.readonly_fields[%d], %r is not a callable or an attribute of %r or found in the model %r."
                                 % (cls.__name__, idx, field, cls.__name__, model._meta.object_name))
 
-    # list_select_related = False
     # save_as = False
     # save_on_top = False
-    for attr in ('list_select_related', 'save_as', 'save_on_top'):
+    for attr in ('save_as', 'save_on_top'):
         if not isinstance(getattr(cls, attr), bool):
             raise ImproperlyConfigured("'%s.%s' should be a boolean."
                     % (cls.__name__, attr))
 
+    # list_select_related = False
+    if not isinstance(cls.list_select_related, (bool, list, tuple)):
+        raise ImproperlyConfigured("'%s.list_select_related' should be a boolean, a list or a tuple."
+                                   % cls.__name__)
 
     # inlines = []
     if hasattr(cls, 'inlines'):
@@ -169,7 +173,7 @@
     fk = _get_foreign_key(parent_model, cls.model, fk_name=cls.fk_name, can_fail=True)
 
     # extra = 3
-    if not isinstance(cls.extra, int):
+    if not isinstance(getattr(cls, 'extra'), int):
         raise ImproperlyConfigured("'%s.extra' should be a integer."
                 % cls.__name__)
 
Index: django/contrib/admin/views/main.py
===================================================================
--- django/contrib/admin/views/main.py	(revision 14496)
+++ django/contrib/admin/views/main.py	(working copy)
@@ -3,6 +3,7 @@
 from django.contrib.admin.util import quote
 from django.core.paginator import Paginator, InvalidPage
 from django.db import models
+from django.db.models.query import QuerySet
 from django.utils.encoding import force_unicode, smart_str
 from django.utils.translation import ugettext
 from django.utils.http import urlencode
@@ -201,7 +202,9 @@
         # with a relationship and the provided queryset doesn't already have
         # select_related defined.
         if not qs.query.select_related:
-            if self.list_select_related:
+            if isinstance(self.list_select_related, (list, tuple)):
+                qs = qs.select_related(*self.list_select_related)
+            elif self.list_select_related:
                 qs = qs.select_related()
             else:
                 for field_name in self.list_display:
@@ -210,9 +213,8 @@
                     except models.FieldDoesNotExist:
                         pass
                     else:
-                        if isinstance(f.rel, models.ManyToOneRel):
-                            qs = qs.select_related()
-                            break
+                        if isinstance(f.rel, (models.ManyToOneRel, models.OneToOneRel)):
+                            qs = qs.select_related(field_name)
 
         # Set ordering.
         if self.order_field:
Index: tests/regressiontests/admin_changelist/tests.py
===================================================================
--- tests/regressiontests/admin_changelist/tests.py	(revision 14496)
+++ tests/regressiontests/admin_changelist/tests.py	(working copy)
@@ -2,7 +2,7 @@
 from django.contrib.admin.views.main import ChangeList
 from django.template import Context, Template
 from django.test import TransactionTestCase
-from regressiontests.admin_changelist.models import Child, Parent
+from regressiontests.admin_changelist.models import Child, Parent, NullableChild
 
 class ChangeListTests(TransactionTestCase):
     def test_select_related_preserved(self):
@@ -16,6 +16,18 @@
                 m.list_select_related, m.list_per_page, m.list_editable, m)
         self.assertEqual(cl.query_set.query.select_related, {'parent': {'name': {}}})
 
+    def test_select_related_nullable(self):
+        """
+        Regression test for #14396: allow list_select_related to explicitly 
+        specify relations to follow.
+        """
+        m = NullableChildAdmin(NullableChild, admin.site)
+        cl = ChangeList(MockRequest(), NullableChild, m.list_display, 
+                        m.list_display_links, m.list_filter, m.date_hierarchy, 
+                        m.search_fields, m.list_select_related, m.list_per_page, 
+                        m.list_editable, m)
+        self.assertEqual(cl.query_set.query.select_related, {'parent': {}})
+
     def test_result_list_html(self):
         """
         Verifies that inclusion tag result_list generates a table when with
@@ -76,5 +88,8 @@
     def queryset(self, request):
         return super(ChildAdmin, self).queryset(request).select_related("parent__name")
 
+class NullableChildAdmin(admin.ModelAdmin):
+    list_select_related = ('parent',)
+
 class MockRequest(object):
     GET = {}
Index: tests/regressiontests/admin_changelist/models.py
===================================================================
--- tests/regressiontests/admin_changelist/models.py	(revision 14496)
+++ tests/regressiontests/admin_changelist/models.py	(working copy)
@@ -6,4 +6,8 @@
 
 class Child(models.Model):
     parent = models.ForeignKey(Parent, editable=False)
-    name = models.CharField(max_length=30, blank=True)
\ No newline at end of file
+    name = models.CharField(max_length=30, blank=True)
+
+class NullableChild(models.Model):
+    parent = models.ForeignKey(Parent, null=True, blank=True)
+    name = models.CharField(max_length=30, blank=True)
Index: tests/regressiontests/modeladmin/tests.py
===================================================================
--- tests/regressiontests/modeladmin/tests.py	(revision 14496)
+++ tests/regressiontests/modeladmin/tests.py	(working copy)
@@ -963,7 +963,7 @@
 
         self.assertRaisesRegexp(
             ImproperlyConfigured,
-            "'ValidationTestModelAdmin.list_select_related' should be a boolean.",
+            "'ValidationTestModelAdmin.list_select_related' should be a boolean, a list or a tuple.",
             validate,
             ValidationTestModelAdmin,
             ValidationTestModel,
@@ -974,6 +974,11 @@
 
         validate(ValidationTestModelAdmin, ValidationTestModel)
 
+        class ValidationTestModelAdmin(ModelAdmin):
+            list_select_related = ('field1', 'field2')
+
+        validate(ValidationTestModelAdmin, ValidationTestModel)
+
     def test_save_as_validation(self):
 
         class ValidationTestModelAdmin(ModelAdmin):
Index: docs/ref/contrib/admin/index.txt
===================================================================
--- docs/ref/contrib/admin/index.txt	(revision 14496)
+++ docs/ref/contrib/admin/index.txt	(working copy)
@@ -476,11 +476,20 @@
 objects on the admin change list page. This can save you a bunch of database
 queries.
 
-The value should be either ``True`` or ``False``. Default is ``False``.
+The value should be one of:
 
+* ``True`` or ``False``. If ``True``, the standard ``select_related()`` call 
+  will be used. 
+
+* A list or tuple of field names. In this case, the value is used as the 
+  argument to :meth:`~django.db.models.QuerySet.select_related` which determines 
+  which relations should be followed.
+
+The default is ``False``.
+
 Note that Django will use :meth:`~django.db.models.QuerySet.select_related`,
 regardless of this setting, if one of the ``list_display`` fields is a
-``ForeignKey``.
+``ForeignKey`` or ``OneToOneField``.
 
 .. attribute:: ModelAdmin.inlines
 
