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 - <p>test content</p> + <p>Middle content</p> 2008-03-18 11:54:58 1 + + <p>Oldest content</p> + 2000-03-18 11:54:58 + 1 + + + <p>Newest content</p> + 2009-03-18 11:54:58 + 1 + + + \ 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):