How to add a sortable count column to the Django admin of a model with a many-to-one relation?

DjangoDjango ModelsDjango Admin

Django Problem Overview


Suppose I have a Book model containing a foreign key to a Publisher model.

How can I display in the Django admin a column with the number of books published by each publisher, in a way that I can use the built-in sorting?

Django Solutions


Solution 1 - Django

I had the same issue (I cannot change my model's manager to add slow annotations or joins). A combination of two of the answers here works. @Andre is really close, the Django Admin supports modifying the queryset for just the admin, so apply the same logic here and then user the admin_order_field attribute. You still need to add the new admin field to list_display, of course.

from django.db.models import Count

class EventAdmin(admin.ModelAdmin)
    list_display = (..., 'show_artist_count')

    def queryset(self, request):
    # def get_queryset(self, request):    for Django 1.6+
        qs = super(EventAdmin, self).queryset(request)
        return qs.annotate(artist_count=Count('artists'))

    def show_artist_count(self, inst):
        return inst.artist_count
    show_artist_count.admin_order_field = 'artist_count'

Solution 2 - Django

Try this:

make a new Manager (and aggregate with count on the book relation field):

class PublisherManager(models.Manager):
    def get_query_set(self):
        return super(PublisherManager,self).get_query_set().annotate(pubcount=Count('book'))

sort it on pubcount:

class Publisher(models.Model):
    ......
    objects = PublisherManager()
   
    class Meta:
        ordering = ('pubcount',)

Solution 3 - Django

You should indeed start off with adding:

class PublisherManager(models.Manager):
    def get_query_set(self):
        return super(PublisherManager,self).get_query_set().annotate(pubcount=Count('book'))

But the correct way to add it as a sortable field is:

class Publisher(models.Model):
    ......
    objects = PublisherManager()

    def count(self):
        return self.pubcount
    count.admin_order_field = 'pubcount'

And then you can just add 'count' to the list_display attribute of model admin in admin.py

Solution 4 - Django

Lincoln B's answer was the right way for me.

At first I wanted to just comment on his solution, but I actually found myself solving a slightly different problem. I had an admin class, which I wanted to "customize" to my needs - namely the django-taggit admin. In one of my application's admin.py, I added:

# sort tags by name in admin (count items also possible)
from taggit.admin import TagAdmin
TagAdmin.ordering = ["name"]
#   make sortable on item_count:
# 1. function for lookup
def item_count(obj):
    """This takes the item_count from object: didn't work as model field."""
    return obj.item_count # not needed: obj.taggit_taggeditem_items.count()
# 2. property in function - admin field name
item_count.admin_order_field = 'item_count'
# 3. queryset override, with count annotation
from django.db.models import Count
TagAdmin.queryset = lambda self, request: super(TagAdmin, self).queryset(request).annotate(item_count=Count('taggit_taggeditem_items'))
# 4. add to list display
TagAdmin.list_display = ["name", item_count]

The interesting observation for me was, that I could not just annotate the queryset, and add "item_count" to list_display - because there was no item_count method in TagAdmin, nor a method or field in the Tag model class (only in the queryset).

Solution 5 - Django

Try something like this:

class PublisherAdminWithCount(Admin):

    def book_count(self, obj):
        return obj.book_set.count()

    list_display = ('user_count',)

admin.site.register(Group, PublisherAdminWithCount)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionGJ.View Question on Stackoverflow
Solution 1 - DjangoLincoln BView Answer on Stackoverflow
Solution 2 - DjangoAndre BossardView Answer on Stackoverflow
Solution 3 - DjangoGJ.View Answer on Stackoverflow
Solution 4 - DjangoTomasz GandorView Answer on Stackoverflow
Solution 5 - DjangogruszczyView Answer on Stackoverflow