diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index bcfa3a8155..c4970af53c 100644
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -157,7 +157,12 @@ class ChangeList(object):
# See whether field_name is a name of a non-field
# that allows sorting.
try:
- attr = getattr(self.model, field_name)
+ if callable(field_name):
+ attr = field_name
+ elif hasattr(self.model_admin, field_name):
+ attr = getattr(self.model_admin, field_name)
+ else:
+ attr = getattr(self.model, field_name)
order_field = attr.admin_order_field
except AttributeError:
pass
diff --git a/tests/regressiontests/admin_views/fixtures/admin-views-users.xml b/tests/regressiontests/admin_views/fixtures/admin-views-users.xml
index 8d6c62b58f..f1ff2961a1 100644
--- a/tests/regressiontests/admin_views/fixtures/admin-views-users.xml
+++ b/tests/regressiontests/admin_views/fixtures/admin-views-users.xml
@@ -74,8 +74,20 @@
Test section
+
+
+
+
\ No newline at end of file
diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py
index 98f99a6d00..52e75699ea 100644
--- a/tests/regressiontests/admin_views/models.py
+++ b/tests/regressiontests/admin_views/models.py
@@ -19,12 +19,20 @@ class Article(models.Model):
def __unicode__(self):
return self.title
+
+ def model_year(self):
+ return self.date.year
+ model_year.admin_order_field = 'date'
+
+def callable_year(dt_value):
+ return dt_value.year
+callable_year.admin_order_field = 'date'
class ArticleInline(admin.TabularInline):
model = Article
class ArticleAdmin(admin.ModelAdmin):
- list_display = ('content', 'date')
+ list_display = ('content', 'date', callable_year, 'model_year', 'modeladmin_year')
list_filter = ('date',)
def changelist_view(self, request):
@@ -34,6 +42,10 @@ class ArticleAdmin(admin.ModelAdmin):
'extra_var': 'Hello!'
}
)
+
+ def modeladmin_year(self, obj):
+ return obj.date.year
+ modeladmin_year.admin_order_field = 'date'
class CustomArticle(models.Model):
content = models.TextField()
diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
index 5a10971570..f331c12591 100644
--- a/tests/regressiontests/admin_views/tests.py
+++ b/tests/regressiontests/admin_views/tests.py
@@ -71,34 +71,83 @@ class AdminViewBasicTest(TestCase):
post_data = {
"name": u"Test section",
# inline data
- "article_set-TOTAL_FORMS": u"4",
- "article_set-INITIAL_FORMS": u"1",
+ "article_set-TOTAL_FORMS": u"6",
+ "article_set-INITIAL_FORMS": u"3",
"article_set-0-id": u"1",
# there is no title in database, give one here or formset
# will fail.
"article_set-0-title": u"Need a title.",
- "article_set-0-content": u"<p>test content</p>",
+ "article_set-0-content": u"<p>Middle content</p>",
"article_set-0-date_0": u"2008-03-18",
"article_set-0-date_1": u"11:54:58",
- "article_set-1-id": u"",
- "article_set-1-title": u"",
- "article_set-1-content": u"",
- "article_set-1-date_0": u"",
- "article_set-1-date_1": u"",
- "article_set-2-id": u"",
- "article_set-2-title": u"",
- "article_set-2-content": u"",
- "article_set-2-date_0": u"",
- "article_set-2-date_1": u"",
+ "article_set-1-id": u"2",
+ "article_set-1-title": u"Need a title.",
+ "article_set-1-content": u"<p>Oldest content</p>",
+ "article_set-1-date_0": u"2000-03-18",
+ "article_set-1-date_1": u"11:54:58",
+ "article_set-2-id": u"3",
+ "article_set-2-title": u"Need a title.",
+ "article_set-2-content": u"<p>Newest content</p>",
+ "article_set-2-date_0": u"2009-03-18",
+ "article_set-2-date_1": u"11:54:58",
"article_set-3-id": u"",
"article_set-3-title": u"",
"article_set-3-content": u"",
"article_set-3-date_0": u"",
"article_set-3-date_1": u"",
+ "article_set-4-id": u"",
+ "article_set-4-title": u"",
+ "article_set-4-content": u"",
+ "article_set-4-date_0": u"",
+ "article_set-4-date_1": u"",
+ "article_set-5-id": u"",
+ "article_set-5-title": u"",
+ "article_set-5-content": u"",
+ "article_set-5-date_0": u"",
+ "article_set-5-date_1": u"",
}
response = self.client.post('/test_admin/admin/admin_views/section/1/', post_data)
self.failUnlessEqual(response.status_code, 302) # redirect somewhere
+ def testChangeListSortingCallable(self):
+ """
+ Ensure we can sort on a list_display field that is a callable
+ (column 2 is callable_year in ArticleAdmin)
+ """
+ response = self.client.get('/test_admin/admin/admin_views/article/', {'ot': 'asc', 'o': 2})
+ self.failUnlessEqual(response.status_code, 200)
+ self.failUnless(
+ response.content.index('Oldest content') < response.content.index('Middle content') and
+ response.content.index('Middle content') < response.content.index('Newest content'),
+ "Results of sorting on callable are out of order."
+ )
+
+ def testChangeListSortingModel(self):
+ """
+ Ensure we can sort on a list_display field that is a Model method
+ (colunn 3 is 'model_year' in ArticleAdmin)
+ """
+ response = self.client.get('/test_admin/admin/admin_views/article/', {'ot': 'dsc', 'o': 3})
+ self.failUnlessEqual(response.status_code, 200)
+ self.failUnless(
+ response.content.index('Newest content') < response.content.index('Middle content') and
+ response.content.index('Middle content') < response.content.index('Oldest content'),
+ "Results of sorting on Model method are out of order."
+ )
+
+ def testChangeListSortingModelAdmin(self):
+ """
+ Ensure we can sort on a list_display field that is a ModelAdmin method
+ (colunn 4 is 'modeladmin_year' in ArticleAdmin)
+ """
+ response = self.client.get('/test_admin/admin/admin_views/article/', {'ot': 'asc', 'o': 4})
+ self.failUnlessEqual(response.status_code, 200)
+ self.failUnless(
+ response.content.index('Oldest content') < response.content.index('Middle content') and
+ response.content.index('Middle content') < response.content.index('Newest content'),
+ "Results of sorting on ModelAdmin method are out of order."
+ )
+
def get_perm(Model, perm):
"""Return the permission object, for the Model"""
ct = ContentType.objects.get_for_model(Model)
@@ -252,7 +301,7 @@ class AdminViewPermissionsTest(TestCase):
# Try POST just to make sure
post = self.client.post('/test_admin/admin/admin_views/article/add/', add_dict)
self.failUnlessEqual(post.status_code, 403)
- self.failUnlessEqual(Article.objects.all().count(), 1)
+ self.failUnlessEqual(Article.objects.all().count(), 3)
self.client.get('/test_admin/admin/logout/')
# Add user may login and POST to add view, then redirect to admin root
@@ -260,7 +309,7 @@ class AdminViewPermissionsTest(TestCase):
self.client.post('/test_admin/admin/', self.adduser_login)
post = self.client.post('/test_admin/admin/admin_views/article/add/', add_dict)
self.assertRedirects(post, '/test_admin/admin/')
- self.failUnlessEqual(Article.objects.all().count(), 2)
+ self.failUnlessEqual(Article.objects.all().count(), 4)
self.client.get('/test_admin/admin/logout/')
# Super can add too, but is redirected to the change list view
@@ -268,7 +317,7 @@ class AdminViewPermissionsTest(TestCase):
self.client.post('/test_admin/admin/', self.super_login)
post = self.client.post('/test_admin/admin/admin_views/article/add/', add_dict)
self.assertRedirects(post, '/test_admin/admin/admin_views/article/')
- self.failUnlessEqual(Article.objects.all().count(), 3)
+ self.failUnlessEqual(Article.objects.all().count(), 5)
self.client.get('/test_admin/admin/logout/')
# 8509 - if a normal user is already logged in, it is possible
@@ -392,7 +441,7 @@ class AdminViewPermissionsTest(TestCase):
self.failUnlessEqual(request.status_code, 403)
post = self.client.post('/test_admin/admin/admin_views/article/1/delete/', delete_dict)
self.failUnlessEqual(post.status_code, 403)
- self.failUnlessEqual(Article.objects.all().count(), 1)
+ self.failUnlessEqual(Article.objects.all().count(), 3)
self.client.get('/test_admin/admin/logout/')
# Delete user can delete
@@ -406,7 +455,7 @@ class AdminViewPermissionsTest(TestCase):
self.failUnlessEqual(response.status_code, 200)
post = self.client.post('/test_admin/admin/admin_views/article/1/delete/', delete_dict)
self.assertRedirects(post, '/test_admin/admin/')
- self.failUnlessEqual(Article.objects.all().count(), 0)
+ self.failUnlessEqual(Article.objects.all().count(), 2)
self.client.get('/test_admin/admin/logout/')
class AdminViewStringPrimaryKeyTest(TestCase):