4
votes

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 :

  • Django 1.11
  • I want to display Book and Author as full readonly, so the foreign key of the Book will not be changed (at least not from the admin)
4
To give you a hint, I think you are going to need to specify the backref attribute of the relationship, so that inheritance is identified both ways. I'm afraid I'm not certain of the syntax, particularly with Django 1.11, but hopefully that gives you a clue. - Jack Parkinson
Yes, I use the "related_name" attribute so it can used from both side of the relationship :) - Thom

4 Answers

6
votes

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! :)

2
votes

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.

0
votes

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.

-1
votes

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.