【问题标题】:Can "list_display" in a Django ModelAdmin display attributes of ForeignKey fields?Django ModelAdmin 中的“list_display”可以显示 ForeignKey 字段的属性吗?
【发布时间】:2010-09-14 21:31:45
【问题描述】:

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

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

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

我已经尝试了所有显而易见的方法,但似乎没有任何效果。

有什么建议吗?

【问题讨论】:

    标签: python django django-models django-admin modeladmin


    【解决方案1】:

    我可能会迟到,但这是另一种方法。您可以简单地在模型中定义一个方法并通过list_display 访问它,如下所示:

    models.py

    class Person(models.Model):
        book = models.ForeignKey(Book, on_delete=models.CASCADE)
    
        def get_book_author(self):
            return self.book.author
    

    admin.py

    class PersonAdmin(admin.ModelAdmin):
        list_display = ('get_book_author',)
    

    但是这种方法和上面提到的其他方法会在列表视图页面的每行中添加两个额外的查询。为了优化这一点,我们可以覆盖get_queryset 来注释必填字段,然后在我们的 ModelAdmin 方法中使用注释字段

    admin.py

    from django.db.models.expressions import F
    
    @admin.register(models.Person)
    class PersonAdmin(admin.ModelAdmin):
        list_display = ('get_author',)
        def get_queryset(self, request):
            queryset = super().get_queryset(request)
            queryset = queryset.annotate(
                _author = F('book__author')
            )
            return queryset
    
        @admin.display(ordering='_author', description='Author')
        def get_author(self, obj):
            return obj._author
    

    【讨论】:

      【解决方案2】:

      作为另一种选择,您可以进行如下查找:

      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() 装饰器:

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

      【讨论】:

      • @AnatoliyArkhipov,有一种方法(基于Terr answer)。我已经更新了这个答案中的代码。
      • 这会导致管理中显示的每行一个查询:(
      • @marcelm 这就是select_related 的用途。 UserAdminget_queryset() 必须被覆盖。
      • 对于 Django Version > 3.2,请参考这个答案:stackoverflow.com/a/67746847/11605100
      • 显示装饰器定义为@admin.display(....)
      【解决方案3】:

      对于 Django >= 3.2

      使用 Django 3.2 或更高版本的正确方法是使用 display decorator

      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
      

      【讨论】:

        【解决方案4】:

        和其他人一样,我也使用了可调用对象。但是它们有一个缺点:默认情况下,您不能订购它们。幸运的是,有一个解决方案:

        Django >= 1.8

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

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

        【讨论】:

        • 方法签名应该是def author(self, obj):
        • 返回当我发表评论时,情况并非如此,但似乎从 1.8 版开始,该方法将对象传递给它。我已经更新了我的答案。
        【解决方案5】:

        我更喜欢这个:

        class CoolAdmin(admin.ModelAdmin):
            list_display = ('pk', 'submodel__field')
        
            @staticmethod
            def submodel__field(obj):
                return obj.submodel.field
        

        【讨论】:

          【解决方案6】:

          尽管上面给出了所有很棒的答案,而且由于我是 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模型链接here

          【讨论】:

          • 对于订单字段不应该是 = 'author__name' 吗?
          • 这非常有效,但我不确定为什么。 objBookAdmin?
          • 哇。我在网上花了一个小时才找到这个。这应该在 Django 文档中更清楚地说明
          • 谢谢@Will。您是否意识到对于 list_display,必须分配 [..., 'get_name', ] 但对于 search_field,它不起作用,而是必须分配 [..., 'author__name', ]?这对我来说似乎违反直觉,不是吗?
          【解决方案7】:

          在 PyPI 中有一个非常易于使用的包可以处理这个问题:django-related-admin。你也可以see the code in GitHub

          使用这个,你想要实现的很简单:

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

          两个链接都包含安装和使用的完整详细信息,因此我不会将它们粘贴在这里以防它们发生变化。

          顺便说一句,如果您已经在使用 model.Admin 以外的其他内容(例如,我使用的是 SimpleHistoryAdmin),您可以这样做:class MyAdmin(SimpleHistoryAdmin, RelatedFieldAdmin)

          【讨论】:

          • getter_for_related_field 在 1.9 中不起作用,所以对于喜欢自定义的人来说,这似乎不是最佳选择。
          • 这个库是最新的,在 Django 3.2 上非常适合我们
          【解决方案8】:

          这个已经被接受了,但是如果有其他的假人(比如我)没有立即从presently accepted answer 得到它,这里有更多的细节。

          ForeignKey 引用的模型类需要在其中有一个__unicode__ 方法,如下所示:

          class Category(models.Model):
              name = models.CharField(max_length=50)
          
              def __unicode__(self):
                  return self.name
          

          这对我来说很重要,应该适用于上述情况。这适用于 Django 1.0.2。

          【讨论】:

          • 在 python 3 上,这将是 def __str__(self):
          【解决方案9】:

          如果您在list_display 中有很多要使用的关系属性字段,并且不想为每个字段创建一个函数(及其属性),那么一个肮脏但简单的解决方案是覆盖ModelAdmin 实例__getattr__方法,动态创建可调用对象:

          class DynamicLookupMixin(object):
              '''
              a mixin to add dynamic callable attributes like 'book__author' which
              return a function that return the instance.book.author value
              '''
          
              def __getattr__(self, attr):
                  if ('__' in attr
                      and not attr.startswith('_')
                      and not attr.endswith('_boolean')
                      and not attr.endswith('_short_description')):
          
                      def dyn_lookup(instance):
                          # traverse all __ lookups
                          return reduce(lambda parent, child: getattr(parent, child),
                                        attr.split('__'),
                                        instance)
          
                      # get admin_order_field, boolean and short_description
                      dyn_lookup.admin_order_field = attr
                      dyn_lookup.boolean = getattr(self, '{}_boolean'.format(attr), False)
                      dyn_lookup.short_description = getattr(
                          self, '{}_short_description'.format(attr),
                          attr.replace('_', ' ').capitalize())
          
                      return dyn_lookup
          
                  # not dynamic lookup, default behaviour
                  return self.__getattribute__(attr)
          
          
          # use examples    
          
          @admin.register(models.Person)
          class PersonAdmin(admin.ModelAdmin, DynamicLookupMixin):
              list_display = ['book__author', 'book__publisher__name',
                              'book__publisher__country']
          
              # custom short description
              book__publisher__country_short_description = 'Publisher Country'
          
          
          @admin.register(models.Product)
          class ProductAdmin(admin.ModelAdmin, DynamicLookupMixin):
              list_display = ('name', 'category__is_new')
          
              # to show as boolean field
              category__is_new_boolean = True
          

          作为gist here

          booleanshort_description 等可调用的特殊属性必须定义为ModelAdmin 属性,例如book__author_verbose_name = 'Author name'category__is_new_boolean = True

          可调用的admin_order_field 属性是自动定义的。

          不要忘记在 ModelAdmin 中使用 list_select_related 属性来让 Django 避免额外的查询。

          【讨论】:

          • 刚刚在 Django 2.2 安装中尝试过,它对我来说效果很好,而其他方法却没有,无论出于何种原因。请注意,现在您需要从 functools 或其他地方导入 reduce...
          【解决方案10】:

          请注意,添加 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 个重复查询)

          之后:10.81 毫秒内 6 次查询

          【讨论】:

          • 这非常重要,应该始终执行
          • 这确实很重要。或者,如果要走__str__ 路线,只需将外键添加到list_displaylist_select_related
          • "list_select_related" 是标题问题的最佳解决方案
          【解决方案11】:

          如果你在 Inline 中尝试,你不会成功,除非:

          在你的内联中:

          class AddInline(admin.TabularInline):
              readonly_fields = ['localname',]
              model = MyModel
              fields = ('localname',)
          

          在您的模型(MyModel)中:

          class MyModel(models.Model):
              localization = models.ForeignKey(Localizations)
          
              def localname(self):
                  return self.localization.name
          

          【讨论】:

            【解决方案12】:

            我刚刚发布了一个让 admin.ModelAdmin 支持 '__' 语法的 sn-p:

            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__'
            【解决方案13】:

            根据文档,您只能显示 ForeignKey 的 __unicode__ 表示:

            http://docs.djangoproject.com/en/dev/ref/contrib/admin/#list-display

            它不支持在 DB API 的其他地方使用的'book__author' 样式格式似乎很奇怪。

            原来有a ticket for this feature,它被标记为Won't Fix。

            【讨论】:

            • @Mermoz 真的吗?看来票仍然设置为 wontfix。它似乎也不起作用(Django 1.3)
            • 1.11 仍然不存在。做 django 已经十几年了,我从来不记得这个 :(
            【解决方案14】:

            AlexRobbins 的回答对我有用,除了前两行需要在模型中(也许这是假设的?),并且应该引用 self:

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

            然后管理部分工作得很好。

            【讨论】:

              【解决方案15】:

              您可以使用可调用对象在列表显示中显示您想要的任何内容。它看起来像这样:

              def book_author(对象): 返回 object.book.author 类 PersonAdmin(admin.ModelAdmin): list_display = [book_author,]

              【讨论】:

              • 这适用于很多不同模型经常调用相同属性的情况; 1.3+ 支持吗?
              • 这方面的问题是最终完成的 SQL 查询量。对于列表中的每个对象,它都会进行查询。这就是为什么 'field__attribute' 会非常方便的原因,因为 Django 肯定会将其扩展到一个 SQL 查询。奇怪的是已经没有支持了。
              猜你喜欢
              • 2015-03-12
              • 2018-12-03
              • 1970-01-01
              • 2013-04-13
              • 2020-09-12
              • 2020-06-11
              • 2020-12-31
              • 2020-01-03
              • 1970-01-01
              相关资源
              最近更新 更多