diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index e9064bb68c..f052fe1808 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -189,6 +189,12 @@ class BaseModelAdmin(object): return None declared_fieldsets = property(_declared_fieldsets) + def get_ordering(self, request): + """ + Hook for specifying field ordering. + """ + return self.ordering or () # otherwise we might try to *None, which is bad ;) + def get_readonly_fields(self, request, obj=None): """ Hook for specifying custom readonly fields. @@ -208,7 +214,7 @@ class BaseModelAdmin(object): """ qs = self.model._default_manager.get_query_set() # TODO: this should be handled by some parameter to the ChangeList. - ordering = self.ordering or () # otherwise we might try to *None, which is bad ;) + ordering = self.get_ordering(request) if ordering: qs = qs.order_by(*ordering) return qs @@ -260,6 +266,7 @@ class BaseModelAdmin(object): clean_lookup = LOOKUP_SEP.join(parts) return clean_lookup in self.list_filter or clean_lookup == self.date_hierarchy + class ModelAdmin(BaseModelAdmin): "Encapsulates all admin options and functionality for a given model." diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index 85b8562659..aea656f401 100644 --- a/django/contrib/admin/views/main.py +++ b/django/contrib/admin/views/main.py @@ -76,7 +76,7 @@ class ChangeList(object): self.list_editable = () else: self.list_editable = list_editable - self.ordering = self.get_ordering() + self.ordering = self.get_ordering(request) self.query = request.GET.get(SEARCH_VAR, '') self.query_set = self.get_query_set(request) self.get_results(request) @@ -172,12 +172,13 @@ class ChangeList(object): ordering = self.lookup_opts.ordering return ordering - def get_ordering(self): + def get_ordering(self, request): params = self.params - # For ordering, first check the "ordering" parameter in the admin + # For ordering, first check the if exists the "get_ordering" method + # in model admin, then check "ordering" parameter in the admin # options, then check the object's default ordering. Finally, a # manually-specified ordering from the query string overrides anything. - ordering = self._get_default_ordering() + ordering = self.model_admin.get_ordering(request) or self._get_default_ordering() if ORDER_VAR in params: # Clear ordering and used params diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 8f941299f7..c0ff3c6036 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -704,6 +704,11 @@ subclass:: If this isn't provided, the Django admin will use the model's default ordering. + .. versionadded:: 1.4 + + If you need to specify a dynamic order (for example depending on user or + language) you can implement a :meth:`~ModelAdmin.get_ordering` method. + .. versionchanged:: 1.4 Django honors all elements in the list/tuple; before 1.4, only the first @@ -957,6 +962,22 @@ templates used by the :class:`ModelAdmin` views: instance.save() formset.save_m2m() +.. method:: ModelAdmin.get_ordering(self, request) + + .. versionadded:: 1.4 + + The ``get_ordering`` method takes a``request`` as parameter and + is expected to return a ``list`` or ``tuple`` for ordering similiar + to the :attr:`ordering` attribute. For example:: + + class PersonAdmin(ModelAdmin): + + def get_ordering(self, request): + if request.user.is_superuser: + return ['name', 'rank'] + else: + return ['name'] + .. method:: ModelAdmin.get_readonly_fields(self, request, obj=None) .. versionadded:: 1.2 @@ -1053,7 +1074,7 @@ templates used by the :class:`ModelAdmin` views: .. method:: ModelAdmin.formfield_for_foreignkey(self, db_field, request, **kwargs) The ``formfield_for_foreignkey`` method on a ``ModelAdmin`` allows you to - override the default formfield for a foreign key field. For example, to + override the default formfield for a foreign keys field. For example, to return a subset of objects for this foreign key field based on the user:: class MyModelAdmin(admin.ModelAdmin): diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt index 65f3dfe050..cc20795ded 100644 --- a/docs/releases/1.4.txt +++ b/docs/releases/1.4.txt @@ -73,10 +73,12 @@ documentation for :attr:`~django.contrib.admin.ModelAdmin.list_filter`. Multiple sort in admin interface ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The admin change list now supports sorting on multiple columns. It respects all -elements of the :attr:`~django.contrib.admin.ModelAdmin.ordering` attribute, and -sorting on multiple columns by clicking on headers is designed to work similarly -to how desktop GUIs do it. +The admin change list now supports sorting on multiple columns. It respects +all elements of the :attr:`~django.contrib.admin.ModelAdmin.ordering` +attribute, and sorting on multiple columns by clicking on headers is designed +to work similarly to how desktop GUIs do it. The new hook +:meth:`~django.contrib.admin.ModelAdmin.get_ordering` for specifying the +ordering dynamically (e.g. depending on the request) has also been added. Tools for cryptographic signing ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/regressiontests/admin_ordering/models.py b/tests/regressiontests/admin_ordering/models.py index fb766777a9..7c92b42010 100644 --- a/tests/regressiontests/admin_ordering/models.py +++ b/tests/regressiontests/admin_ordering/models.py @@ -24,3 +24,11 @@ class SongInlineDefaultOrdering(admin.StackedInline): class SongInlineNewOrdering(admin.StackedInline): model = Song ordering = ('duration', ) + +class DynOrderingBandAdmin(admin.ModelAdmin): + + def get_ordering(self, request): + if request.user.is_superuser: + return ['rank'] + else: + return ['name'] diff --git a/tests/regressiontests/admin_ordering/tests.py b/tests/regressiontests/admin_ordering/tests.py index 8f5a6bd05a..70eca5f771 100644 --- a/tests/regressiontests/admin_ordering/tests.py +++ b/tests/regressiontests/admin_ordering/tests.py @@ -1,7 +1,8 @@ -from django.test import TestCase +from django.test import TestCase, RequestFactory +from django.contrib.auth.models import User from django.contrib.admin.options import ModelAdmin -from models import Band, Song, SongInlineDefaultOrdering, SongInlineNewOrdering +from models import Band, Song, SongInlineDefaultOrdering, SongInlineNewOrdering, DynOrderingBandAdmin class TestAdminOrdering(TestCase): """ @@ -11,6 +12,7 @@ class TestAdminOrdering(TestCase): """ def setUp(self): + self.request_factory = RequestFactory() b1 = Band(name='Aerosmith', bio='', rank=3) b1.save() b2 = Band(name='Radiohead', bio='', rank=1) @@ -38,6 +40,22 @@ class TestAdminOrdering(TestCase): names = [b.name for b in ma.queryset(None)] self.assertEqual([u'Radiohead', u'Van Halen', u'Aerosmith'], names) + def test_dynamic_ordering(self): + """ + Let's use a custom ModelAdmin that changes the ordering dinamically. + """ + super_user = User.objects.create(username='admin', is_superuser=True) + other_user = User.objects.create(username='other') + request = self.request_factory.get('/') + request.user = super_user + ma = DynOrderingBandAdmin(Band, None) + names = [b.name for b in ma.queryset(request)] + self.assertEqual([u'Radiohead', u'Van Halen', u'Aerosmith'], names) + request.user = other_user + names = [b.name for b in ma.queryset(request)] + self.assertEqual([u'Aerosmith', u'Radiohead', u'Van Halen'], names) + + class TestInlineModelAdminOrdering(TestCase): """ Let's make sure that InlineModelAdmin.queryset uses the ordering we define