Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #16257 -- Added new `ModelAdmin.get_list_display_links()` metho…

…d to allow for the dynamic display of links on the admin changelist. Thanks to graveyboat for the suggestion and initial patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@17037 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 9796f69533320171bf22d769d6e4ab3f5a45aac1 1 parent a05c70f
Julien Phalip authored October 26, 2011
26  django/contrib/admin/options.py
@@ -650,6 +650,18 @@ def get_list_display(self, request):
650 650
         """
651 651
         return self.list_display
652 652
 
  653
+    def get_list_display_links(self, request, list_display):
  654
+        """
  655
+        Return a sequence containing the fields to be displayed as links
  656
+        on the changelist. The list_display parameter is the list of fields
  657
+        returned by get_list_display().
  658
+        """
  659
+        if self.list_display_links or not list_display:
  660
+            return self.list_display_links
  661
+        else:
  662
+            # Use only the first item in list_display as link
  663
+            return list(list_display)[:1]
  664
+
653 665
     def construct_change_message(self, request, form, formsets):
654 666
         """
655 667
         Construct a change message from a changed object.
@@ -1087,22 +1099,20 @@ def change_view(self, request, object_id, extra_context=None):
1087 1099
 
1088 1100
     @csrf_protect_m
1089 1101
     def changelist_view(self, request, extra_context=None):
1090  
-        "The 'change list' admin view for this model."
  1102
+        """
  1103
+        The 'change list' admin view for this model.
  1104
+        """
1091 1105
         from django.contrib.admin.views.main import ERROR_FLAG
1092 1106
         opts = self.model._meta
1093 1107
         app_label = opts.app_label
1094 1108
         if not self.has_change_permission(request, None):
1095 1109
             raise PermissionDenied
1096 1110
 
1097  
-        # Check actions to see if any are available on this changelist
1098  
-        actions = self.get_actions(request)
1099  
-
1100 1111
         list_display = self.get_list_display(request)
  1112
+        list_display_links = self.get_list_display_links(request, list_display)
1101 1113
 
1102  
-        list_display_links = self.list_display_links
1103  
-        if not self.list_display_links and list_display:
1104  
-            list_display_links = list(list_display)[:1]
1105  
-
  1114
+        # Check actions to see if any are available on this changelist
  1115
+        actions = self.get_actions(request)
1106 1116
         if actions:
1107 1117
             # Add the action checkboxes if there are any actions available.
1108 1118
             list_display = ['action_checkbox'] +  list(list_display)
10  docs/ref/contrib/admin/index.txt
@@ -1044,6 +1044,16 @@ templates used by the :class:`ModelAdmin` views:
1044 1044
     displayed on the changelist view as described above in the
1045 1045
     :attr:`ModelAdmin.list_display` section.
1046 1046
 
  1047
+.. method:: ModelAdmin.get_list_display_links(self, request, list_display)
  1048
+
  1049
+    .. versionadded:: 1.4
  1050
+
  1051
+    The ``get_list_display_links`` method is given the ``HttpRequest`` and
  1052
+    the ``list`` or ``tuple`` returned by :meth:`ModelAdmin.get_list_display`.
  1053
+    It is expected to return a ``list`` or ``tuple`` of field names on the
  1054
+    changelist that will be linked to the change view, as described in the
  1055
+    :attr:`ModelAdmin.list_display_links` section.
  1056
+
1047 1057
 .. method:: ModelAdmin.get_urls(self)
1048 1058
 
1049 1059
     The ``get_urls`` method on a ``ModelAdmin`` returns the URLs to be used for
13  docs/releases/1.4.txt
@@ -124,13 +124,20 @@ to work similarly to how desktop GUIs do it. The new hook
124 124
 :meth:`~django.contrib.admin.ModelAdmin.get_ordering` for specifying the
125 125
 ordering dynamically (e.g. depending on the request) has also been added.
126 126
 
127  
-``ModelAdmin.save_related()``
128  
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  127
+New ``ModelAdmin`` methods
  128
+~~~~~~~~~~~~~~~~~~~~~~~~~~
129 129
 
130  
-A new :meth:`~django.contrib.admin.ModelAdmin.save_related` hook was added to
  130
+A new :meth:`~django.contrib.admin.ModelAdmin.save_related` method was added to
131 131
 :mod:`~django.contrib.admin.ModelAdmin` to ease the customization of how
132 132
 related objects are saved in the admin.
133 133
 
  134
+Two other new methods,
  135
+:meth:`~django.contrib.admin.ModelAdmin.get_list_display` and
  136
+:meth:`~django.contrib.admin.ModelAdmin.get_list_display_links`
  137
+were added to :class:`~django.contrib.admin.ModelAdmin` to enable the dynamic
  138
+customization of fields and links to display on the admin
  139
+change list.
  140
+
134 141
 Admin inlines respect user permissions
135 142
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
136 143
 
13  tests/regressiontests/admin_changelist/admin.py
@@ -58,13 +58,20 @@ class ChordsBandAdmin(admin.ModelAdmin):
58 58
 
59 59
 
60 60
 class DynamicListDisplayChildAdmin(admin.ModelAdmin):
61  
-    list_display = ('name', 'parent')
  61
+    list_display = ('parent', 'name', 'age')
62 62
 
63 63
     def get_list_display(self, request):
64  
-        my_list_display = list(self.list_display)
  64
+        my_list_display = super(DynamicListDisplayChildAdmin, self).get_list_display(request)
65 65
         if request.user.username == 'noparents':
  66
+            my_list_display = list(my_list_display)
66 67
             my_list_display.remove('parent')
67  
-
68 68
         return my_list_display
69 69
 
  70
+class DynamicListDisplayLinksChildAdmin(admin.ModelAdmin):
  71
+    list_display = ('parent', 'name', 'age')
  72
+    list_display_links = ['parent', 'name']
  73
+
  74
+    def get_list_display_links(self, request, list_display):
  75
+        return ['age']
  76
+
70 77
 site.register(Child, DynamicListDisplayChildAdmin)
1  tests/regressiontests/admin_changelist/models.py
@@ -7,6 +7,7 @@ class Parent(models.Model):
7 7
 class Child(models.Model):
8 8
     parent = models.ForeignKey(Parent, editable=False, null=True)
9 9
     name = models.CharField(max_length=30, blank=True)
  10
+    age = models.IntegerField(null=True, blank=True)
10 11
 
11 12
 class Genre(models.Model):
12 13
     name = models.CharField(max_length=20)
55  tests/regressiontests/admin_changelist/tests.py
@@ -9,7 +9,8 @@
9 9
 from django.test.client import RequestFactory
10 10
 
11 11
 from .admin import (ChildAdmin, QuartetAdmin, BandAdmin, ChordsBandAdmin,
12  
-    GroupAdmin, ParentAdmin, DynamicListDisplayChildAdmin, CustomPaginationAdmin,
  12
+    GroupAdmin, ParentAdmin, DynamicListDisplayChildAdmin,
  13
+    DynamicListDisplayLinksChildAdmin, CustomPaginationAdmin,
13 14
     FilteredChildAdmin, CustomPaginator, site as custom_site)
14 15
 from .models import (Child, Parent, Genre, Band, Musician, Group, Quartet,
15 16
     Membership, ChordsMusician, ChordsBand, Invitation)
@@ -41,7 +42,9 @@ def test_result_list_empty_changelist_value(self):
41 42
         new_child = Child.objects.create(name='name', parent=None)
42 43
         request = self.factory.get('/child/')
43 44
         m = ChildAdmin(Child, admin.site)
44  
-        cl = ChangeList(request, Child, m.list_display, m.list_display_links,
  45
+        list_display = m.get_list_display(request)
  46
+        list_display_links = m.get_list_display_links(request, list_display)
  47
+        cl = ChangeList(request, Child, list_display, list_display_links,
45 48
                 m.list_filter, m.date_hierarchy, m.search_fields,
46 49
                 m.list_select_related, m.list_per_page, m.list_max_show_all, m.list_editable, m)
47 50
         cl.formset = None
@@ -61,7 +64,9 @@ def test_result_list_html(self):
61 64
         new_child = Child.objects.create(name='name', parent=new_parent)
62 65
         request = self.factory.get('/child/')
63 66
         m = ChildAdmin(Child, admin.site)
64  
-        cl = ChangeList(request, Child, m.list_display, m.list_display_links,
  67
+        list_display = m.get_list_display(request)
  68
+        list_display_links = m.get_list_display_links(request, list_display)
  69
+        cl = ChangeList(request, Child, list_display, list_display_links,
65 70
                 m.list_filter, m.date_hierarchy, m.search_fields,
66 71
                 m.list_select_related, m.list_per_page, m.list_max_show_all, m.list_editable, m)
67 72
         cl.formset = None
@@ -334,28 +339,31 @@ def _mocked_authenticated_request(user):
334 339
         m = custom_site._registry[Child]
335 340
         request = _mocked_authenticated_request(user_noparents)
336 341
         response = m.changelist_view(request)
337  
-        # XXX - Calling render here to avoid ContentNotRenderedError to be
338  
-        # raised. Ticket #15826 should fix this but it's not yet integrated.
339  
-        response.render()
340 342
         self.assertNotContains(response, 'Parent object')
341 343
 
  344
+        list_display = m.get_list_display(request)
  345
+        list_display_links = m.get_list_display_links(request, list_display)
  346
+        self.assertEqual(list_display, ['name', 'age'])
  347
+        self.assertEqual(list_display_links, ['name'])
  348
+
342 349
         # Test with user 'parents'
343 350
         m = DynamicListDisplayChildAdmin(Child, admin.site)
344 351
         request = _mocked_authenticated_request(user_parents)
345 352
         response = m.changelist_view(request)
346  
-        # XXX - #15826
347  
-        response.render()
348 353
         self.assertContains(response, 'Parent object')
349 354
 
350 355
         custom_site.unregister(Child)
351 356
 
  357
+        list_display = m.get_list_display(request)
  358
+        list_display_links = m.get_list_display_links(request, list_display)
  359
+        self.assertEqual(list_display, ('parent', 'name', 'age'))
  360
+        self.assertEqual(list_display_links, ['parent'])
  361
+
352 362
         # Test default implementation
353 363
         custom_site.register(Child, ChildAdmin)
354 364
         m = custom_site._registry[Child]
355 365
         request = _mocked_authenticated_request(user_noparents)
356 366
         response = m.changelist_view(request)
357  
-        # XXX - #15826
358  
-        response.render()
359 367
         self.assertContains(response, 'Parent object')
360 368
 
361 369
     def test_show_all(self):
@@ -386,3 +394,30 @@ def test_show_all(self):
386 394
         cl.get_results(request)
387 395
         self.assertEqual(len(cl.result_list), 10)
388 396
 
  397
+    def test_dynamic_list_display_links(self):
  398
+        """
  399
+        Regression tests for #16257: dynamic list_display_links support.
  400
+        """
  401
+        parent = Parent.objects.create(name='parent')
  402
+        for i in range(1, 10):
  403
+            Child.objects.create(id=i, name='child %s' % i, parent=parent, age=i)
  404
+
  405
+        superuser = User.objects.create(
  406
+            username='superuser',
  407
+            is_superuser=True)
  408
+
  409
+        def _mocked_authenticated_request(user):
  410
+            request = self.factory.get('/child/')
  411
+            request.user = user
  412
+            return request
  413
+
  414
+        m = DynamicListDisplayLinksChildAdmin(Child, admin.site)
  415
+        request = _mocked_authenticated_request(superuser)
  416
+        response = m.changelist_view(request)
  417
+        for i in range(1, 10):
  418
+            self.assertContains(response, '<a href="%s/">%s</a>' % (i, i))
  419
+
  420
+        list_display = m.get_list_display(request)
  421
+        list_display_links = m.get_list_display_links(request, list_display)
  422
+        self.assertEqual(list_display, ('parent', 'name', 'age'))
  423
+        self.assertEqual(list_display_links, ['age'])

0 notes on commit 9796f69

Please sign in to comment.
Something went wrong with that request. Please try again.