Link in django admin to foreign key object
Asked Answered
R

6

45

I have a model A with a ForeignKey to a model B. In Django admin, how can I add a link in the admin page of model A next to the ForeignKey field which open the admin page of the model B ?

Roddie answered 3/3, 2015 at 13:21 Comment(0)
A
54

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.

Advise answered 3/3, 2015 at 14:16 Comment(3)
No longer necessary in Django 1.9+ - see the pencil icon next to the FK item.Emelyemelyne
I would only add link_to_B.short_description = 'B' will also change the title of the column to B instead of link to BWeitman
Is there a solution without having to list all the fields explicitly, i.e. list_display = [f.name for f in MyModel._meta.fields]? That is, is it possible to display all foreign keys as links?Schrock
F
60

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.

Featurelength answered 23/2, 2018 at 15:14 Comment(4)
Thanks for this answer. I have extended this concept but in a way that is reusable across models: https://mcmap.net/q/242840/-display-foreign-key-columns-as-link-to-detail-object-in-django-adminErmelindaermengarde
Note that auth used in the reverse link is the app name where the model lives. Generally speaking, it should be of the form admin:myapp_mymodel_changeHidrosis
Also note - for this to work, the linked model needs to also have a registered admin view - ie "admin.site.register(User)" - with User, that's true, but if you are using a custom model, reminder to also register it's view in admin or the reverse function won't work because it won't find app_model_changeTendance
Thanks @bethlakshmi, I've added a reminder to check the comments for your additions.Featurelength
A
54

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.

Advise answered 3/3, 2015 at 14:16 Comment(3)
No longer necessary in Django 1.9+ - see the pencil icon next to the FK item.Emelyemelyne
I would only add link_to_B.short_description = 'B' will also change the title of the column to B instead of link to BWeitman
Is there a solution without having to list all the fields explicitly, i.e. list_display = [f.name for f in MyModel._meta.fields]? That is, is it possible to display all foreign keys as links?Schrock
C
24

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',
    )
Coffeecolored answered 17/12, 2018 at 21:23 Comment(2)
You need to import mark_safe. ``` from django.utils.html import mark_safe ```Normalie
Definitively the correct answer. Just a note: can improve the query performance using 'args=[obj.model_id]' and avoiding a useless join in the SQL querySperling
C
4

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

Constructionist answered 24/8, 2020 at 7:16 Comment(0)
F
1

I created mixin, that does this + similar thing to many-to-many relations (there it shows count of related objects and links to changelist with apropriate filter). Based on gist I've forked from:

https://gist.github.com/hovi/2e3a216ecc4be685ec9e0d23b0eb7901

Tested on django 1.1.x and 1.0.x

Fecundity answered 30/10, 2019 at 15:7 Comment(0)
L
0

There is an easier solution today, with related being the foreign key field to be linked to:

class YourModelAdmin(model.modelAdmin):
    list_display = ["field_one", "field_two", "related"]
    list_display_links = ["field_one", "related"]
Lout answered 28/1, 2019 at 8:57 Comment(2)
list_display_links creates a link to main object. In your case both links will be the same. The question is to have 2 different links field_one - to parent object, related - to related objectCluj
This is an incorrect answerBellabelladonna

© 2022 - 2024 — McMap. All rights reserved.