diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
index a1fd3b5657..c8ebccfd25 100644
--- a/django/contrib/admin/templatetags/admin_list.py
+++ b/django/contrib/admin/templatetags/admin_list.py
@@ -104,6 +104,7 @@ def result_headers(cl):
return_attr=True
)
if attr:
+ field_name = _coerce_field_name(field_name, i)
# Potentially not sortable
# if the field is the action checkbox: no sorting and special class
@@ -183,6 +184,18 @@ def _boolean_icon(field_val):
return format_html('', icon_url, field_val)
+def _coerce_field_name(field_name, field_index):
+ """
+ Coerce a field_name (which may be a callable) to a string.
+ """
+ if callable(field_name):
+ if field_name.__name__ == '':
+ return 'lambda' + str(field_index)
+ else:
+ return field_name.__name__
+ return field_name
+
+
def items_for_result(cl, result, form):
"""
Generates the actual list of data.
@@ -197,9 +210,9 @@ def items_for_result(cl, result, form):
first = True
pk = cl.lookup_opts.pk.attname
- for field_name in cl.list_display:
+ for field_index, field_name in enumerate(cl.list_display):
empty_value_display = cl.model_admin.get_empty_value_display()
- row_classes = ['field-%s' % field_name]
+ row_classes = ['field-%s' % _coerce_field_name(field_name, field_index)]
try:
f, attr, value = lookup_field(field_name, result, cl.model_admin)
except ObjectDoesNotExist:
diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py
index 932a29f3ac..bc2d08fc71 100644
--- a/tests/admin_views/admin.py
+++ b/tests/admin_views/admin.py
@@ -90,7 +90,7 @@ class ChapterXtra1Admin(admin.ModelAdmin):
class ArticleAdmin(admin.ModelAdmin):
list_display = (
'content', 'date', callable_year, 'model_year', 'modeladmin_year',
- 'model_year_reversed', 'section',
+ 'model_year_reversed', 'section', lambda obj: obj.title,
)
list_editable = ('section',)
list_filter = ('date', 'section')
diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py
index 22a5d87231..55ff3c5c08 100644
--- a/tests/admin_views/tests.py
+++ b/tests/admin_views/tests.py
@@ -344,6 +344,15 @@ class AdminViewBasicTest(AdminViewBasicTestCase):
# started with 3 articles, one was deleted.
self.assertEqual(Section.objects.latest('id').article_set.count(), 2)
+ def test_change_list_column_field_classes(self):
+ response = self.client.get(reverse('admin:admin_views_article_changelist'))
+ # callables display the callable name.
+ self.assertContains(response, 'column-callable_year')
+ self.assertContains(response, 'field-callable_year')
+ # lambdas display as "lambda" + index that they appear in list_display.
+ self.assertContains(response, 'column-lambda8')
+ self.assertContains(response, 'field-lambda8')
+
def test_change_list_sorting_callable(self):
"""
Ensure we can sort on a list_display field that is a callable