Fixed #14206 - dynamic list_display support in admin
Thanks to gabejackson for the suggestion, and to cyrus for the patch. git-svn-id: http://code.djangoproject.com/svn/django/trunk@16340 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
45e55b9143
commit
207e3ed9d5
|
@ -625,6 +625,13 @@ class ModelAdmin(BaseModelAdmin):
|
|||
description = capfirst(action.replace('_', ' '))
|
||||
return func, action, description
|
||||
|
||||
def get_list_display(self, request):
|
||||
"""
|
||||
Return a sequence containing the fields to be displayed on the
|
||||
changelist.
|
||||
"""
|
||||
return self.list_display
|
||||
|
||||
def construct_change_message(self, request, form, formsets):
|
||||
"""
|
||||
Construct a change message from a changed object.
|
||||
|
@ -1053,7 +1060,7 @@ class ModelAdmin(BaseModelAdmin):
|
|||
actions = self.get_actions(request)
|
||||
|
||||
# Remove action checkboxes if there aren't any actions available.
|
||||
list_display = list(self.list_display)
|
||||
list_display = list(self.get_list_display(request))
|
||||
if not actions:
|
||||
try:
|
||||
list_display.remove('action_checkbox')
|
||||
|
|
|
@ -967,6 +967,15 @@ templates used by the :class:`ModelAdmin` views:
|
|||
a ``dictionary``, as described above in the :attr:`ModelAdmin.prepopulated_fields`
|
||||
section.
|
||||
|
||||
.. method:: ModelAdmin.get_list_display(self, request)
|
||||
|
||||
.. versionadded:: 1.4
|
||||
|
||||
The ``get_list_display`` method is given the ``HttpRequest`` 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` section.
|
||||
|
||||
.. method:: ModelAdmin.get_urls(self)
|
||||
|
||||
The ``get_urls`` method on a ``ModelAdmin`` returns the URLs to be used for
|
||||
|
|
|
@ -4,19 +4,25 @@ from django.contrib.admin.views.main import ChangeList, SEARCH_VAR
|
|||
from django.core.paginator import Paginator
|
||||
from django.template import Context, Template
|
||||
from django.test import TransactionTestCase
|
||||
from django.test.client import RequestFactory
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from models import (Child, Parent, Genre, Band, Musician, Group, Quartet,
|
||||
Membership, ChordsMusician, ChordsBand, Invitation)
|
||||
|
||||
|
||||
class ChangeListTests(TransactionTestCase):
|
||||
def setUp(self):
|
||||
self.factory = RequestFactory()
|
||||
|
||||
def test_select_related_preserved(self):
|
||||
"""
|
||||
Regression test for #10348: ChangeList.get_query_set() shouldn't
|
||||
overwrite a custom select_related provided by ModelAdmin.queryset().
|
||||
"""
|
||||
m = ChildAdmin(Child, admin.site)
|
||||
cl = ChangeList(MockRequest(), Child, m.list_display, m.list_display_links,
|
||||
request = self.factory.get('/child/')
|
||||
cl = ChangeList(request, Child, 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': {'name': {}}})
|
||||
|
@ -27,7 +33,7 @@ class ChangeListTests(TransactionTestCase):
|
|||
for relationship fields
|
||||
"""
|
||||
new_child = Child.objects.create(name='name', parent=None)
|
||||
request = MockRequest()
|
||||
request = self.factory.get('/child/')
|
||||
m = ChildAdmin(Child, admin.site)
|
||||
cl = ChangeList(request, Child, m.list_display, m.list_display_links,
|
||||
m.list_filter, m.date_hierarchy, m.search_fields,
|
||||
|
@ -40,7 +46,6 @@ class ChangeListTests(TransactionTestCase):
|
|||
self.assertFalse(table_output.find(row_html) == -1,
|
||||
'Failed to find expected row element: %s' % table_output)
|
||||
|
||||
|
||||
def test_result_list_html(self):
|
||||
"""
|
||||
Verifies that inclusion tag result_list generates a table when with
|
||||
|
@ -48,7 +53,7 @@ class ChangeListTests(TransactionTestCase):
|
|||
"""
|
||||
new_parent = Parent.objects.create(name='parent')
|
||||
new_child = Child.objects.create(name='name', parent=new_parent)
|
||||
request = MockRequest()
|
||||
request = self.factory.get('/child/')
|
||||
m = ChildAdmin(Child, admin.site)
|
||||
cl = ChangeList(request, Child, m.list_display, m.list_display_links,
|
||||
m.list_filter, m.date_hierarchy, m.search_fields,
|
||||
|
@ -72,7 +77,7 @@ class ChangeListTests(TransactionTestCase):
|
|||
"""
|
||||
new_parent = Parent.objects.create(name='parent')
|
||||
new_child = Child.objects.create(name='name', parent=new_parent)
|
||||
request = MockRequest()
|
||||
request = self.factory.get('/child/')
|
||||
m = ChildAdmin(Child, admin.site)
|
||||
|
||||
# Test with list_editable fields
|
||||
|
@ -104,8 +109,7 @@ class ChangeListTests(TransactionTestCase):
|
|||
new_parent = Parent.objects.create(name='parent')
|
||||
for i in range(200):
|
||||
new_child = Child.objects.create(name='name %s' % i, parent=new_parent)
|
||||
request = MockRequest()
|
||||
request.GET['p'] = -1 # Anything outside range
|
||||
request = self.factory.get('/child/', data={'p': -1}) # Anything outside range
|
||||
m = ChildAdmin(Child, admin.site)
|
||||
|
||||
# Test with list_editable fields
|
||||
|
@ -122,7 +126,7 @@ class ChangeListTests(TransactionTestCase):
|
|||
for i in range(200):
|
||||
new_child = Child.objects.create(name='name %s' % i, parent=new_parent)
|
||||
|
||||
request = MockRequest()
|
||||
request = self.factory.get('/child/')
|
||||
m = ChildAdmin(Child, admin.site)
|
||||
m.list_display = ['id', 'name', 'parent']
|
||||
m.list_display_links = ['id']
|
||||
|
@ -148,7 +152,7 @@ class ChangeListTests(TransactionTestCase):
|
|||
band.genres.add(blues)
|
||||
|
||||
m = BandAdmin(Band, admin.site)
|
||||
request = MockFilterRequest('genres', blues.pk)
|
||||
request = self.factory.get('/band/', data={'genres': blues.pk})
|
||||
|
||||
cl = ChangeList(request, Band, m.list_display,
|
||||
m.list_display_links, m.list_filter, m.date_hierarchy,
|
||||
|
@ -171,7 +175,7 @@ class ChangeListTests(TransactionTestCase):
|
|||
Membership.objects.create(group=band, music=lead, role='bass player')
|
||||
|
||||
m = GroupAdmin(Group, admin.site)
|
||||
request = MockFilterRequest('members', lead.pk)
|
||||
request = self.factory.get('/group/', data={'members': lead.pk})
|
||||
|
||||
cl = ChangeList(request, Group, m.list_display,
|
||||
m.list_display_links, m.list_filter, m.date_hierarchy,
|
||||
|
@ -195,7 +199,7 @@ class ChangeListTests(TransactionTestCase):
|
|||
Membership.objects.create(group=four, music=lead, role='guitar player')
|
||||
|
||||
m = QuartetAdmin(Quartet, admin.site)
|
||||
request = MockFilterRequest('members', lead.pk)
|
||||
request = self.factory.get('/quartet/', data={'members': lead.pk})
|
||||
|
||||
cl = ChangeList(request, Quartet, m.list_display,
|
||||
m.list_display_links, m.list_filter, m.date_hierarchy,
|
||||
|
@ -219,7 +223,7 @@ class ChangeListTests(TransactionTestCase):
|
|||
Invitation.objects.create(band=three, player=lead, instrument='bass')
|
||||
|
||||
m = ChordsBandAdmin(ChordsBand, admin.site)
|
||||
request = MockFilterRequest('members', lead.pk)
|
||||
request = self.factory.get('/chordsband/', data={'members': lead.pk})
|
||||
|
||||
cl = ChangeList(request, ChordsBand, m.list_display,
|
||||
m.list_display_links, m.list_filter, m.date_hierarchy,
|
||||
|
@ -242,7 +246,7 @@ class ChangeListTests(TransactionTestCase):
|
|||
Child.objects.create(parent=parent, name='Daniel')
|
||||
|
||||
m = ParentAdmin(Parent, admin.site)
|
||||
request = MockFilterRequest('child__name', 'Daniel')
|
||||
request = self.factory.get('/parent/', data={'child__name': 'Daniel'})
|
||||
|
||||
cl = ChangeList(request, Parent, m.list_display, m.list_display_links,
|
||||
m.list_filter, m.date_hierarchy, m.search_fields,
|
||||
|
@ -262,7 +266,7 @@ class ChangeListTests(TransactionTestCase):
|
|||
Child.objects.create(parent=parent, name='Daniel')
|
||||
|
||||
m = ParentAdmin(Parent, admin.site)
|
||||
request = MockSearchRequest('daniel')
|
||||
request = self.factory.get('/parent/', data={SEARCH_VAR: 'daniel'})
|
||||
|
||||
cl = ChangeList(request, Parent, m.list_display, m.list_display_links,
|
||||
m.list_filter, m.date_hierarchy, m.search_fields,
|
||||
|
@ -282,7 +286,7 @@ class ChangeListTests(TransactionTestCase):
|
|||
Child.objects.create(name='name %s' % i, parent=parent)
|
||||
Child.objects.create(name='filtered %s' % i, parent=parent)
|
||||
|
||||
request = MockRequest()
|
||||
request = self.factory.get('/child/')
|
||||
|
||||
# Test default queryset
|
||||
m = ChildAdmin(Child, admin.site)
|
||||
|
@ -302,6 +306,51 @@ class ChangeListTests(TransactionTestCase):
|
|||
self.assertEqual(cl.paginator.count, 30)
|
||||
self.assertEqual(cl.paginator.page_range, [1, 2, 3])
|
||||
|
||||
def test_dynamic_list_display(self):
|
||||
"""
|
||||
Regression tests for #14206: dynamic list_display support.
|
||||
"""
|
||||
parent = Parent.objects.create(name='parent')
|
||||
for i in range(10):
|
||||
Child.objects.create(name='child %s' % i, parent=parent)
|
||||
|
||||
user_noparents = User.objects.create(
|
||||
username='noparents',
|
||||
is_superuser=True)
|
||||
user_parents = User.objects.create(
|
||||
username='parents',
|
||||
is_superuser=True)
|
||||
|
||||
def _mocked_authenticated_request(user):
|
||||
request = self.factory.get('/child/')
|
||||
request.user = user
|
||||
return request
|
||||
|
||||
# Test with user 'noparents'
|
||||
m = DynamicListDisplayChildAdmin(Child, admin.site)
|
||||
request = _mocked_authenticated_request(user_noparents)
|
||||
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')
|
||||
|
||||
# Test with user 'parents'
|
||||
m = DynamicListDisplayChildAdmin(Child, admin.site)
|
||||
request = _mocked_authenticated_request(user_parents)
|
||||
response = m.changelist_view(request)
|
||||
# XXX - #15826
|
||||
response.render()
|
||||
self.assertContains(response, 'Parent object')
|
||||
|
||||
# Test default implementation
|
||||
m = ChildAdmin(Child, admin.site)
|
||||
request = _mocked_authenticated_request(user_noparents)
|
||||
response = m.changelist_view(request)
|
||||
# XXX - #15826
|
||||
response.render()
|
||||
self.assertContains(response, 'Parent object')
|
||||
|
||||
|
||||
class ParentAdmin(admin.ModelAdmin):
|
||||
list_filter = ['child__name']
|
||||
|
@ -311,18 +360,19 @@ class ParentAdmin(admin.ModelAdmin):
|
|||
class ChildAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'parent']
|
||||
list_per_page = 10
|
||||
|
||||
def queryset(self, request):
|
||||
return super(ChildAdmin, self).queryset(request).select_related("parent__name")
|
||||
|
||||
|
||||
class FilteredChildAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'parent']
|
||||
list_per_page = 10
|
||||
|
||||
def queryset(self, request):
|
||||
return super(FilteredChildAdmin, self).queryset(request).filter(
|
||||
name__contains='filtered')
|
||||
|
||||
class MockRequest(object):
|
||||
GET = {}
|
||||
|
||||
class CustomPaginator(Paginator):
|
||||
def __init__(self, queryset, page_size, orphans=0, allow_empty_first_page=True):
|
||||
|
@ -333,19 +383,25 @@ class CustomPaginator(Paginator):
|
|||
class BandAdmin(admin.ModelAdmin):
|
||||
list_filter = ['genres']
|
||||
|
||||
|
||||
class GroupAdmin(admin.ModelAdmin):
|
||||
list_filter = ['members']
|
||||
|
||||
|
||||
class QuartetAdmin(admin.ModelAdmin):
|
||||
list_filter = ['members']
|
||||
|
||||
|
||||
class ChordsBandAdmin(admin.ModelAdmin):
|
||||
list_filter = ['members']
|
||||
|
||||
class MockFilterRequest(object):
|
||||
def __init__(self, filter, q):
|
||||
self.GET = {filter: q}
|
||||
|
||||
class MockSearchRequest(object):
|
||||
def __init__(self, q):
|
||||
self.GET = {SEARCH_VAR: q}
|
||||
class DynamicListDisplayChildAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'parent')
|
||||
|
||||
def get_list_display(self, request):
|
||||
my_list_display = list(self.list_display)
|
||||
if request.user.username == 'noparents':
|
||||
my_list_display.remove('parent')
|
||||
|
||||
return my_list_display
|
||||
|
|
Loading…
Reference in New Issue