Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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
  • Loading branch information...
commit 207e3ed9d58fabe68477aa62f7ef41464f1761e9 1 parent 45e55b9
@spookylukey spookylukey authored
View
9 django/contrib/admin/options.py
@@ -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.
@@ -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')
View
9 docs/ref/contrib/admin/index.txt
@@ -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
View
102 tests/regressiontests/admin_changelist/tests.py
@@ -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': {}}})
@@ -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,
@@ -40,7 +46,6 @@ 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
@@ -48,7 +53,7 @@ def test_result_list_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)
cl = ChangeList(request, Child, m.list_display, m.list_display_links,
m.list_filter, m.date_hierarchy, m.search_fields,
@@ -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
@@ -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
@@ -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']
@@ -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,
@@ -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,
@@ -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,
@@ -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,
@@ -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,
@@ -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,
@@ -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)
@@ -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']
@@ -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 @@ 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
Please sign in to comment.
Something went wrong with that request. Please try again.