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

enter image description here


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',
    )