guettli
guettli

Reputation: 27806

Django Admin: Add Hyperlink to related model

I would like to add a hyperlink to the related model Training

django-admin-add-hyperlink-to-related-model

It would be nice to have declarative solution, since I want to use this at several places.

The "pencil" icon opens the related model in a popup window. That's not what I want. I want a plain hyperlink to the related model.

BTW, if you use "raw_id_fields", then the result is exactly what I was looking for: There is a hyperlink to the corresponding admin interface of this ForeignKey.

Upvotes: 6

Views: 1580

Answers (2)

JPG
JPG

Reputation: 88459

Update Jan 4, 2023

From Django 4.1, this becomes a part of the official build (related PR).

Related widget wrappers now have a link to object’s change form

Result

output


Previous Answer

The class named RelatedFieldWidgetWrapper is showing the icons on the Django Admin page and thus you need to override the same. So, create a custom class as below,

from django.contrib.admin.widgets import RelatedFieldWidgetWrapper


class CustomRelatedFieldWidgetWrapper(RelatedFieldWidgetWrapper):
    template_name = 'admin/widgets/custom_related_widget_wrapper.html'

    @classmethod
    def create_from_root(cls, root_widget: RelatedFieldWidgetWrapper):
        # You don't need this method of you are using the MonkeyPatch method
        set_attr_fields = [
            "widget", "rel", "admin_site", "can_add_related", "can_change_related",
            "can_delete_related", "can_view_related"
        ]
        init_args = {field: getattr(root_widget, field) for field in set_attr_fields}
        return CustomRelatedFieldWidgetWrapper(**init_args)

    def get_context(self, name, value, attrs):
        context = super().get_context(name, value, attrs)
        rel_opts = self.rel.model._meta
        info = (rel_opts.app_label, rel_opts.model_name)
        context['list_related_url'] = self.get_related_url(info, 'changelist')
        return context

See, the context variable list_related_url is the relative path that we need here. Now, create an HTML file to render the output,

#File: any_registered_appname/templates/admin/widgets/custom_related_widget_wrapper.html

{% extends "admin/widgets/related_widget_wrapper.html" %}
{% block links %}
    {{ block.super }}
    <a href="{{list_related_url}}">- Link To Related Model -</a>
{% endblock %}

How to connect?

Method-1 : Monkey Patch

# admin.py

# other imports
from ..widgets import CustomRelatedFieldWidgetWrapper
from django.contrib.admin import widgets

widgets.RelatedFieldWidgetWrapper = CustomRelatedFieldWidgetWrapper # monket patch

Method-2 : Override ModelAdmin

# admin.py
class AlbumAdmin(admin.ModelAdmin):
    hyperlink_fields = ["related_field_1"]

    def formfield_for_dbfield(self, db_field, request, **kwargs):
        formfield = super().formfield_for_dbfield(db_field, request, **kwargs)
        if db_field.name in self.hyperlink_fields:
            formfield.widget = CustomRelatedFieldWidgetWrapper.create_from_root(
                formfield.widget
            )
        return formfield

Result

Result Screenshot

Upvotes: 8

Marco
Marco

Reputation: 2834

There are several ways to go. Here is one.

Add some javascript that changes the existing link behavior. Add the following script at the end of the overridden admin template admin/widgets/related_widget_wrapper.html. It removes the class which triggers the modal and changes the link to the object.

It will only be triggered for id_company field. Change to your needs.

{% block javascript %}
<script>
'use strict';
{
    const $ = django.jQuery;

    function changeEditButton() {
        const edit_btn = document.getElementById('change_id_company');
        const value = edit_btn.previousElementSibling.value;
        const split_link_template = edit_btn.getAttribute('data-href-template').split('?');
        edit_btn.classList.remove('related-widget-wrapper-link');
        edit_btn.setAttribute('href', split_link_template[0].replace('__fk__', value));
    };

    $(document).ready(function() {
        changeEditButton();
        $('body').on('change', '#id_company', function(e) {
            changeEditButton();
        });
    });
}
</script>
{% endblock %}

This code can also be modified to be triggered for all edit buttons and not only for the company edit button.

Upvotes: 1

Related Questions