mirror of https://github.com/django/django.git
Fixed #16257 -- Added new `ModelAdmin.get_list_display_links()` method 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
This commit is contained in:
parent
a05c70fae1
commit
9796f69533
|
@ -650,6 +650,18 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
"""
|
"""
|
||||||
return self.list_display
|
return self.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. The list_display parameter is the list of fields
|
||||||
|
returned by get_list_display().
|
||||||
|
"""
|
||||||
|
if self.list_display_links or not list_display:
|
||||||
|
return self.list_display_links
|
||||||
|
else:
|
||||||
|
# Use only the first item in list_display as link
|
||||||
|
return list(list_display)[:1]
|
||||||
|
|
||||||
def construct_change_message(self, request, form, formsets):
|
def construct_change_message(self, request, form, formsets):
|
||||||
"""
|
"""
|
||||||
Construct a change message from a changed object.
|
Construct a change message from a changed object.
|
||||||
|
@ -1087,22 +1099,20 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
|
|
||||||
@csrf_protect_m
|
@csrf_protect_m
|
||||||
def changelist_view(self, request, extra_context=None):
|
def changelist_view(self, request, extra_context=None):
|
||||||
"The 'change list' admin view for this model."
|
"""
|
||||||
|
The 'change list' admin view for this model.
|
||||||
|
"""
|
||||||
from django.contrib.admin.views.main import ERROR_FLAG
|
from django.contrib.admin.views.main import ERROR_FLAG
|
||||||
opts = self.model._meta
|
opts = self.model._meta
|
||||||
app_label = opts.app_label
|
app_label = opts.app_label
|
||||||
if not self.has_change_permission(request, None):
|
if not self.has_change_permission(request, None):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
|
list_display = self.get_list_display(request)
|
||||||
|
list_display_links = self.get_list_display_links(request, list_display)
|
||||||
|
|
||||||
# Check actions to see if any are available on this changelist
|
# Check actions to see if any are available on this changelist
|
||||||
actions = self.get_actions(request)
|
actions = self.get_actions(request)
|
||||||
|
|
||||||
list_display = self.get_list_display(request)
|
|
||||||
|
|
||||||
list_display_links = self.list_display_links
|
|
||||||
if not self.list_display_links and list_display:
|
|
||||||
list_display_links = list(list_display)[:1]
|
|
||||||
|
|
||||||
if actions:
|
if actions:
|
||||||
# Add the action checkboxes if there are any actions available.
|
# Add the action checkboxes if there are any actions available.
|
||||||
list_display = ['action_checkbox'] + list(list_display)
|
list_display = ['action_checkbox'] + list(list_display)
|
||||||
|
|
|
@ -1044,6 +1044,16 @@ templates used by the :class:`ModelAdmin` views:
|
||||||
displayed on the changelist view as described above in the
|
displayed on the changelist view as described above in the
|
||||||
:attr:`ModelAdmin.list_display` section.
|
: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 by :meth:`ModelAdmin.get_list_display`.
|
||||||
|
It is expected to return a ``list`` or ``tuple`` of field names on the
|
||||||
|
changelist that will be linked to the change view, as described in the
|
||||||
|
:attr:`ModelAdmin.list_display_links` section.
|
||||||
|
|
||||||
.. method:: ModelAdmin.get_urls(self)
|
.. method:: ModelAdmin.get_urls(self)
|
||||||
|
|
||||||
The ``get_urls`` method on a ``ModelAdmin`` returns the URLs to be used for
|
The ``get_urls`` method on a ``ModelAdmin`` returns the URLs to be used for
|
||||||
|
|
|
@ -124,13 +124,20 @@ to work similarly to how desktop GUIs do it. The new hook
|
||||||
:meth:`~django.contrib.admin.ModelAdmin.get_ordering` for specifying the
|
:meth:`~django.contrib.admin.ModelAdmin.get_ordering` for specifying the
|
||||||
ordering dynamically (e.g. depending on the request) has also been added.
|
ordering dynamically (e.g. depending on the request) has also been added.
|
||||||
|
|
||||||
``ModelAdmin.save_related()``
|
New ``ModelAdmin`` methods
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
A new :meth:`~django.contrib.admin.ModelAdmin.save_related` hook was added to
|
A new :meth:`~django.contrib.admin.ModelAdmin.save_related` method was added to
|
||||||
:mod:`~django.contrib.admin.ModelAdmin` to ease the customization of how
|
:mod:`~django.contrib.admin.ModelAdmin` to ease the customization of how
|
||||||
related objects are saved in the admin.
|
related objects are saved in the admin.
|
||||||
|
|
||||||
|
Two other new methods,
|
||||||
|
:meth:`~django.contrib.admin.ModelAdmin.get_list_display` and
|
||||||
|
:meth:`~django.contrib.admin.ModelAdmin.get_list_display_links`
|
||||||
|
were added to :class:`~django.contrib.admin.ModelAdmin` to enable the dynamic
|
||||||
|
customization of fields and links to display on the admin
|
||||||
|
change list.
|
||||||
|
|
||||||
Admin inlines respect user permissions
|
Admin inlines respect user permissions
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -58,13 +58,20 @@ class ChordsBandAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
|
|
||||||
class DynamicListDisplayChildAdmin(admin.ModelAdmin):
|
class DynamicListDisplayChildAdmin(admin.ModelAdmin):
|
||||||
list_display = ('name', 'parent')
|
list_display = ('parent', 'name', 'age')
|
||||||
|
|
||||||
def get_list_display(self, request):
|
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':
|
if request.user.username == 'noparents':
|
||||||
|
my_list_display = list(my_list_display)
|
||||||
my_list_display.remove('parent')
|
my_list_display.remove('parent')
|
||||||
|
|
||||||
return my_list_display
|
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']
|
||||||
|
|
||||||
site.register(Child, DynamicListDisplayChildAdmin)
|
site.register(Child, DynamicListDisplayChildAdmin)
|
||||||
|
|
|
@ -7,6 +7,7 @@ class Parent(models.Model):
|
||||||
class Child(models.Model):
|
class Child(models.Model):
|
||||||
parent = models.ForeignKey(Parent, editable=False, null=True)
|
parent = models.ForeignKey(Parent, editable=False, null=True)
|
||||||
name = models.CharField(max_length=30, blank=True)
|
name = models.CharField(max_length=30, blank=True)
|
||||||
|
age = models.IntegerField(null=True, blank=True)
|
||||||
|
|
||||||
class Genre(models.Model):
|
class Genre(models.Model):
|
||||||
name = models.CharField(max_length=20)
|
name = models.CharField(max_length=20)
|
||||||
|
|
|
@ -9,7 +9,8 @@ from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
|
|
||||||
from .admin import (ChildAdmin, QuartetAdmin, BandAdmin, ChordsBandAdmin,
|
from .admin import (ChildAdmin, QuartetAdmin, BandAdmin, ChordsBandAdmin,
|
||||||
GroupAdmin, ParentAdmin, DynamicListDisplayChildAdmin, CustomPaginationAdmin,
|
GroupAdmin, ParentAdmin, DynamicListDisplayChildAdmin,
|
||||||
|
DynamicListDisplayLinksChildAdmin, CustomPaginationAdmin,
|
||||||
FilteredChildAdmin, CustomPaginator, site as custom_site)
|
FilteredChildAdmin, CustomPaginator, site as custom_site)
|
||||||
from .models import (Child, Parent, Genre, Band, Musician, Group, Quartet,
|
from .models import (Child, Parent, Genre, Band, Musician, Group, Quartet,
|
||||||
Membership, ChordsMusician, ChordsBand, Invitation)
|
Membership, ChordsMusician, ChordsBand, Invitation)
|
||||||
|
@ -41,7 +42,9 @@ class ChangeListTests(TestCase):
|
||||||
new_child = Child.objects.create(name='name', parent=None)
|
new_child = Child.objects.create(name='name', parent=None)
|
||||||
request = self.factory.get('/child/')
|
request = self.factory.get('/child/')
|
||||||
m = ChildAdmin(Child, admin.site)
|
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_filter, m.date_hierarchy, m.search_fields,
|
||||||
m.list_select_related, m.list_per_page, m.list_max_show_all, m.list_editable, m)
|
m.list_select_related, m.list_per_page, m.list_max_show_all, m.list_editable, m)
|
||||||
cl.formset = None
|
cl.formset = None
|
||||||
|
@ -61,7 +64,9 @@ class ChangeListTests(TestCase):
|
||||||
new_child = Child.objects.create(name='name', parent=new_parent)
|
new_child = Child.objects.create(name='name', parent=new_parent)
|
||||||
request = self.factory.get('/child/')
|
request = self.factory.get('/child/')
|
||||||
m = ChildAdmin(Child, admin.site)
|
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_filter, m.date_hierarchy, m.search_fields,
|
||||||
m.list_select_related, m.list_per_page, m.list_max_show_all, m.list_editable, m)
|
m.list_select_related, m.list_per_page, m.list_max_show_all, m.list_editable, m)
|
||||||
cl.formset = None
|
cl.formset = None
|
||||||
|
@ -334,28 +339,31 @@ class ChangeListTests(TestCase):
|
||||||
m = custom_site._registry[Child]
|
m = custom_site._registry[Child]
|
||||||
request = _mocked_authenticated_request(user_noparents)
|
request = _mocked_authenticated_request(user_noparents)
|
||||||
response = m.changelist_view(request)
|
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()
|
|
||||||
self.assertNotContains(response, 'Parent object')
|
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, ['name', 'age'])
|
||||||
|
self.assertEqual(list_display_links, ['name'])
|
||||||
|
|
||||||
# Test with user 'parents'
|
# Test with user 'parents'
|
||||||
m = DynamicListDisplayChildAdmin(Child, admin.site)
|
m = DynamicListDisplayChildAdmin(Child, admin.site)
|
||||||
request = _mocked_authenticated_request(user_parents)
|
request = _mocked_authenticated_request(user_parents)
|
||||||
response = m.changelist_view(request)
|
response = m.changelist_view(request)
|
||||||
# XXX - #15826
|
|
||||||
response.render()
|
|
||||||
self.assertContains(response, 'Parent object')
|
self.assertContains(response, 'Parent object')
|
||||||
|
|
||||||
custom_site.unregister(Child)
|
custom_site.unregister(Child)
|
||||||
|
|
||||||
|
list_display = m.get_list_display(request)
|
||||||
|
list_display_links = m.get_list_display_links(request, list_display)
|
||||||
|
self.assertEqual(list_display, ('parent', 'name', 'age'))
|
||||||
|
self.assertEqual(list_display_links, ['parent'])
|
||||||
|
|
||||||
# Test default implementation
|
# Test default implementation
|
||||||
custom_site.register(Child, ChildAdmin)
|
custom_site.register(Child, ChildAdmin)
|
||||||
m = custom_site._registry[Child]
|
m = custom_site._registry[Child]
|
||||||
request = _mocked_authenticated_request(user_noparents)
|
request = _mocked_authenticated_request(user_noparents)
|
||||||
response = m.changelist_view(request)
|
response = m.changelist_view(request)
|
||||||
# XXX - #15826
|
|
||||||
response.render()
|
|
||||||
self.assertContains(response, 'Parent object')
|
self.assertContains(response, 'Parent object')
|
||||||
|
|
||||||
def test_show_all(self):
|
def test_show_all(self):
|
||||||
|
@ -386,3 +394,30 @@ class ChangeListTests(TestCase):
|
||||||
cl.get_results(request)
|
cl.get_results(request)
|
||||||
self.assertEqual(len(cl.result_list), 10)
|
self.assertEqual(len(cl.result_list), 10)
|
||||||
|
|
||||||
|
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)
|
||||||
|
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, ('parent', 'name', 'age'))
|
||||||
|
self.assertEqual(list_display_links, ['age'])
|
Loading…
Reference in New Issue