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 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
The question is: can we do it more automatically? For instance, provide to the FooAdmin
definition a list of foreign key to display as links to detail page:
class FooAdmin(ModelAdmin):
...
list_foreign_key_links = ('bar',)
...
I know that these ModelAdmin
classes are generated with metaclass programming. Then, it should be possible. What would be a good start to do so?
Create a (default) object of the foreign model. It will automatically have id=1 (if no object existed yet). Change your foreignkey field by replacing null=True, blank=True by default=1 to associate that new object you created to all existing rows.
ForeignKey is a Django ORM field-to-column mapping for creating and working with relationships between tables in relational databases. ForeignKey is defined within the django. db. models.
Your intermediate model must contain one - and only one - foreign key to the source model (this would be Group in our example), or you must explicitly specify the foreign keys Django should use for the relationship using ManyToManyField.
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> |
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With