Code

Ticket #16257: 16257.get_list_display_links.diff

File 16257.get_list_display_links.diff, 10.3 KB (added by julien, 3 years ago)
Line 
1diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
2index f052fe1..163110b 100644
3--- a/django/contrib/admin/options.py
4+++ b/django/contrib/admin/options.py
5@@ -306,13 +306,6 @@ class ModelAdmin(BaseModelAdmin):
6         for inline_class in self.inlines:
7             inline_instance = inline_class(self.model, self.admin_site)
8             self.inline_instances.append(inline_instance)
9-        if 'action_checkbox' not in self.list_display and self.actions is not None:
10-            self.list_display = ['action_checkbox'] +  list(self.list_display)
11-        if not self.list_display_links:
12-            for name in self.list_display:
13-                if name != 'action_checkbox':
14-                    self.list_display_links = [name]
15-                    break
16         super(ModelAdmin, self).__init__()
17 
18     def get_urls(self):
19@@ -637,7 +630,23 @@ class ModelAdmin(BaseModelAdmin):
20         Return a sequence containing the fields to be displayed on the
21         changelist.
22         """
23-        return self.list_display
24+        list_display = self.list_display
25+        if 'action_checkbox' not in list_display and self.actions is not None:
26+            list_display = ['action_checkbox'] +  list(list_display)
27+        return list_display
28+
29+    def get_list_display_links(self, request, list_display):
30+        """
31+        Return a sequence containing the fields to be displayed as links
32+        on the changelist.
33+        """
34+        list_display_links = self.list_display_links
35+        if not list_display_links:
36+            for name in list_display:
37+                if name != 'action_checkbox':
38+                    list_display_links = [name]
39+                    break
40+        return list_display_links
41 
42     def construct_change_message(self, request, form, formsets):
43         """
44@@ -1074,9 +1083,11 @@ class ModelAdmin(BaseModelAdmin):
45             except ValueError:
46                 pass
47 
48+        list_display_links = list(self.get_list_display_links(request, list_display))
49+
50         ChangeList = self.get_changelist(request)
51         try:
52-            cl = ChangeList(request, self.model, list_display, self.list_display_links,
53+            cl = ChangeList(request, self.model, list_display, list_display_links,
54                 self.list_filter, self.date_hierarchy, self.search_fields,
55                 self.list_select_related, self.list_per_page, self.list_editable, self)
56         except IncorrectLookupParameters:
57diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt
58index c0ff3c6..a640950 100644
59--- a/docs/ref/contrib/admin/index.txt
60+++ b/docs/ref/contrib/admin/index.txt
61@@ -1005,6 +1005,16 @@ templates used by the :class:`ModelAdmin` views:
62     displayed on the changelist view as described above in the
63     :attr:`ModelAdmin.list_display` section.
64 
65+.. method:: ModelAdmin.get_list_display_links(self, request, list_display)
66+
67+    .. versionadded:: 1.4
68+
69+    The ``get_list_display_links`` method is given the ``HttpRequest`` and
70+    the ``list`` or ``tuple`` returned from :meth:`ModelAdmin.get_list_display`
71+    and is expected to return a ``list`` or ``tuple`` of field names that
72+    will be displayed on the changelist view as described above in the
73+    :attr:`ModelAdmin.list_display_links` section.
74+
75 .. method:: ModelAdmin.get_urls(self)
76 
77     The ``get_urls`` method on a ``ModelAdmin`` returns the URLs to be used for
78diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt
79index cc20795..dbcc292 100644
80--- a/docs/releases/1.4.txt
81+++ b/docs/releases/1.4.txt
82@@ -80,6 +80,14 @@ to work similarly to how desktop GUIs do it. The new hook
83 :meth:`~django.contrib.admin.ModelAdmin.get_ordering` for specifying the
84 ordering dynamically (e.g. depending on the request) has also been added.
85 
86+New ``ModelAdmin`` methods
87+~~~~~~~~~~~~~~~~~~~~~~~~~~
88+
89+Two new methods, :meth:`~ModelAdmin.get_list_display` and
90+:meth:`~ModelAdmin.get_list_display_links` were added to :class:`~ModelAdmin` to
91+enable the dynamic customization of fields and links to display on the admin
92+change list.
93+
94 Tools for cryptographic signing
95 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
96 
97diff --git a/tests/regressiontests/admin_changelist/models.py b/tests/regressiontests/admin_changelist/models.py
98index 509d37c..633d8bb 100644
99--- a/tests/regressiontests/admin_changelist/models.py
100+++ b/tests/regressiontests/admin_changelist/models.py
101@@ -6,6 +6,7 @@ class Parent(models.Model):
102 class Child(models.Model):
103     parent = models.ForeignKey(Parent, editable=False, null=True)
104     name = models.CharField(max_length=30, blank=True)
105+    age = models.IntegerField(null=True, blank=True)
106 
107 class Genre(models.Model):
108     name = models.CharField(max_length=20)
109diff --git a/tests/regressiontests/admin_changelist/tests.py b/tests/regressiontests/admin_changelist/tests.py
110index 709fa76..b221990 100644
111--- a/tests/regressiontests/admin_changelist/tests.py
112+++ b/tests/regressiontests/admin_changelist/tests.py
113@@ -35,7 +35,9 @@ class ChangeListTests(TestCase):
114         new_child = Child.objects.create(name='name', parent=None)
115         request = self.factory.get('/child/')
116         m = ChildAdmin(Child, admin.site)
117-        cl = ChangeList(request, Child, m.list_display, m.list_display_links,
118+        list_display = m.get_list_display(request)
119+        list_display_links = m.get_list_display_links(request, list_display)
120+        cl = ChangeList(request, Child, list_display, list_display_links,
121                 m.list_filter, m.date_hierarchy, m.search_fields,
122                 m.list_select_related, m.list_per_page, m.list_editable, m)
123         cl.formset = None
124@@ -55,7 +57,9 @@ class ChangeListTests(TestCase):
125         new_child = Child.objects.create(name='name', parent=new_parent)
126         request = self.factory.get('/child/')
127         m = ChildAdmin(Child, admin.site)
128-        cl = ChangeList(request, Child, m.list_display, m.list_display_links,
129+        list_display = m.get_list_display(request)
130+        list_display_links = m.get_list_display_links(request, list_display)
131+        cl = ChangeList(request, Child, list_display, list_display_links,
132                 m.list_filter, m.date_hierarchy, m.search_fields,
133                 m.list_select_related, m.list_per_page, m.list_editable, m)
134         cl.formset = None
135@@ -335,6 +339,11 @@ class ChangeListTests(TestCase):
136         response.render()
137         self.assertNotContains(response, 'Parent object')
138 
139+        list_display = m.get_list_display(request)
140+        list_display_links = m.get_list_display_links(request, list_display)
141+        self.assertEqual(list_display, ['action_checkbox', 'name', 'age'])
142+        self.assertEqual(list_display_links, ['name'])
143+
144         # Test with user 'parents'
145         m = DynamicListDisplayChildAdmin(Child, admin.site)
146         request = _mocked_authenticated_request(user_parents)
147@@ -343,6 +352,11 @@ class ChangeListTests(TestCase):
148         response.render()
149         self.assertContains(response, 'Parent object')
150 
151+        list_display = m.get_list_display(request)
152+        list_display_links = m.get_list_display_links(request, list_display)
153+        self.assertEqual(list_display, ['action_checkbox', 'parent', 'name', 'age'])
154+        self.assertEqual(list_display_links, ['parent'])
155+
156         # Test default implementation
157         m = ChildAdmin(Child, admin.site)
158         request = _mocked_authenticated_request(user_noparents)
159@@ -351,6 +365,37 @@ class ChangeListTests(TestCase):
160         response.render()
161         self.assertContains(response, 'Parent object')
162 
163+    def test_dynamic_list_display_links(self):
164+        """
165+        Regression tests for #16257: dynamic list_display_links support.
166+        """
167+        parent = Parent.objects.create(name='parent')
168+        for i in range(1, 10):
169+            Child.objects.create(id=i, name='child %s' % i, parent=parent, age=i)
170+
171+        superuser = User.objects.create(
172+            username='superuser',
173+            is_superuser=True)
174+
175+        def _mocked_authenticated_request(user):
176+            request = self.factory.get('/child/')
177+            request.user = user
178+            return request
179+
180+        m = DynamicListDisplayLinksChildAdmin(Child, admin.site)
181+        request = _mocked_authenticated_request(superuser)
182+        response = m.changelist_view(request)
183+        # XXX - Calling render here to avoid ContentNotRenderedError to be
184+        # raised. Ticket #15826 should fix this but it's not yet integrated.
185+        response.render()
186+        for i in range(1, 10):
187+            self.assertContains(response, '<a href="%s/">%s</a>' % (i, i))
188+
189+        list_display = m.get_list_display(request)
190+        list_display_links = m.get_list_display_links(request, list_display)
191+        self.assertEqual(list_display, ['action_checkbox', 'parent', 'name', 'age'])
192+        self.assertEqual(list_display_links, ['age'])
193+
194 
195 class ParentAdmin(admin.ModelAdmin):
196     list_filter = ['child__name']
197@@ -397,11 +442,17 @@ class ChordsBandAdmin(admin.ModelAdmin):
198 
199 
200 class DynamicListDisplayChildAdmin(admin.ModelAdmin):
201-    list_display = ('name', 'parent')
202+    list_display = ('parent', 'name', 'age')
203 
204     def get_list_display(self, request):
205-        my_list_display = list(self.list_display)
206+        my_list_display = super(DynamicListDisplayChildAdmin, self).get_list_display(request)
207         if request.user.username == 'noparents':
208             my_list_display.remove('parent')
209-
210         return my_list_display
211+
212+class DynamicListDisplayLinksChildAdmin(admin.ModelAdmin):
213+    list_display = ('parent', 'name', 'age')
214+    list_display_links = ['parent', 'name']
215+
216+    def get_list_display_links(self, request, list_display):
217+        return ['age']
218diff --git a/tests/regressiontests/admin_registration/tests.py b/tests/regressiontests/admin_registration/tests.py
219index 92e9d8a..b92913d 100644
220--- a/tests/regressiontests/admin_registration/tests.py
221+++ b/tests/regressiontests/admin_registration/tests.py
222@@ -39,7 +39,7 @@ class TestRegistration(TestCase):
223                            search_fields=["name"], list_display=['__str__'])
224         self.assertEqual(self.site._registry[Person].search_fields, ['name'])
225         self.assertEqual(self.site._registry[Person].list_display,
226-                         ['action_checkbox', '__str__'])
227+                         ['__str__'])
228         self.assertTrue(self.site._registry[Person].save_on_top)
229 
230     def test_iterable_registration(self):