How to display foreign key values to listview page ?

April 2020

In this tutorial we will learn how to customize the listview page of a specific model to show the values of a foreign key.

You can find the source code in this repository

By default the Django admin will display the fields specified in the instruction list_display of your model admin

class ShopAdmin(admin.ModelAdmin):
    inlines = [shopplanning_inline]
    fieldsets = [
        (None, {'fields': ['user','name','email','address']}),
    ]
    list_display = ('user','name', 'email',  )
    ordering = ('user',)

We have implemented in this tutorial a planning for our Shop model. It should be nice to display the planning information at the same time that we display our list of existing shops. The rendering will look like this

So let’s do this. Since Django 2.1 we can override more Django admin templates. For our use case, the template we want to override is the change_list_results.html. (Find more information about here).

Since we want this specific behaviour only for our Shop model, we need to create a directory in backoffice/templates/admin/backoffice/shop directory (backoffice is our app name).

Then inside this directory we will copy the default Django admin change_list_results.html file

cp ../../../environnements/genericenv/lib/python3.7/site-packages/django/contrib/admin/templates/admin/change_list_results.html backoffice/templates/admin/backoffice/shop/

Please adapt the path with your own path

If we edit the file, we can see items displayed in the listview are build in a for loop which generates a simple html <tr><td> elements of an html table

{% for result in results %}
{% if result.form and result.form.non_field_errors %}
    <tr><td colspan="{{ result|length }}">{{ result.form.non_field_errors }}</td></tr>
{% endif %}
<tr class="{% cycle 'row1' 'row2' %}" id=" ">
    {% for item in result %}
    {{ item }}
    {% endfor %}
</tr>
{% endfor %}

So we will need to implement our custom code within this loop.

<tr class="{% cycle 'row1' 'row2' %}" id=" ">
    {% for item in result %}
    {{ item }}
    {% endfor %}
    <tr><td></td><td></td><td></td>
        <td> <table border>
            {% with i=forloop.counter0|stringformat:'s'|add:':' %}
            {% with items=cl.result_list|slice:i %}
                {% displayPlanning refShop %}
            {% endwith  %}
            {% endwith %}
          </table>
        </td>
    </tr>
</tr>

We add a new line in our html table with a <tr> instruction composed of <td>elements and one of them, will contain a new html table with our planning results.

To display our planning, we need to get the shop reference (the primary key). To do that we use some template tags instruction

{% with i=forloop.counter0|stringformat:'s'|add:':' %}
{% with items=cl.result_list|slice:i %}

And then we need to create our planning HTML instruction. To do that, we will create a custom template tags and then use it

{% displayPlanning items.0.pk %}

To create a custom template tags, just create a new python directory named templatetags inside your app folder.

from django import template
from backoffice.models import *
from django.utils.safestring import mark_safe
import uuid

register = template.Library()


@register.simple_tag
def displayPlanning(refShop):
    plannings = ShopPlanning.objects.filter(refShop=refShop).select_related("refShop")
    fullHtml=""
    for planning in plannings:

        html="<tr><td>"+planning.get_dayName()+"</td><td>"+str(planning.startHour)+"</td><td>"+str(planning.endHour)+"</td></tr>"
        fullHtml+=html
    return mark_safe(fullHtml)

In our customtags.py file, we will register our new tag displayPlanning, which will get the planning for the refShop passed as argument, and then build our HTML table.

We need not forget to load this customtags library inside our change_list_results.html file

{% load customtags %}

Christophe Surbier