diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index f052fe1..163110b 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -306,13 +306,6 @@ class ModelAdmin(BaseModelAdmin):
         for inline_class in self.inlines:
             inline_instance = inline_class(self.model, self.admin_site)
             self.inline_instances.append(inline_instance)
-        if 'action_checkbox' not in self.list_display and self.actions is not None:
-            self.list_display = ['action_checkbox'] +  list(self.list_display)
-        if not self.list_display_links:
-            for name in self.list_display:
-                if name != 'action_checkbox':
-                    self.list_display_links = [name]
-                    break
         super(ModelAdmin, self).__init__()
 
     def get_urls(self):
@@ -637,7 +630,23 @@ class ModelAdmin(BaseModelAdmin):
         Return a sequence containing the fields to be displayed on the
         changelist.
         """
-        return self.list_display
+        list_display = self.list_display
+        if 'action_checkbox' not in list_display and self.actions is not None:
+            list_display = ['action_checkbox'] +  list(list_display)
+        return list_display
+
+    def get_list_display_links(self, request, list_display):
+        """
+        Return a sequence containing the fields to be displayed as links
+        on the changelist.
+        """
+        list_display_links = self.list_display_links
+        if not list_display_links:
+            for name in list_display:
+                if name != 'action_checkbox':
+                    list_display_links = [name]
+                    break
+        return list_display_links
 
     def construct_change_message(self, request, form, formsets):
         """
@@ -1074,9 +1083,11 @@ class ModelAdmin(BaseModelAdmin):
             except ValueError:
                 pass
 
+        list_display_links = list(self.get_list_display_links(request, list_display))
+
         ChangeList = self.get_changelist(request)
         try:
-            cl = ChangeList(request, self.model, list_display, self.list_display_links,
+            cl = ChangeList(request, self.model, list_display, list_display_links,
                 self.list_filter, self.date_hierarchy, self.search_fields,
                 self.list_select_related, self.list_per_page, self.list_editable, self)
         except IncorrectLookupParameters:
diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt
index c0ff3c6..a640950 100644
--- a/docs/ref/contrib/admin/index.txt
+++ b/docs/ref/contrib/admin/index.txt
@@ -1005,6 +1005,16 @@ templates used by the :class:`ModelAdmin` views:
     displayed on the changelist view as described above in the
     :attr:`ModelAdmin.list_display` section.
 
+.. method:: ModelAdmin.get_list_display_links(self, request, list_display)
+
+    .. versionadded:: 1.4
+
+    The ``get_list_display_links`` method is given the ``HttpRequest`` and
+    the ``list`` or ``tuple`` returned from :meth:`ModelAdmin.get_list_display`
+    and is expected to return a ``list`` or ``tuple`` of field names that
+    will be displayed on the changelist view as described above in the
+    :attr:`ModelAdmin.list_display_links` section.
+
 .. method:: ModelAdmin.get_urls(self)
 
     The ``get_urls`` method on a ``ModelAdmin`` returns the URLs to be used for
diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt
index cc20795..dbcc292 100644
--- a/docs/releases/1.4.txt
+++ b/docs/releases/1.4.txt
@@ -80,6 +80,14 @@ to work similarly to how desktop GUIs do it. The new hook
 :meth:`~django.contrib.admin.ModelAdmin.get_ordering` for specifying the
 ordering dynamically (e.g. depending on the request) has also been added.
 
+New ``ModelAdmin`` methods
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Two new methods, :meth:`~ModelAdmin.get_list_display` and
+:meth:`~ModelAdmin.get_list_display_links` were added to :class:`~ModelAdmin` to
+enable the dynamic customization of fields and links to display on the admin
+change list.
+
 Tools for cryptographic signing
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/tests/regressiontests/admin_changelist/models.py b/tests/regressiontests/admin_changelist/models.py
index 509d37c..633d8bb 100644
--- a/tests/regressiontests/admin_changelist/models.py
+++ b/tests/regressiontests/admin_changelist/models.py
@@ -6,6 +6,7 @@ class Parent(models.Model):
 class Child(models.Model):
     parent = models.ForeignKey(Parent, editable=False, null=True)
     name = models.CharField(max_length=30, blank=True)
+    age = models.IntegerField(null=True, blank=True)
 
 class Genre(models.Model):
     name = models.CharField(max_length=20)
diff --git a/tests/regressiontests/admin_changelist/tests.py b/tests/regressiontests/admin_changelist/tests.py
index 709fa76..b221990 100644
--- a/tests/regressiontests/admin_changelist/tests.py
+++ b/tests/regressiontests/admin_changelist/tests.py
@@ -35,7 +35,9 @@ class ChangeListTests(TestCase):
         new_child = Child.objects.create(name='name', parent=None)
         request = self.factory.get('/child/')
         m = ChildAdmin(Child, admin.site)
-        cl = ChangeList(request, Child, m.list_display, m.list_display_links,
+        list_display = m.get_list_display(request)
+        list_display_links = m.get_list_display_links(request, list_display)
+        cl = ChangeList(request, Child, list_display, list_display_links,
                 m.list_filter, m.date_hierarchy, m.search_fields,
                 m.list_select_related, m.list_per_page, m.list_editable, m)
         cl.formset = None
@@ -55,7 +57,9 @@ class ChangeListTests(TestCase):
         new_child = Child.objects.create(name='name', parent=new_parent)
         request = self.factory.get('/child/')
         m = ChildAdmin(Child, admin.site)
-        cl = ChangeList(request, Child, m.list_display, m.list_display_links,
+        list_display = m.get_list_display(request)
+        list_display_links = m.get_list_display_links(request, list_display)
+        cl = ChangeList(request, Child, list_display, list_display_links,
                 m.list_filter, m.date_hierarchy, m.search_fields,
                 m.list_select_related, m.list_per_page, m.list_editable, m)
         cl.formset = None
@@ -335,6 +339,11 @@ class ChangeListTests(TestCase):
         response.render()
         self.assertNotContains(response, 'Parent object')
 
+        list_display = m.get_list_display(request)
+        list_display_links = m.get_list_display_links(request, list_display)
+        self.assertEqual(list_display, ['action_checkbox', 'name', 'age'])
+        self.assertEqual(list_display_links, ['name'])
+
         # Test with user 'parents'
         m = DynamicListDisplayChildAdmin(Child, admin.site)
         request = _mocked_authenticated_request(user_parents)
@@ -343,6 +352,11 @@ class ChangeListTests(TestCase):
         response.render()
         self.assertContains(response, 'Parent object')
 
+        list_display = m.get_list_display(request)
+        list_display_links = m.get_list_display_links(request, list_display)
+        self.assertEqual(list_display, ['action_checkbox', 'parent', 'name', 'age'])
+        self.assertEqual(list_display_links, ['parent'])
+
         # Test default implementation
         m = ChildAdmin(Child, admin.site)
         request = _mocked_authenticated_request(user_noparents)
@@ -351,6 +365,37 @@ class ChangeListTests(TestCase):
         response.render()
         self.assertContains(response, 'Parent object')
 
+    def test_dynamic_list_display_links(self):
+        """
+        Regression tests for #16257: dynamic list_display_links support.
+        """
+        parent = Parent.objects.create(name='parent')
+        for i in range(1, 10):
+            Child.objects.create(id=i, name='child %s' % i, parent=parent, age=i)
+
+        superuser = User.objects.create(
+            username='superuser',
+            is_superuser=True)
+
+        def _mocked_authenticated_request(user):
+            request = self.factory.get('/child/')
+            request.user = user
+            return request
+
+        m = DynamicListDisplayLinksChildAdmin(Child, admin.site)
+        request = _mocked_authenticated_request(superuser)
+        response = m.changelist_view(request)
+        # XXX - Calling render here to avoid ContentNotRenderedError to be
+        # raised. Ticket #15826 should fix this but it's not yet integrated.
+        response.render()
+        for i in range(1, 10):
+            self.assertContains(response, '<a href="%s/">%s</a>' % (i, i))
+
+        list_display = m.get_list_display(request)
+        list_display_links = m.get_list_display_links(request, list_display)
+        self.assertEqual(list_display, ['action_checkbox', 'parent', 'name', 'age'])
+        self.assertEqual(list_display_links, ['age'])
+
 
 class ParentAdmin(admin.ModelAdmin):
     list_filter = ['child__name']
@@ -397,11 +442,17 @@ class ChordsBandAdmin(admin.ModelAdmin):
 
 
 class DynamicListDisplayChildAdmin(admin.ModelAdmin):
-    list_display = ('name', 'parent')
+    list_display = ('parent', 'name', 'age')
 
     def get_list_display(self, request):
-        my_list_display = list(self.list_display)
+        my_list_display = super(DynamicListDisplayChildAdmin, self).get_list_display(request)
         if request.user.username == 'noparents':
             my_list_display.remove('parent')
-
         return my_list_display
+
+class DynamicListDisplayLinksChildAdmin(admin.ModelAdmin):
+    list_display = ('parent', 'name', 'age')
+    list_display_links = ['parent', 'name']
+
+    def get_list_display_links(self, request, list_display):
+        return ['age']
diff --git a/tests/regressiontests/admin_registration/tests.py b/tests/regressiontests/admin_registration/tests.py
index 92e9d8a..b92913d 100644
--- a/tests/regressiontests/admin_registration/tests.py
+++ b/tests/regressiontests/admin_registration/tests.py
@@ -39,7 +39,7 @@ class TestRegistration(TestCase):
                            search_fields=["name"], list_display=['__str__'])
         self.assertEqual(self.site._registry[Person].search_fields, ['name'])
         self.assertEqual(self.site._registry[Person].list_display,
-                         ['action_checkbox', '__str__'])
+                         ['__str__'])
         self.assertTrue(self.site._registry[Person].save_on_top)
 
     def test_iterable_registration(self):
