From f2ed107b079b050950e2fc5f2b689ca553ae12f5 Mon Sep 17 00:00:00 2001 From: Julien Phalip Date: Sat, 24 Sep 2011 06:17:53 +0000 Subject: [PATCH] Simplified the admin changelist multi-sort interface specifically by removing the popup window, adding explicit tooltip help texts, improving the hover visual states and allowing all operations (i.e. removing a column from sorting and toggling the sorting with and without changing the sorting priority) to be actionable with just one click. Many thanks to Idan Gazit for the feedback and direction. Refs #16212. git-svn-id: http://code.djangoproject.com/svn/django/trunk@16899 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../contrib/admin/static/admin/css/base.css | 127 +++++++++--------- django/contrib/admin/static/admin/css/rtl.css | 17 +-- .../admin/static/admin/img/arrow-down.gif | Bin 80 -> 0 bytes .../admin/static/admin/img/arrow-up.gif | Bin 838 -> 0 bytes .../admin/static/admin/img/icon_cog.gif | Bin 537 -> 0 bytes .../admin/static/admin/img/sorting-icons.gif | Bin 0 -> 369 bytes .../templates/admin/change_list_results.html | 106 ++------------- .../contrib/admin/templatetags/admin_list.py | 27 ++-- docs/releases/1.4.txt | 13 +- 9 files changed, 104 insertions(+), 186 deletions(-) delete mode 100644 django/contrib/admin/static/admin/img/arrow-down.gif delete mode 100644 django/contrib/admin/static/admin/img/arrow-up.gif delete mode 100644 django/contrib/admin/static/admin/img/icon_cog.gif create mode 100644 django/contrib/admin/static/admin/img/sorting-icons.gif diff --git a/django/contrib/admin/static/admin/css/base.css b/django/contrib/admin/static/admin/css/base.css index 89f2225c7c..5db94a7d25 100644 --- a/django/contrib/admin/static/admin/css/base.css +++ b/django/contrib/admin/static/admin/css/base.css @@ -309,77 +309,84 @@ tr.alt { /* SORTABLE TABLES */ +thead th { + padding: 0; + line-height: normal; +} + thead th a:link, thead th a:visited { color: #666; - display: block; } thead th.sorted { background: #c5c5c5 url(../img/nav-bg-selected.gif) top left repeat-x; } -table thead th.sorted a { - padding-right: 13px; +table thead th .text span { + padding: 2px 5px; + display:block; } -table thead th.ascending a { - background: url(../img/arrow-up.gif) right .4em no-repeat; -} - -table thead th.descending a { - background: url(../img/arrow-down.gif) right .4em no-repeat; -} - -table thead th.sorted a span.text { - display: block; - float: left; - cursor: pointer; /* IE needs this */ -} - -table thead th.sorted a span.sortpos { - display: block; - float: right; - font-size: .6em; - text-align: right; - cursor: pointer; /* IE needs this */ -} - -table thead th.sorted a img { - vertical-align: top; -} - -table thead th.sorted a span.clear { - display: block; - clear: both; -} - -#sorting-popup-div { - display: none; - position: absolute; - background-color: white; - border: 1px solid #ddd; - z-index: 2000; /* more than filters on right */ -} - -#sorting-popup-div table { - border-right: 0px; - border-left: 0px; -} - -#sorting-popup-div .reset { - text-align: center; -} - -#sorting-popup-div .cancel { - font-size: 10px; - background: #e1e1e1 url(../img/nav-bg.gif) 0 50% repeat-x; - border-top: 1px solid #ddd; - text-align: center; -} - -#sorting-popup-div .cancel a { - width: 100%; +table thead th .text a { display: block; + cursor: pointer; + padding: 2px 5px; +} + +table thead th.sortable:hover { + background: white url(../img/nav-bg-reverse.gif) 0 -5px repeat-x; +} + +thead th.sorted a.sortremove { + visibility: hidden; +} + +table thead th.sorted:hover a.sortremove { + visibility: visible; +} + +table thead th.sorted .sortoptions { + display: block; + padding: 4px 5px 0 5px; + float: right; + text-align: right; +} + +table thead th.sorted .sortpriority { + font-size: .8em; + min-width: 12px; + text-align: center; + vertical-align: top; +} + +table thead th.sorted .sortoptions a { + width: 14px; + height: 12px; + display: inline-block; +} + +table thead th.sorted .sortoptions a.sortremove { + background: url(../img/sorting-icons.gif) -4px -5px no-repeat; +} + +table thead th.sorted .sortoptions a.sortremove:hover { + background: url(../img/sorting-icons.gif) -4px -27px no-repeat; +} + +table thead th.sorted .sortoptions a.ascending { + background: url(../img/sorting-icons.gif) -5px -50px no-repeat; +} + +table thead th.sorted .sortoptions a.ascending:hover { + background: url(../img/sorting-icons.gif) -5px -72px no-repeat; +} + +table thead th.sorted .sortoptions a.descending { + background: url(../img/sorting-icons.gif) -5px -94px no-repeat; +} + +table thead th.sorted .sortoptions a.descending:hover { + background: url(../img/sorting-icons.gif) -5px -115px no-repeat; } /* ORDERABLE TABLES */ diff --git a/django/contrib/admin/static/admin/css/rtl.css b/django/contrib/admin/static/admin/css/rtl.css index fce6b1ab0e..029bf36f82 100644 --- a/django/contrib/admin/static/admin/css/rtl.css +++ b/django/contrib/admin/static/admin/css/rtl.css @@ -80,22 +80,7 @@ div.breadcrumbs { /* SORTABLE TABLES */ - -table thead th.sorted a { - padding-left: 13px; - padding-right: 0px; -} - -table thead th.ascending a, -table thead th.descending a { - background-position: left; -} - -table thead th.sorted a span.text { - float: right; -} - -table thead th.sorted a span.sortpos { +table thead th.sorted .sortoptions { float: left; } diff --git a/django/contrib/admin/static/admin/img/arrow-down.gif b/django/contrib/admin/static/admin/img/arrow-down.gif deleted file mode 100644 index a967b9fd5563a0fc2f5fde8ec0f7de3fc8fbc5a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 80 zcmZ?wbhEHb3wfn>L+1d2-{%jW1rjm^pLi|Ns9P7#I|PvM@3LmFNK3 i3?Q`(%%TyyyiA!oB04G)Te5yh$2@OMO7G-kum%ACMICAY diff --git a/django/contrib/admin/static/admin/img/arrow-up.gif b/django/contrib/admin/static/admin/img/arrow-up.gif deleted file mode 100644 index 3fe4851399a37337891ccf5452ee5d59bbedeec7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 838 zcmZ?wbhEHb3wfn>L+1d2-{%jW1rjm^pJM!zdUHfe{k|ia%Kx8GuSOX;Q=NFCLWFl0*@NH#f6pb?3n1_z+epkX4f1j diff --git a/django/contrib/admin/static/admin/img/icon_cog.gif b/django/contrib/admin/static/admin/img/icon_cog.gif deleted file mode 100644 index e390ef2ef5a00858c6dda1974a1f334eddff46be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 537 zcmZ?wbhEHbeT7er_Y@`ciy~t^XJcBuwcQ$g$oxgTC{la;w4L# zEM2;E*|KHJmoHzjV#TUet5&aGy=KjtwQJX|Teoig`t=(&Y}mMQk7cXDFeC5iO8#iv;ym|B1ty{Nm-@bF_&b@p09zJ~d zPslyS+G*NoT!1voMb! zn^BXstOT#Nl7_gDl%X1nlmee5pNt%jh^?T!xK6!=fF_4|y|lQfX`7HLXT27yiX6Yb wq^SC8V{R@HWifeaZceo(OLj#*IbMC|PAeuxBQ6HLN%aQ0^=2AQO^poJ0GNkqX#fBK diff --git a/django/contrib/admin/static/admin/img/sorting-icons.gif b/django/contrib/admin/static/admin/img/sorting-icons.gif new file mode 100644 index 0000000000000000000000000000000000000000..451aae59874c795763cee9b044bf2c487f15a853 GIT binary patch literal 369 zcmZ?wbhEHb6k}*%IKsg2;>C++&z_w!(hg+Pin}?%lgLZrnIy)1g(XR`svm zGk^a4IdkUBoH=vGj2X$3R|a-2{Qv(y1JOY7CkrDxgCK(rNC(JH2G(~8Dt#%L^DAd!+Lo{0I{PlaWBPvS zl4w&vYiXCFU#`t13Om+FTO9ns`tXZiGLm(GX1Uw7}z rTlY@?-zz3A8IKJL+X^m4Dfq0|aN9iZ(F((o&leUP?N)YTV6X-NKX0r` literal 0 HcmV?d00001 diff --git a/django/contrib/admin/templates/admin/change_list_results.html b/django/contrib/admin/templates/admin/change_list_results.html index b1db6470c9..e3d4b2549a 100644 --- a/django/contrib/admin/templates/admin/change_list_results.html +++ b/django/contrib/admin/templates/admin/change_list_results.html @@ -11,15 +11,17 @@ {% for header in result_headers %} - {% if header.sortable %}{% endif %} - {{ header.text|capfirst }} - {% if header.sortable %} - {% if header.sort_pos > 0 %} - {% if header.sort_pos == 1 %} {% endif %} - {{ header.sort_pos }} - {% endif %} - - {% endif %} + {% if header.sortable %} + {% if header.sort_priority > 0 %} +
+ + {% if num_sorted_fields > 1 %}{{ header.sort_priority }}{% endif %} + +
+ {% endif %} + {% endif %} +
{% if header.sortable %}{{ header.text|capfirst }}{% else %}{{ header.text|capfirst }}{% endif %}
+
{% endfor %} @@ -33,90 +35,4 @@ - -{# Sorting popup: #} -
- - - - {% for header in result_headers|dictsort:"sort_pos" %} - {% if header.sort_pos > 0 %} - - - - - - - - {% endif %} - {% endfor %} - -
- {% trans "Sorting by:" %} -
{{ header.sort_pos }}{{ header.text|capfirst }}{% if header.ascending %}{% trans "ascending" %}{% else %}{% trans "descending" %}{% endif %}{% trans "toggle" %}{% trans "remove" %}
- - -
- - {% endif %} diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index 0f5eafc24d..ba3d1329ad 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -83,7 +83,6 @@ def result_headers(cl): """ ordering_field_columns = cl.get_ordering_field_columns() for i, field_name in enumerate(cl.list_display): - admin_order_field = None text, attr = label_for_field(field_name, cl.model, model_admin = cl.model_admin, return_attr = True @@ -95,25 +94,31 @@ def result_headers(cl): if field_name == 'action_checkbox': yield { "text": text, - "class_attrib": mark_safe(' class="action-checkbox-column"') + "class_attrib": mark_safe(' class="action-checkbox-column"'), + "sortable": False, } continue admin_order_field = getattr(attr, "admin_order_field", None) if not admin_order_field: # Not sortable - yield {"text": text} + yield { + "text": text, + "sortable": False, + } continue # OK, it is sortable if we got this far - th_classes = [] + th_classes = ['sortable'] order_type = '' new_order_type = 'asc' - sort_pos = 0 + sort_priority = 0 + sorted = False # Is it currently being sorted on? if i in ordering_field_columns: + sorted = True order_type = ordering_field_columns.get(i).lower() - sort_pos = ordering_field_columns.keys().index(i) + 1 + sort_priority = ordering_field_columns.keys().index(i) + 1 th_classes.append('sorted %sending' % order_type) new_order_type = {'asc': 'desc', 'desc': 'asc'}[order_type] @@ -144,8 +149,9 @@ def result_headers(cl): yield { "text": text, "sortable": True, + "sorted": sorted, "ascending": order_type == "asc", - "sort_pos": sort_pos, + "sort_priority": sort_priority, "url_primary": cl.get_query_string({ORDER_VAR: '.'.join(o_list_primary)}), "url_remove": cl.get_query_string({ORDER_VAR: '.'.join(o_list_remove)}), "url_toggle": cl.get_query_string({ORDER_VAR: '.'.join(o_list_toggle)}), @@ -260,13 +266,14 @@ def result_list(cl): Displays the headers and data list together """ headers = list(result_headers(cl)) + num_sorted_fields = 0 for h in headers: - # Sorting in templates depends on sort_pos attribute - h.setdefault('sort_pos', 0) + if h['sortable'] and h['sorted']: + num_sorted_fields += 1 return {'cl': cl, 'result_hidden_fields': list(result_hidden_fields(cl)), 'result_headers': headers, - 'reset_sorting_url': cl.get_query_string(remove=[ORDER_VAR]), + 'num_sorted_fields': num_sorted_fields, 'results': list(results(cl))} @register.inclusion_tag('admin/date_hierarchy.html') diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt index 5b92f95f59..9cf268a869 100644 --- a/docs/releases/1.4.txt +++ b/docs/releases/1.4.txt @@ -360,13 +360,16 @@ Removed admin icons ~~~~~~~~~~~~~~~~~~~ As part of an effort to improve the performance and usability of the admin's -vertical and horizontal "filter" widgets, some icon files were removed and -grouped into a single sprite file (``selector-icons.gif``): +changelist sorting interface and of the admin's :attr:`horizontal +` and :attr:`vertical +` "filter" widgets, some icon +files were removed and grouped into two sprite files, respectively: ``selector-add.gif``, ``selector-addall.gif``, ``selector-remove.gif``, ``selector-removeall.gif``, ``selector_stacked-add.gif`` and -``selector_stacked-remove.gif``. If you used those icons to customize the -admin then you will want to replace them with your own icons or retrieve them -from a previous release. +``selector_stacked-remove.gif`` into ``selector-icons.gif``; and +``arrow-up.gif`` and ``arrow-down.gif`` into ``sorting-icons.gif``. If you used +those icons to customize the admin then you will want to replace them with your +own icons or retrieve them from a previous release. Compatibility with old signed data ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~