diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index 3c0c6f0ac2..5c678fba6f 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -84,22 +84,31 @@ def result_headers(cl): header = attr.short_description except AttributeError: header = field_name.replace('_', ' ') - # Non-field list_display values don't get ordering capability. - yield {"text": header} + + # It is a non-field, but perhaps one that is sortable + if not getattr(getattr(cl.model, field_name), "admin_order_field", None): + yield {"text": header} + continue + + # So this _is_ a sortable non-field. Go to the yield + # after the else clause. else: if isinstance(f.rel, models.ManyToOneRel) and f.null: yield {"text": f.verbose_name} + continue else: - th_classes = [] - new_order_type = 'asc' - if field_name == cl.order_field: - th_classes.append('sorted %sending' % cl.order_type.lower()) - new_order_type = {'asc': 'desc', 'desc': 'asc'}[cl.order_type.lower()] + header = f.verbose_name - yield {"text": f.verbose_name, - "sortable": True, - "url": cl.get_query_string({ORDER_VAR: i, ORDER_TYPE_VAR: new_order_type}), - "class_attrib": (th_classes and ' class="%s"' % ' '.join(th_classes) or '')} + th_classes = [] + new_order_type = 'asc' + if field_name == cl.order_field: + th_classes.append('sorted %sending' % cl.order_type.lower()) + new_order_type = {'asc': 'desc', 'desc': 'asc'}[cl.order_type.lower()] + + yield {"text": header, + "sortable": True, + "url": cl.get_query_string({ORDER_VAR: i, ORDER_TYPE_VAR: new_order_type}), + "class_attrib": (th_classes and ' class="%s"' % ' '.join(th_classes) or '')} def _boolean_icon(field_val): BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'} diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index 282038e205..9fae17d5fe 100644 --- a/django/contrib/admin/views/main.py +++ b/django/contrib/admin/views/main.py @@ -655,10 +655,17 @@ class ChangeList(object): order_field, order_type = ordering[0], 'asc' if params.has_key(ORDER_VAR): try: + field_name = lookup_opts.admin.list_display[int(params[ORDER_VAR])] try: - f = lookup_opts.get_field(lookup_opts.admin.list_display[int(params[ORDER_VAR])]) + f = lookup_opts.get_field(field_name) except models.FieldDoesNotExist: - pass + # see if field_name is a name of a non-field + # that allows sorting + try: + attr = getattr(lookup_opts.admin.manager.model, field_name) + order_field = attr.admin_order_field + except IndexError: + pass else: if not isinstance(f.rel, models.ManyToOneRel) or not f.null: order_field = f.name diff --git a/docs/model-api.txt b/docs/model-api.txt index 0a10918d54..9f87d53719 100644 --- a/docs/model-api.txt +++ b/docs/model-api.txt @@ -1295,10 +1295,30 @@ A few special cases to note about ``list_display``: list_display = ('__str__', 'some_other_field') - * For any element of ``list_display`` that is not a field on the model, the - change list page will not allow ordering by that column. This is because - ordering is done at the database level, and Django has no way of knowing - how to order the result of a custom method at the SQL level. + * Usually, elements of ``list_display`` that aren't actual database fields + can't be used in sorting (because Django does all the sorting at the + database level). + + However, if an element of ``list_display`` represents a certain database + field, you can indicate this fact by setting the ``admin_order_field`` + attribute of the item. + + For example:: + + class Person(models.Model): + first_name = models.CharField(maxlength=50) + color_code = models.CharField(maxlength=6) + + class Admin: + list_display = ('first_name', 'colored_first_name') + + def colored_first_name(self): + return '%s' % (self.color_code, self.first_name) + colored_first_name.allow_tags = True + colored_first_name.admin_order_field = 'first_name' + + The above will tell Django to order by the ``first_name`` field when + trying to sort by ``colored_first_name`` in the admin. ``list_display_links`` ----------------------