Login

Admin: ordering by multiple fields in a column sort

Author:
benatkin
Posted:
July 16, 2010
Language:
Python
Version:
1.2
Score:
6 (after 6 ratings)

The new changelist supports clicking on a column header to order by that column, like iTunes. Unlike iTunes, which sorts by track number if you click the Artist or Album column header, it can only order by the column clicked on. By adding a property to my ModelAdmin, and subclassing ChangeList, I was able to make it also sort by track number when sorting by Artist or Album.

Blog post Repository with full example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from django.contrib import admin
from django.contrib.admin.views.main import ChangeList
from tunes.models import Song

class SpecialOrderingChangeList(ChangeList):
    def apply_special_ordering(self, queryset):
        order_type, order_by = [self.params.get(param, None) for param in ('ot', 'o')]
        special_ordering = self.model_admin.special_ordering
        if special_ordering and order_type and order_by:
            try:
                order_field = self.list_display[int(order_by)]
                ordering = special_ordering[order_field]
                if order_type == 'desc':
                    ordering = ['-' + field for field in ordering]
                queryset = queryset.order_by(*ordering)
            except IndexError:
                return queryset
            except KeyError:
                return queryset
        return queryset

    def get_query_set(self):
        queryset = super(SpecialOrderingChangeList, self).get_query_set()
        queryset = self.apply_special_ordering(queryset)
        return queryset

class SongAdmin(admin.ModelAdmin):
    list_display = ['name', 'time', 'artist', 'album', 'track', 'total_tracks']
    special_ordering = {'artist': ('artist', 'album', 'track'), 'album': ('album', 'track')}

    def get_changelist(self, request, **kwargs):
        return SpecialOrderingChangeList

admin.site.register(Song, SongAdmin)

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 11 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 6 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 7 months ago
  5. Help text hyperlinks by sa2812 1 year, 8 months ago

Comments

XelaRellum (on July 24, 2010):

Thanks a lot for that snippet. Was just about to get some bad headache about the multiple column sorting when I stumbled over this. Saved my day!!

Alex

#

gamesbook (on October 18, 2010):

This does not seem to work for extra fields defined in the model class using the "def Field" function... anyone able to upgrade it to do so?

#

xaralis (on December 2, 2010):

I have enhanced this a little by taking into account default ordering and also default model ordering if it's not defined in admin:

class SpecialOrderingChangeList(ChangeList):
    def apply_special_ordering(self, queryset):
        order_type, order_by = [self.params.get(param, None) for param in ('ot', 'o')]

        ordering_attr = self.model_admin.ordering or self.model._meta.ordering

        if order_type is None and order_by is None and ordering_attr is not None:
            order_type = 'desc' if ordering_attr[0] == '-' else 'asc'
            order_by = self.model_admin.list_display.index(ordering_attr[0])

        special_ordering = self.model_admin.special_ordering
        if special_ordering and order_type and order_by:
            try:
                order_field = self.list_display[int(order_by)]
                ordering = special_ordering[order_field]
                if order_type == 'desc':
                    ordering = ['-' + field for field in ordering]
                queryset = queryset.order_by(*ordering)
            except IndexError:
                return queryset
            except KeyError:
                return queryset
        return queryset

    def get_query_set(self):
        queryset = super(SpecialOrderingChangeList, self).get_query_set()
        queryset = self.apply_special_ordering(queryset)
        return queryset

#

gamesbook (on January 12, 2011):

The script by xaralis (on December 2, 2010) returns errors: list.index(x): x not in list

Any ideas on this?

#

xaralis (on February 28, 2011):

gamesbook: I've just bumped into this as well. You should double-check what you have in ordering and special_ordering attributes. The special_ordering should always contain attribute which is named as one of model's own, something special will not work, e.g.:

ordering = ('group__name',)
special_ordering = {'group_and_name': ('group__name', 'name')}

This won't work.

ordering = ('group',)
special_ordering = {'group': ('group__name', 'name')}

This will.

#

gautier (on July 26, 2011):

You saved my day! Thanks

Even though I quite changed it :

def SpecialOrderingChangeListFactory(*ordering):
    class SpecialOrderingChangeList(ChangeList):
        def get_query_set(self):
            queryset = super(SpecialOrderingChangeList, self).get_query_set()
            queryset = queryset.order_by(*ordering)
            return queryset

    return SpecialOrderingChangeList

class SubCategoryAdmin(admin.ModelAdmin):
    def get_changelist(self, request, **kwargs):
        return SpecialOrderingChangeListFactory("category__title", "title")

#

Please login first before commenting.