Link in django admin to foreign key object
You can do the following:
models.py (example):
model B(models.Model):
name = models.CharField(max_length=20)
model A(models.Model):
field1 = models.CharField(max_length=20)
Bkey = models.ForeignKey(B)
admin.py
from django.core import urlresolvers
class AAdmin(admin.ModelAdmin):
list_display = ["field1","link_to_B"]
def link_to_B(self, obj):
link=urlresolvers.reverse("admin:yourapp_b_change", args=[obj.B.id]) #model name has to be lowercase
return u'<a href="%s">%s</a>' % (link,obj.B.name)
link_to_B.allow_tags=True
Replace yourapp with the name of your app.
In addition of the accepted answer, in newer versions of Django, the reverse method is now in the package django.urls (cf. this link).
Moreover, you should use the format_html function to output HTML in the admin. Then the allow_tags becomes useless.
Finally, to add a link to the edit page of a user, I have this function in admin.py
:
from django.urls import reverse
from django.utils.html import format_html
class ObjectAdmin(admin.ModelAdmin):
list_display = ('name', 'link_to_user')
def link_to_user(self, obj):
link = reverse("admin:auth_user_change", args=[obj.user_id])
return format_html('<a href="{}">Edit {}</a>', link, obj.user.username)
link_to_user.short_description = 'Edit user'
Don't forget to check the comments, there are a few considerations to take into account.
If you need to
- have a href link to FK page on detail page (not on the list page)
- speed up loading time by preventing loading choices/options to FK widget
Code for Django 3.2 for classes FKLinkWidget and CustomModelAdmin
[code below tested on Django 1.8 with Python 3]
Step 1: define base admin helpers
class FKLinkWidget(forms.TextInput):
"""Widget to show html link for FK field instead of default option field"""
NO_VALUE_TEXT = 'None'
def __init__(self, attrs=None):
self.app_label = None
self.model_name = None
self.pk = None
self.repr = None
super().__init__(attrs)
def set_obj(self, obj):
self.app_label = obj._meta.app_label
self.model_name = obj._meta.model_name
self.pk = obj.pk
self.repr = str(obj)
def render(self, name, value, attrs=None):
if self.pk:
view_name = f"admin:{self.app_label}_{self.model_name}_change"
link_url = reverse(view_name, args=[self.pk])
return format_html('<a href="{}" target="_blank">{}</a>', link_url, self.repr)
else:
return format_html(value or self.NO_VALUE_TEXT)
class CustomModelAdmin(admin.ModelAdmin):
"""extendable ModelAdmin which provides several custom attributes
- fk_links = list of FK fields that should be shown as read-only links on detail page
this can prevent loading all choice options by django admin, which results 504 http error
"""
fk_links = []
def __init__(self, model, admin_site):
super().__init__(model, admin_site)
intersect = set(self.fk_links).intersection(self.readonly_fields + self.raw_id_fields)
if intersect:
raise ValueError(f'CustomModelAdmin fields: {intersect} are in readonly or raw_id fields')
def get_form(self, request, obj=None, **kwargs):
self.obj = obj
form = super().get_form(request, obj, **kwargs)
return form
def formfield_for_dbfield(self, db_field, **kwargs):
if db_field.name in self.fk_links:
kwargs['widget'] = FKLinkWidget
formfield = super().formfield_for_dbfield(db_field, **kwargs)
if db_field.name in self.fk_links:
# we disable any actions for that field
if self.obj:
fk = getattr(self.obj, db_field.name)
if fk:
formfield.widget.widget.set_obj(fk)
formfield.widget.can_add_related = False
formfield.widget.can_change_related = False
formfield.widget.can_delete_related = False
return formfield
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
if db_field.name in self.fk_links:
kwargs["queryset"] = db_field.rel.to._default_manager.none()
kwargs["required"] = False
return super().formfield_for_foreignkey(db_field, request, **kwargs)
Step 2: use CustomModelAdmin as base class for your admin model
@admin.register(UserProfile)
class UserProfileAdmin(CustomModelAdmin):
fk_links = ['user',]
Step 3: result it will looks like
Django 2.0+ and Python 3.5+:
from django.urls import reverse
from django.utils.html import escape, mark_safe
@admin.register(models.YourModel)
class YourModelAdmin(BaseModelAdmin):
def model_str(self, obj: models.YourModel):
link = reverse("admin:module_model_change", args=[obj.model_id])
return mark_safe(f'<a href="{link}">{escape(obj.model.__str__())}</a>')
model_str.short_description = 'Model'
model_str.admin_order_field = 'model' # Make row sortable
list_display = (
'model_str',
)