Code

Ticket #11868: 11868.diff

File 11868.diff, 8.5 KB (added by jacob, 3 years ago)

Updated to trunk (r16060)

Line 
1diff --git a/django/contrib/admin/media/css/base.css b/django/contrib/admin/media/css/base.css
2index c5e385d..9a7a0f2 100644
3--- a/django/contrib/admin/media/css/base.css
4+++ b/django/contrib/admin/media/css/base.css
5@@ -326,6 +326,20 @@ table thead th.descending a {
6     background: url(../img/admin/arrow-down.gif) right .4em no-repeat;
7 }
8 
9+table thead th.sorted a span.text {
10+   display: block;
11+   float: left;
12+}
13+table thead th.sorted a span.sort_pos {
14+   display: block;
15+   float: right;
16+   font-size: .6em;
17+}
18+table thead th.sorted a span.clear{
19+   display: block;
20+   clear: both;
21+}
22+
23 /* ORDERABLE TABLES */
24 
25 table.orderable tbody tr td:hover {
26diff --git a/django/contrib/admin/templates/admin/change_list_results.html b/django/contrib/admin/templates/admin/change_list_results.html
27index b3fa224..2fd44c8 100644
28--- a/django/contrib/admin/templates/admin/change_list_results.html
29+++ b/django/contrib/admin/templates/admin/change_list_results.html
30@@ -8,10 +8,14 @@
31 <table id="result_list">
32 <thead>
33 <tr>
34-{% for header in result_headers %}<th scope="col"{{ header.class_attrib }}>
35-{% if header.sortable %}<a href="{{ header.url }}">{% endif %}
36-{{ header.text|capfirst }}
37-{% if header.sortable %}</a>{% endif %}</th>{% endfor %}
38+{% for header in result_headers %}
39+<th scope="col" {{ header.class_attrib }}>
40+  {% if header.sortable %}<a href="{{ header.url }}">{% endif %}
41+  <span class="text">{{ header.text|capfirst }}</span>
42+  {% if header.sortable %}<span class="sort_pos">{{ header.sort_pos }}</span>
43+  <span class="clear"></span>
44+  </a>{% endif %}
45+</th>{% endfor %}
46 </tr>
47 </thead>
48 <tbody>
49diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
50index fdf082b..3b34091 100644
51--- a/django/contrib/admin/templatetags/admin_list.py
52+++ b/django/contrib/admin/templatetags/admin_list.py
53@@ -110,14 +110,44 @@ def result_headers(cl):
54 
55         th_classes = []
56         new_order_type = 'asc'
57-        if field_name == cl.order_field or admin_order_field == cl.order_field:
58-            th_classes.append('sorted %sending' % cl.order_type.lower())
59-            new_order_type = {'asc': 'desc', 'desc': 'asc'}[cl.order_type.lower()]
60+        ordering_fields = cl.get_ordering_fields()
61+        sort_pos = ''
62+        if field_name in ordering_fields.keys() or admin_order_field in ordering_fields.keys():
63+            if not field_name in ordering_fields.keys():
64+                field_name = admin_order_field
65+            order_type = ordering_fields.get(field_name).lower()
66+            sort_pos = ordering_fields.keys().index(field_name) + 1
67+            th_classes.append('sorted %sending' % order_type)
68+            new_order_type = {'':'asc', 'asc': 'desc', 'desc': ''}[order_type]
69+
70+        # build new ordering param
71+        o_list = []
72+        for f in ordering_fields.keys():
73+            try:
74+                n = cl.list_display.index(f)
75+            except ValueError:
76+                continue
77+
78+            if f == field_name:
79+                if new_order_type == '':
80+                    continue
81+                t = new_order_type
82+            else:
83+                t = ordering_fields.get(f)
84+
85+            o_list.append((t=='desc' and '-' or '') + str(n))
86+
87+        if field_name not in ordering_fields.keys() and new_order_type:
88+            n = cl.list_display.index(field_name)
89+            o_list.append((new_order_type=='desc' and '-' or '') + str(n))
90+
91+        o_list = ','.join(o_list)
92 
93         yield {
94             "text": header,
95             "sortable": True,
96-            "url": cl.get_query_string({ORDER_VAR: i, ORDER_TYPE_VAR: new_order_type}),
97+            "sort_pos": sort_pos > 0 and len(ordering_fields) > 0 and unicode(sort_pos) or '',
98+            "url": cl.get_query_string({ORDER_VAR: o_list}),
99             "class_attrib": mark_safe(th_classes and ' class="%s"' % ' '.join(th_classes) or '')
100         }
101 
102diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
103index 170d168..b53806c 100644
104--- a/django/contrib/admin/views/main.py
105+++ b/django/contrib/admin/views/main.py
106@@ -4,6 +4,8 @@ from django.contrib.admin.util import quote, get_fields_from_path
107 from django.core.exceptions import SuspiciousOperation
108 from django.core.paginator import InvalidPage
109 from django.db import models
110+from django.db.models.query import QuerySet
111+from django.utils.datastructures import SortedDict
112 from django.utils.encoding import force_unicode, smart_str
113 from django.utils.translation import ugettext, ugettext_lazy
114 from django.utils.http import urlencode
115@@ -59,7 +61,7 @@ class ChangeList(object):
116             self.list_editable = ()
117         else:
118             self.list_editable = list_editable
119-        self.order_field, self.order_type = self.get_ordering()
120+        self.ordering = self.get_ordering()
121         self.query = request.GET.get(SEARCH_VAR, '')
122         self.query_set = self.get_query_set()
123         self.get_results(request)
124@@ -136,35 +138,46 @@ class ChangeList(object):
125         # manually-specified ordering from the query string.
126         ordering = self.model_admin.ordering or lookup_opts.ordering or ['-' + lookup_opts.pk.name]
127 
128-        if ordering[0].startswith('-'):
129-            order_field, order_type = ordering[0][1:], 'desc'
130-        else:
131-            order_field, order_type = ordering[0], 'asc'
132         if ORDER_VAR in params:
133-            try:
134-                field_name = self.list_display[int(params[ORDER_VAR])]
135+            # Clear ordering and used params
136+            ordering = []
137+            order_params = params[ORDER_VAR].split(',')
138+            for p in order_params:
139                 try:
140-                    f = lookup_opts.get_field(field_name)
141-                except models.FieldDoesNotExist:
142-                    # See whether field_name is a name of a non-field
143-                    # that allows sorting.
144+                    none, pfx, idx = p.rpartition('-')
145+                    field_name = self.list_display[int(idx)]
146                     try:
147-                        if callable(field_name):
148-                            attr = field_name
149-                        elif hasattr(self.model_admin, field_name):
150-                            attr = getattr(self.model_admin, field_name)
151-                        else:
152-                            attr = getattr(self.model, field_name)
153-                        order_field = attr.admin_order_field
154-                    except AttributeError:
155-                        pass
156-                else:
157-                    order_field = f.name
158-            except (IndexError, ValueError):
159-                pass # Invalid ordering specified. Just use the default.
160-        if ORDER_TYPE_VAR in params and params[ORDER_TYPE_VAR] in ('asc', 'desc'):
161-            order_type = params[ORDER_TYPE_VAR]
162-        return order_field, order_type
163+                        f = lookup_opts.get_field(field_name)
164+                    except models.FieldDoesNotExist:
165+                        # See whether field_name is a name of a non-field
166+                        # that allows sorting.
167+                        try:
168+                            if callable(field_name):
169+                                attr = field_name
170+                            elif hasattr(self.model_admin, field_name):
171+                                attr = getattr(self.model_admin, field_name)
172+                            else:
173+                                attr = getattr(self.model, field_name)
174+                            field_name = attr.admin_order_field
175+                        except AttributeError:
176+                            pass
177+                    else:
178+                        field_name = f.name
179+
180+                    ordering.append(pfx + field_name)
181+
182+                except (IndexError, ValueError):
183+                    pass # Invalid ordering specified. Skip it.
184+
185+        return ordering
186+
187+    def get_ordering_fields(self):
188+        # Returns a SortedDict of ordering fields and asc/desc
189+        ordering_fields = SortedDict()
190+        for o in self.ordering:
191+            none, t, f = o.rpartition('-')
192+            ordering_fields[f] = t == '-' and 'desc' or 'asc'
193+        return ordering_fields
194 
195     def get_query_set(self):
196         use_distinct = False
197@@ -239,8 +252,8 @@ class ChangeList(object):
198                             break
199 
200         # Set ordering.
201-        if self.order_field:
202-            qs = qs.order_by('%s%s' % ((self.order_type == 'desc' and '-' or ''), self.order_field))
203+        if self.ordering:
204+            qs = qs.order_by(*self.ordering)
205 
206         # Apply keyword searches.
207         def construct_search(field_name):