How to sort Django admin list column by a value from a related model

Mar 18, 2020 · by Tim Kamanin

Here's the real-life case.

On this blog, I have two models: Post and PostStats. The former is, as you might have guessed, for posts, while the latter is for per post statistics like a view count.

Here's a simplified version of both models:

python
class Post(models.Model):
    title = models.CharField(max_length=255)
  body = models.TextField()

class PostStats(models.Model):
    post = models.ForeignKey('Post', on_delete=models.CASCADE, related_name='post_stats')
    view_count = models.PositiveIntegerField(default=0)

For the Post model I have the following ModelAdmin class:

python
class PostAdmin(admin.ModelAdmin):
    list_display = ['title', 'live']

Now I want to add a 'view_count' column from a related PostStats model to PostAdmin and be able to do a sort by this column.

My first intention was to declare view_count as a query lookup:

python
class PostAdmin(admin.ModelAdmin):
    list_display = ['title', 'live', 'post_stats__view_count']

Note I added post_stats__view_count where post_stats is the Post -> PostStats relation name, and view_count is the name of the field I want to sort by. Unfortunately, ModelAdmin wasn't happy about that and threw me an error.

Let's try something else then. Okay, you can add a synthetic column to ModelAdmin like this:

python
class PostAdmin(admin.ModelAdmin):
    list_display = ['title', 'live', 'get_view_count']

    def get_view_count(self, obj):
        return obj.tutorialstats.view_count

That gives us get_view_count column with the desired value, but there are still two issues: you can't click to sort the column, and its name is ugly.

After digging inside ModelAdmin internals a bit, I've finally found the solution! It turns out that get_view_count method may have two properties (actually it can have more, we're just interested in the following two): admin_order_field - is a field name you want the custom column to sort by. The cool thing is that admin_order_field supports query lookups like stats__view_count, bingo!; short_description - allows you to change the column name.

Now we have everything in place to solve our problem, and the complete solution looks like:

python
class PostAdmin(admin.ModelAdmin):
    list_display = ['title', 'live', 'get_view_count']

    def get_view_count(self, obj):
        return obj.tutorialstats.view_count

    get_view_count.admin_order_field = 'post_stats__view_count'
    get_view_count.short_description = 'View count'

Click on the "View count" column, and you should get the admin list results sorted by the column value.

Want to get more 🔥 tips like this one?

Subscribe to get notified about new dev tutorials