Index: django/contrib/admin/templatetags/admin_list.py
===================================================================
--- django/contrib/admin/templatetags/admin_list.py	(revision 4451)
+++ django/contrib/admin/templatetags/admin_list.py	(working copy)
@@ -84,23 +84,32 @@
                     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'}
     return '<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (settings.ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val)
Index: django/contrib/admin/views/main.py
===================================================================
--- django/contrib/admin/views/main.py	(revision 4451)
+++ django/contrib/admin/views/main.py	(working copy)
@@ -655,10 +655,17 @@
             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
Index: docs/model-api.txt
===================================================================
--- docs/model-api.txt	(revision 4451)
+++ docs/model-api.txt	(working copy)
@@ -1295,11 +1295,29 @@
 
           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.
+    * For any element of ``list_display`` that is not a field on the
+      model, you can specify the actual field that should be used to
+      order the list in that element's ``admin_order_field``
+      attribute.  If you don't, then 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.
 
+      Here's a full example model::
+
+          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 '<span style="color: #%s;">%s</span>' % (self.color_code, self.first_name)
+              colored_first_name.allow_tags = True
+              colored_first_name.admin_order_field = 'first_name'
+
+
 ``list_display_links``
 ----------------------
 
