【问题标题】:Display foreign key columns as link to detail object in Django admin在 Django admin 中将外键列显示为详细对象的链接
【发布时间】:2016-09-29 01:53:01
【问题描述】:

link-in-django-admin-to-foreign-key-object 中所述,可以将 ForeignKey 字段显示为指向管理员详细信息页面的链接。

总结一下,

class Foo(Model):
    bar = models.ForeignKey(Bar)

class FooAdmin(ModelAdmin):
    list_display = ('link_to_bar',)
    def link_to_bar(self, obj):
        link = urlresolvers.reverse('admin:app_bar_change', args=[obj.bar_id])
        return u'<a href="%s">%s</a>' % (link, obj.bar) if obj.bar else None
    link_to_bar.allow_tags = True

问题是:我们可以更自动地做到这一点吗?例如,向FooAdmin 定义提供一个外键列表,以显示为详细页面的链接:

class FooAdmin(ModelAdmin):
    ...
    list_foreign_key_links = ('bar',)
    ...

我知道这些ModelAdmin 类是通过元类编程生成的。那么,应该是可以的。这样做是一个好的开始?

【问题讨论】:

    标签: python django django-admin


    【解决方案1】:

    下面的解决方案使用this answer 但使其可被所有模型重用,避免了向每个管理类添加方法的需要。

    示例模型

    # models.py
    from django.db import models
    
    class Country(models.Model):
        name = models.CharField(max_length=200)
        population = models.IntegerField()
    
    class Career(models.Model):
        name = models.CharField(max_length=200)
        average_salary = models.IntegerField()
    
    class Person(models.Model):
        name = models.CharField(max_length=200)
        age = models.IntegerField()
        country = models.ForeignKey(Country, on_delete=models.CASCADE)
        career = models.ForeignKey(Career, on_delete=models.CASCADE)
    

    示例管理员

    # admin.py
    from django.utils.html import format_html
    from django.urls import reverse
    
    from .models import Person
    
    
    def linkify(field_name):
        """
        Converts a foreign key value into clickable links.
        
        If field_name is 'parent', link text will be str(obj.parent)
        Link will be admin url for the admin url for obj.parent.id:change
        """
        def _linkify(obj):
            linked_obj = getattr(obj, field_name)
            if linked_obj is None:
                return '-'
            app_label = linked_obj._meta.app_label
            model_name = linked_obj._meta.model_name
            view_name = f'admin:{app_label}_{model_name}_change'
            link_url = reverse(view_name, args=[linked_obj.pk])
            return format_html('<a href="{}">{}</a>', link_url, linked_obj)
    
        _linkify.short_description = field_name  # Sets column name
        return _linkify
    
    
    
    @admin.register(Person)
    class PersonAdmin(admin.ModelAdmin):
        list_display = [
            "name",
            "age",
            linkify(field_name="country"),
            linkify(field_name="career"),
        ]
    

    结果

    给定一个名为 app 的应用程序和一个 Person 实例 Person(name='Adam' age=20),其国家和职业外键值的 ID 为 123456, 列表结果将是:

    | Name | Age |                          Country                          |...|
    |------|-----|-----------------------------------------------------------|...|
    | Adam |  20 | <a href="/admin/app/country/123">Country object(123)</a>  |...|
    

    (继续)

    |...|                          Career                         |
    |---|---------------------------------------------------------|
    |...| <a href="/admin/app/career/456">Career object(456)</a>  |
    

    【讨论】:

    • 很棒的功能!我会将linked_obj.id 更改为linked_obj.pk 以处理自定义pks。
    • 超级有帮助!我会使用来自django.contrib.contenttypes.modelsc = ContentType.objects.get_for_model(obj) 而不是私人成员_meta。然后您可以从c.app_labelc.model 获取应用程序/模型名称。
    • 如果 linked_obj 为 null 则引发属性错误 - 处理可空字段添加 if not linked_obj: return '-'
    • readonly_fields = (linkify("example"),); exclude = ("example",) 可以在数据库对象的管理视图中创建只读字段。但是,由于某种原因,我无法通过覆盖 def get_readonly_fields(self): ... 来做类似的事情
    【解决方案2】:

    一个好的开始是查看BaseModelAdminModelAdmin 的来源。尝试找出 ModelAdmin 如何生成默认链接。 扩展ModelAdmin,添加生成任意外键链接的方法,看看ChangeList是如何生成变更列表的。

    我还建议您使用format_html 来呈现链接,这使得link_to_bar.allow_tags = True 变得不必要:

    from django.utils.html import format_html
    
    class FooAdmin(ModelAdmin):
        list_display = ('link_to_bar', )
        def link_to_bar(self, obj):
            link = urlresolvers.reverse('admin:app_bar_change', args=[obj.bar_id])
            return format_html('<a href="{}">{}</a>', link, obj.bar) if obj.bar else None
    

    【讨论】:

      【解决方案3】:

      对已接受的答案稍作修改。不一定更好,但实现了cmets中的一些建议:

      from django.contrib.contenttypes.models import ContentType
      from django.urls import reverse
      from django.utils.html import format_html
      
      
      def linkify(field_name):
          def _linkify(obj):
              content_type = ContentType.objects.get_for_model(obj)
              app_label = content_type.app_label
              linked_obj = getattr(obj, field_name)
              linked_content_type = ContentType.objects.get_for_model(linked_obj)
              model_name = linked_content_type.model
              view_name = f"admin:{app_label}_{model_name}_change"
              link_url = reverse(view_name, args=[linked_obj.pk])
              return format_html('<a href="{}">{}</a>', link_url, linked_obj)
      
          _linkify.short_description = field_name.replace("_", " ").capitalize()
          return _linkify
      

      【讨论】:

      • 我得到:'在模板/var/work/django/oscar/lib/python3.8/site-packages/grappelli/templates/admin/base.html,第0行错误'NoneType'对象没有属性“_meta”
      猜你喜欢
      • 2015-10-29
      • 2015-05-04
      • 2016-11-06
      • 2012-07-05
      • 2020-06-26
      • 2021-02-07
      • 1970-01-01
      • 2016-11-12
      • 1970-01-01
      相关资源
      最近更新 更多