Skip to content

Commit

Permalink
Fixed #14206 - dynamic list_display support in admin
Browse files Browse the repository at this point in the history
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
  • Loading branch information
spookylukey committed Jun 8, 2011
1 parent 45e55b9 commit 207e3ed
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 24 deletions.
9 changes: 8 additions & 1 deletion django/contrib/admin/options.py
Expand Up @@ -625,6 +625,13 @@ def get_action(self, action):
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.
Expand Down Expand Up @@ -1053,7 +1060,7 @@ def changelist_view(self, request, extra_context=None):
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')
Expand Down
9 changes: 9 additions & 0 deletions docs/ref/contrib/admin/index.txt
Expand Up @@ -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
Expand Down
102 changes: 79 additions & 23 deletions tests/regressiontests/admin_changelist/tests.py
Expand Up @@ -4,19 +4,25 @@
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': {}}})
Expand All @@ -27,7 +33,7 @@ def test_result_list_empty_changelist_value(self):
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,
Expand All @@ -40,15 +46,14 @@ def test_result_list_empty_changelist_value(self):
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
default ModelAdmin settings.
"""
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,
Expand All @@ -72,7 +77,7 @@ def test_result_list_editable_html(self):
"""
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
Expand Down Expand Up @@ -104,8 +109,7 @@ def test_result_list_editable(self):
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
Expand All @@ -122,7 +126,7 @@ def test_custom_paginator(self):
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']
Expand All @@ -148,7 +152,7 @@ def test_distinct_for_m2m_in_list_filter(self):
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,
Expand All @@ -171,7 +175,7 @@ def test_distinct_for_through_m2m_in_list_filter(self):
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,
Expand All @@ -195,7 +199,7 @@ def test_distinct_for_inherited_m2m_in_list_filter(self):
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,
Expand All @@ -219,7 +223,7 @@ def test_distinct_for_m2m_to_inherited_in_list_filter(self):
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,
Expand All @@ -242,7 +246,7 @@ def test_distinct_for_non_unique_related_object_in_list_filter(self):
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,
Expand All @@ -262,7 +266,7 @@ def test_distinct_for_non_unique_related_object_in_search_fields(self):
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,
Expand All @@ -282,7 +286,7 @@ def test_pagination(self):
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)
Expand All @@ -302,6 +306,51 @@ def test_pagination(self):
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']
Expand All @@ -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):
Expand All @@ -333,19 +383,25 @@ def __init__(self, queryset, page_size, orphans=0, allow_empty_first_page=True):
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

0 comments on commit 207e3ed

Please sign in to comment.