Django ModelAdmin中的"list_display"能展示ForeignKey字段的属性吗?

415

我有一个名为Person的模型,它与Book具有外键关系,其中Book有许多字段,但我最关心的是author(一个标准的CharField)。

话虽如此,在我的PersonAdmin模型中,我想使用list_display显示book.author

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

我已经尝试了所有显而易见的方法,但似乎没有什么作用。

有什么建议吗?

15个回答

651

作为另一个选项,您可以进行查找,例如:

#models.py
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'

自Django 3.2版本以来,您可以使用display()装饰器:

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

6
这会导致在管理员界面每显示一行就会产生一个查询 :( - marcelm
7
这句话的意思是:“这就是 select_related 的作用。UserAdminget_queryset() 必须被重写。” - interDist
1
对于 Django 版本 > 3.2,请参考此答案:https://dev59.com/83VC5IYBdhLWcg3w2lCI#67746847 - ajinzrathod
5
显示修饰器被定义为 @admin.display(....) - Eyong Kevin Enowanyo
1
@dark_prince,我认为这是在示例中建议列表显示中的其他字段,而不是您要执行的实际代码。 - Joe Sadoski
显示剩余6条评论

200

尽管上面有很多好的答案,但由于我对Django还很陌生,我仍然卡住了。以下是我以非常新手的角度解释的内容。

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(错误的方式) - 您认为使用“model__field”来引用将起作用,但实际上不会。

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

admin.site.register(Book, BookAdmin)

admin.py(正确方法) - 这是您按照Django方式引用外键名称的方法

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)

如需更多参考资料,请参见Django模型链接此处


4
订单字段应该是 = 'author__name' 吗? - Yunti
2
这个完美运行,然而我不确定为什么。objBookAdmin 吗? - Kyias
哇,我在网上花了一个小时才找到这个。Django文档中应该更清晰地表述。 - Robert Johnstone
谢谢@Will。你知道吗,对于list_display,必须分配[...,'get_name'],但对于search_field,它不起作用,而是必须分配[...,'author__name']?这对我来说似乎是违反直觉的,不是吗? - Daniel Lema

74
请注意,添加get_author函数将会减慢管理员中的list_display速度,因为显示每个人都会进行一次SQL查询。
为了避免这种情况,您需要修改PersonAdmin中的get_queryset方法,例如:
def get_queryset(self, request):
    return super(PersonAdmin,self).get_queryset(request).select_related('book')

之前:73个查询,用时36.02毫秒(其中67个查询在管理界面中重复)

之后:6个查询,用时10.81毫秒


10
这非常重要,应该始终加以实施。 - xleon
5
确实很重要。或者,如果选择使用__str__方法,只需将外键添加到list_displaylist_select_related中即可。 - Scratch'N'Purr
2
"list_select_related" 是标题问题的最佳解决方案。 - Nishi

74

像其他人一样,我也选择使用可调用对象。但是它们有一个缺点:默认情况下,您不能按照它们进行排序。幸运的是,这个问题有解决方案:

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'

1
方法签名应该是 def author(self, obj): - sheats
2
当我发表评论时,情况并非如此,但似乎自1.8版本以来,该方法会传递对象。我已经更新了我的答案。 - Arjen
取决于您是在管理员还是模型上定义它,嗯。 - keyvanm

39

针对 Django >= 3.2

在 Django 3.2 或更高版本中正确的做法是使用 display 装饰器

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

    @admin.display(description='Author Name', ordering='author__name')
    def get_author_name(self, obj):
        return obj.author.name

24

11
@Mermoz 真的吗?看起来这个问题的状态仍然是“不修复”。而且它似乎也没有解决(在Django 1.3中)。 - Dave
1.11仍不存在。我已经使用Django十多年了,但我从未记得过这个版本 :( - Aaron McMillin

13

我刚刚发布了一个代码片段,可以让admin.ModelAdmin支持'__'语法:

http://djangosnippets.org/snippets/2887/

这样你就可以做到:

class PersonAdmin(RelatedFieldAdmin):
    list_display = ['book__author',]

这基本上只是在做其他答案中描述的同样的事情,但它会自动处理(1)设置admin_order_field (2)设置short_description和(3)修改查询集以避免为每一行进行数据库查询。


我非常喜欢这个想法,但它似乎在最近的Django版本中不再起作用:AttributeError: type object 'BaseModel' has no attribute '__metaclass__' - Vincent van Leeuwen

12

在PyPI上有一个非常易于使用的程序包可以确切地处理这个问题:django-related-admin。您也可以在GitHub上查看代码

使用它,您想要实现的目标就像这样简单:

class PersonAdmin(RelatedFieldAdmin):
    list_display = ['book__author',]

两个链接都包含安装和使用的完整细节,所以我不会在这里粘贴它们,以防它们改变。

另外一点需要注意的是,如果你已经在使用model.Admin以外的其他东西(例如我之前在使用SimpleHistoryAdmin),你可以这样做:class MyAdmin(SimpleHistoryAdmin, RelatedFieldAdmin)


getter_for_related_field在1.9版本中无法使用,因此对于喜欢自定义的人来说似乎不是最佳选择。 - GriMel
1
这个库是最新的,在 Django 3.2 上运行良好,非常适合我们使用。 - Kevin Parker

11

通过使用可调用函数,您可以在列表显示中显示任何想要的内容。代码如下:

def book_author(object):
  return object.book.author

class PersonAdmin(admin.ModelAdmin):
  list_display = [book_author,]

这个适用于很多不同的模型经常调用相同属性的情况,它在1.3+中被支持吗? - kagali-san
3
问题在于最终执行的 SQL 查询数量。对于列表中的每个对象,都会执行一个查询。这就是为什么 'field__attribute' 很方便,因为 Django 肯定会将其扩展为一个 SQL 查询。奇怪的是还没有支持它。 - emyller

7

这个问题已经得到解决,但如果还有其他像我一样的“蠢蛋”没有立即从目前被接受的答案中理解,这里有更详细的说明。

ForeignKey引用的模型类需要在其中拥有一个__unicode__方法,就像这里:

class Category(models.Model):
    name = models.CharField(max_length=50)

    def __unicode__(self):
        return self.name

这对我很有帮助,同样适用于上述情况。这适用于Django 1.0.2。


4
在Python 3中,这将是 def __str__(self): - Martlark

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接