As explained in link-in-django-admin-to-foreign-key-object, one can display a ForeignKey field as a link to the admin detail page.
To summarize,
class Fo
A good start would be looking at the source of BaseModelAdmin and ModelAdmin. Try to find out how the ModelAdmin generates the default links.
Extend ModelAdmin
, add a method to generate links to arbitrary foreign keys and look at how ChangeList generates the change list.
I would also suggest you use format_html to render the links, which makes link_to_bar.allow_tags = True
unnecessary:
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
A slight respin on the accepted answer. It is not necessarily better, but implements some of the advice in the comments:
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
The solution below uses this answer but makes it reusable by all models, avoiding the need to add methods to each admin class.
# 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"),
]
Given an App named app
, and a Person instance Person(name='Adam' age=20)
with country and carreer foreign key values with ids 123
and 456
,
the list result will be:
| Name | Age | Country |...|
|------|-----|-----------------------------------------------------------|...|
| Adam | 20 | <a href="/admin/app/country/123">Country object(123)</a> |...|
(Continues)
|...| Career |
|---|---------------------------------------------------------|
|...| <a href="/admin/app/career/456">Career object(456)</a> |