Fixed #25790 -- Allowed disable column sorting in the admin changelist.
Thanks Ramiro Morales for completing the patch.
This commit is contained in:
parent
7d96f0c49a
commit
ef2512b2ff
|
@ -111,6 +111,7 @@ class BaseModelAdmin(metaclass=forms.MediaDefiningClass):
|
|||
formfield_overrides = {}
|
||||
readonly_fields = ()
|
||||
ordering = None
|
||||
sortable_by = None
|
||||
view_on_site = True
|
||||
show_full_result_count = True
|
||||
checks_class = BaseModelAdminChecks
|
||||
|
@ -353,6 +354,10 @@ class BaseModelAdmin(metaclass=forms.MediaDefiningClass):
|
|||
qs = qs.order_by(*ordering)
|
||||
return qs
|
||||
|
||||
def get_sortable_by(self, request):
|
||||
"""Hook for specifying which fields can be sorted in the changelist."""
|
||||
return self.sortable_by if self.sortable_by is not None else self.get_list_display(request)
|
||||
|
||||
def lookup_allowed(self, lookup, value):
|
||||
from django.contrib.admin.filters import SimpleListFilter
|
||||
|
||||
|
@ -688,6 +693,7 @@ class ModelAdmin(BaseModelAdmin):
|
|||
# Add the action checkboxes if any actions are available.
|
||||
if self.get_actions(request):
|
||||
list_display = ['action_checkbox'] + list(list_display)
|
||||
sortable_by = self.get_sortable_by(request)
|
||||
ChangeList = self.get_changelist(request)
|
||||
return ChangeList(
|
||||
request,
|
||||
|
@ -702,6 +708,7 @@ class ModelAdmin(BaseModelAdmin):
|
|||
self.list_max_show_all,
|
||||
self.list_editable,
|
||||
self,
|
||||
sortable_by,
|
||||
)
|
||||
|
||||
def get_object(self, request, object_id, from_field=None):
|
||||
|
|
|
@ -100,6 +100,7 @@ def result_headers(cl):
|
|||
model_admin=cl.model_admin,
|
||||
return_attr=True
|
||||
)
|
||||
is_field_sortable = cl.sortable_by is None or field_name in cl.sortable_by
|
||||
if attr:
|
||||
field_name = _coerce_field_name(field_name, i)
|
||||
# Potentially not sortable
|
||||
|
@ -115,11 +116,14 @@ def result_headers(cl):
|
|||
|
||||
admin_order_field = getattr(attr, "admin_order_field", None)
|
||||
if not admin_order_field:
|
||||
is_field_sortable = False
|
||||
|
||||
if not is_field_sortable:
|
||||
# Not sortable
|
||||
yield {
|
||||
"text": text,
|
||||
"class_attrib": format_html(' class="column-{}"', field_name),
|
||||
"sortable": False,
|
||||
'text': text,
|
||||
'class_attrib': format_html(' class="column-{}"', field_name),
|
||||
'sortable': False,
|
||||
}
|
||||
continue
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ IGNORED_PARAMS = (
|
|||
class ChangeList:
|
||||
def __init__(self, request, model, list_display, list_display_links,
|
||||
list_filter, date_hierarchy, search_fields, list_select_related,
|
||||
list_per_page, list_max_show_all, list_editable, model_admin):
|
||||
list_per_page, list_max_show_all, list_editable, model_admin, sortable_by):
|
||||
self.model = model
|
||||
self.opts = model._meta
|
||||
self.lookup_opts = self.opts
|
||||
|
@ -50,6 +50,7 @@ class ChangeList:
|
|||
self.list_max_show_all = list_max_show_all
|
||||
self.model_admin = model_admin
|
||||
self.preserved_filters = model_admin.get_preserved_filters(request)
|
||||
self.sortable_by = sortable_by
|
||||
|
||||
# Get search parameters from the query string.
|
||||
try:
|
||||
|
|
|
@ -1296,6 +1296,22 @@ subclass::
|
|||
a full count on the table which can be expensive if the table contains a
|
||||
large number of rows.
|
||||
|
||||
.. attribute:: ModelAdmin.sortable_by
|
||||
|
||||
.. versionadded:: 2.1
|
||||
|
||||
By default, the change list page allows sorting by all model fields (and
|
||||
callables that have the ``admin_order_field`` property) specified in
|
||||
:attr:`list_display`.
|
||||
|
||||
If you want to disable sorting for some columns, set ``sortable_by`` to
|
||||
a collection (e.g. ``list``, ``tuple``, or ``set``) of the subset of
|
||||
:attr:`list_display` that you want to be sortable. An empty collection
|
||||
disables sorting for all columns.
|
||||
|
||||
If you need to specify this list dynamically, implement a
|
||||
:meth:`~ModelAdmin.get_sortable_by` method instead.
|
||||
|
||||
.. attribute:: ModelAdmin.view_on_site
|
||||
|
||||
Set ``view_on_site`` to control whether or not to display the "View on site" link.
|
||||
|
@ -1564,6 +1580,24 @@ templates used by the :class:`ModelAdmin` views:
|
|||
to return the same kind of sequence type as for the
|
||||
:attr:`~ModelAdmin.search_fields` attribute.
|
||||
|
||||
.. method:: ModelAdmin.get_sortable_by(request)
|
||||
|
||||
.. versionadded:: 2.1
|
||||
|
||||
The ``get_sortable_by()`` method is passed the ``HttpRequest`` and is
|
||||
expected to return a collection (e.g. ``list``, ``tuple``, or ``set``) of
|
||||
field names that will be sortable in the change list page.
|
||||
|
||||
Its default implementation returns :attr:`sortable_by` if it's set,
|
||||
otherwise it defers to :meth:`get_list_display`.
|
||||
|
||||
For example, to prevent one or more columns from being sortable::
|
||||
|
||||
class PersonAdmin(admin.ModelAdmin):
|
||||
|
||||
def get_sortable_by(self, request):
|
||||
return {*self.get_list_display(request)} - {'rank'}
|
||||
|
||||
.. method:: ModelAdmin.get_inline_instances(request, obj=None)
|
||||
|
||||
The ``get_inline_instances`` method is given the ``HttpRequest`` and the
|
||||
|
|
|
@ -43,6 +43,10 @@ Minor features
|
|||
* You can now :ref:`override the the default admin site
|
||||
<overriding-default-admin-site>`.
|
||||
|
||||
* The new :attr:`.ModelAdmin.sortable_by` attribute and
|
||||
:meth:`.ModelAdmin.get_sortable_by` method allow limiting the columns that
|
||||
can be sorted in the change list page.
|
||||
|
||||
:mod:`django.contrib.admindocs`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -1069,3 +1069,43 @@ site2.register(Person, save_as_continue=False)
|
|||
site7 = admin.AdminSite(name="admin7")
|
||||
site7.register(Article, ArticleAdmin2)
|
||||
site7.register(Section)
|
||||
|
||||
|
||||
# Used to test ModelAdmin.sortable_by and get_sortable_by().
|
||||
class ArticleAdmin6(admin.ModelAdmin):
|
||||
list_display = (
|
||||
'content', 'date', callable_year, 'model_year', 'modeladmin_year',
|
||||
'model_year_reversed', 'section',
|
||||
)
|
||||
sortable_by = ('date', callable_year)
|
||||
|
||||
def modeladmin_year(self, obj):
|
||||
return obj.date.year
|
||||
modeladmin_year.admin_order_field = 'date'
|
||||
|
||||
|
||||
class ActorAdmin6(admin.ModelAdmin):
|
||||
list_display = ('name', 'age')
|
||||
sortable_by = ('name',)
|
||||
|
||||
def get_sortable_by(self, request):
|
||||
return ('age',)
|
||||
|
||||
|
||||
class ChapterAdmin6(admin.ModelAdmin):
|
||||
list_display = ('title', 'book')
|
||||
sortable_by = ()
|
||||
|
||||
|
||||
class ColorAdmin6(admin.ModelAdmin):
|
||||
list_display = ('value',)
|
||||
|
||||
def get_sortable_by(self, request):
|
||||
return ()
|
||||
|
||||
|
||||
site6 = admin.AdminSite(name='admin6')
|
||||
site6.register(Article, ArticleAdmin6)
|
||||
site6.register(Actor, ActorAdmin6)
|
||||
site6.register(Chapter, ChapterAdmin6)
|
||||
site6.register(Color, ColorAdmin6)
|
||||
|
|
|
@ -131,6 +131,7 @@ class AdminViewBasicTestCase(TestCase):
|
|||
cls.chap4 = Chapter.objects.create(title='Chapter 2', content='[ insert contents here ]', book=cls.b2)
|
||||
cls.cx1 = ChapterXtra1.objects.create(chap=cls.chap1, xtra='ChapterXtra1 1')
|
||||
cls.cx2 = ChapterXtra1.objects.create(chap=cls.chap3, xtra='ChapterXtra1 2')
|
||||
Actor.objects.create(name='Palin', age=27)
|
||||
|
||||
# Post data for edit inline
|
||||
cls.inline_post_data = {
|
||||
|
@ -930,6 +931,35 @@ class AdminViewBasicTest(AdminViewBasicTestCase):
|
|||
self.assertContains(response, 'question__expires__month=10')
|
||||
self.assertContains(response, 'question__expires__year=2016')
|
||||
|
||||
def test_sortable_by_columns_subset(self):
|
||||
expected_sortable_fields = ('date', 'callable_year')
|
||||
expected_not_sortable_fields = (
|
||||
'content', 'model_year', 'modeladmin_year', 'model_year_reversed',
|
||||
'section',
|
||||
)
|
||||
response = self.client.get(reverse('admin6:admin_views_article_changelist'))
|
||||
for field_name in expected_sortable_fields:
|
||||
self.assertContains(response, '<th scope="col" class="sortable column-%s">' % field_name)
|
||||
for field_name in expected_not_sortable_fields:
|
||||
self.assertContains(response, '<th scope="col" class="column-%s">' % field_name)
|
||||
|
||||
def test_get_sortable_by_columns_subset(self):
|
||||
response = self.client.get(reverse('admin6:admin_views_actor_changelist'))
|
||||
self.assertContains(response, '<th scope="col" class="sortable column-age">')
|
||||
self.assertContains(response, '<th scope="col" class="column-name">')
|
||||
|
||||
def test_sortable_by_no_column(self):
|
||||
expected_not_sortable_fields = ('title', 'book')
|
||||
response = self.client.get(reverse('admin6:admin_views_chapter_changelist'))
|
||||
for field_name in expected_not_sortable_fields:
|
||||
self.assertContains(response, '<th scope="col" class="column-%s">' % field_name)
|
||||
self.assertNotContains(response, '<th scope="col" class="sortable column')
|
||||
|
||||
def test_get_sortable_by_no_column(self):
|
||||
response = self.client.get(reverse('admin6:admin_views_color_changelist'))
|
||||
self.assertContains(response, '<th scope="col" class="column-value">')
|
||||
self.assertNotContains(response, '<th scope="col" class="sortable column')
|
||||
|
||||
|
||||
@override_settings(TEMPLATES=[{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
|
|
|
@ -12,6 +12,7 @@ urlpatterns = [
|
|||
url(r'^test_admin/admin3/', (admin.site.get_urls(), 'admin', 'admin3'), {'form_url': 'pony'}),
|
||||
url(r'^test_admin/admin4/', customadmin.simple_site.urls),
|
||||
url(r'^test_admin/admin5/', admin.site2.urls),
|
||||
url(r'^test_admin/admin6/', admin.site6.urls),
|
||||
url(r'^test_admin/admin7/', admin.site7.urls),
|
||||
# All admin views accept `extra_context` to allow adding it like this:
|
||||
url(r'^test_admin/admin8/', (admin.site.get_urls(), 'admin', 'admin-extra-context'), {'extra_context': {}}),
|
||||
|
|
Loading…
Reference in New Issue