Thom
Thom

Reputation: 1623

How show related object in Django admin from the model with a foreign key point of view?

I got two Django models linked by a Foreign key:

class Author(models.Model):
    ...

class Book(models.Model):
    author = models.ForeignKey(Author)

Please consider admin example below (I want to do the opposite):

from django.contrib import admin
from my_app.models import Author, Book

class BookInline(admin.TabularInline):
    model = Book

class AuthorAdmin(admin.ModelAdmin):
    inlines = [
        BookInline,
    ]

admin.site.register(Author, AuthorAdmin)

With this example, we can see in the admin all authors, and for each author, their books.

Now, I would like to to it the other way around. Have en entry per Book in the administration and display the Author informations (it can be readonly) on the Book details. I tried with the solution below, but obviously it didn't work:

from django.contrib import admin
from my_app.models import Author, Book

class AuthorInline(admin.TabularInline):
    model = Author

class BookAdmin(admin.ModelAdmin):
    inlines = [
        AuthorInline,
    ]

admin.site.register(Book, BookAdmin)

Django raised an error :

<class 'my_app.admin.AuthorInline'>: (admin.E202) 'my_app.Author' has no ForeignKey to 'my_app.Book'.

Do you know how to do that ?

More context :

Upvotes: 6

Views: 7037

Answers (4)

jnns
jnns

Reputation: 5634

There's ModelAdmin.readonly_fields that can take callables on both the ModelAdmin itself or the Model and show them on the add/change form. (docs)

If you wanted to show an author's name on the book change form in the admin, you would do it like this:

class BookAdmin(admin.ModelAdmin):
    readonly_fields = ['get_author_name']
    [...]

    def get_author_name(self, book):
        return book.author.name

All the readonly fields will be displayed beneath the form's regular fields. You can also customize the way fields are displayed by modifying ModelForm.get_fields().

If you do it like this, you save yourself the trouble of overwriting the templates.

Upvotes: 4

Thom
Thom

Reputation: 1623

Here my solution, maybe not the best, but it works! :)

The idea is to change the template and therefor be able to display it the way I wanted.

admin.py :

class BookAdmin(admin.ModelAdmin):
    ...
    change_form_template = 'admin/book_details.html'

    def change_view(self, request, object_id, form_url='', extra_context=None):
        extra_context = extra_context or {}
        extra_context['book'] = Book.objects.get(id=object_id)
        extra_context['author'] = extra_context['book'].author

        return super().change_view(request, object_id, form_url, extra_context=extra_context)

admin.site.register(Book, BookAdmin)

For the template I just copy/past the template change_form.html from the admin.

admin/book_details.html:

{% extends "admin/base_site.html" %}
{% load i18n admin_urls static admin_modify %}


{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}" />{% endblock %}

{% block coltype %}colM{% endblock %}

{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
&rsaquo; {% if has_change_permission %}<a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %}
&rsaquo; {% if add %}{% blocktrans with name=opts.verbose_name %}Add {{ name }}{% endblocktrans %}{% else %}{{ original|truncatewords:"18" }}{% endif %}
</div>
{% endblock %}


{% block content %}

# Display here want you want !

{% endblock %}

I took a look at Django admin's template to be able to display my Book and Author the same way as if they were displayed by the default template. So my user won't be disrupted by this view.

If you found a better way, please let me know! :)

Upvotes: 7

Daniel Roseman
Daniel Roseman

Reputation: 599450

You can't do this. Inlines only make sense in one direction.

The way to do this is to define the display of the author field so that it gives you sufficient information automatically; for example, by defining the __str__ method.

Upvotes: 0

Dhruv Aggarwal
Dhruv Aggarwal

Reputation: 177

As the error says:

class 'my_app.admin.AuthorInline'>: (admin.E202) 'my_app.Author' has no ForeignKey to 'my_app.Book'.

You need to add the foreign key in Author table that references Book table.

The first one worked because you would have a foreign key in Book that references Author.

Upvotes: -1

Related Questions