Can "list_display" in a Django ModelAdmin display attributes of ForeignKey fields?

I have a Person model that has a foreign key relationship to Book, which has a number of fields, but I'm most concerned about author (a standard CharField).

With that being said, in my PersonAdmin model, I'd like to display book.author using list_display:

class PersonAdmin(admin.ModelAdmin):
    list_display = ['book.author',]

I've tried all of the obvious methods for doing so, but nothing seems to work.

Any suggestions?


Solution 1:

As another option, you can do look ups like:

class UserAdmin(admin.ModelAdmin):
    list_display = (..., 'get_author')
    
    def get_author(self, obj):
        return obj.book.author
    get_author.short_description = 'Author'
    get_author.admin_order_field = 'book__author'

Since Django 3.2 you can use display() decorator:

class UserAdmin(admin.ModelAdmin):
    list_display = (..., 'get_author')
    
    @display(ordering='book__author', description='Author')
    def get_author(self, obj):
        return obj.book.author

Solution 2:

Despite all the great answers above and due to me being new to Django, I was still stuck. Here's my explanation from a very newbie perspective.

models.py

class Author(models.Model):
    name = models.CharField(max_length=255)

class Book(models.Model):
    author = models.ForeignKey(Author)
    title = models.CharField(max_length=255)

admin.py (Incorrect Way) - you think it would work by using 'model__field' to reference, but it doesn't

class BookAdmin(admin.ModelAdmin):
    model = Book
    list_display = ['title', 'author__name', ]

admin.site.register(Book, BookAdmin)

admin.py (Correct Way) - this is how you reference a foreign key name the Django way

class BookAdmin(admin.ModelAdmin):
    model = Book
    list_display = ['title', 'get_name', ]

    def get_name(self, obj):
        return obj.author.name
    get_name.admin_order_field  = 'author'  #Allows column order sorting
    get_name.short_description = 'Author Name'  #Renames column head

    #Filtering on side - for some reason, this works
    #list_filter = ['title', 'author__name']

admin.site.register(Book, BookAdmin)

For additional reference, see the Django model link here

Solution 3:

Like the rest, I went with callables too. But they have one downside: by default, you can't order on them. Fortunately, there is a solution for that:

Django >= 1.8

def author(self, obj):
    return obj.book.author
author.admin_order_field  = 'book__author'

Django < 1.8

def author(self):
    return self.book.author
author.admin_order_field  = 'book__author'

Solution 4:

Please note that adding the get_author function would slow the list_display in the admin, because showing each person would make a SQL query.

To avoid this, you need to modify get_queryset method in PersonAdmin, for example:

def get_queryset(self, request):
    return super(PersonAdmin,self).get_queryset(request).select_related('book')

Before: 73 queries in 36.02ms (67 duplicated queries in admin)

After: 6 queries in 10.81ms