til: django_extra-read-only-admin-information.md
This data as json
| path | topic | title | url | body | html | shot | created | created_utc | updated | updated_utc | shot_hash | slug |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| django_extra-read-only-admin-information.md | django | Adding extra read-only information to a Django admin change page | https://github.com/simonw/til/blob/main/django/extra-read-only-admin-information.md | I figured out this pattern today for adding templated extra blocks of information to the Django admin change page for an object. It's really simply and incredibly useful. I can see myself using this a lot in the future. ```python from django.contrib import admin from django.template.loader import render_to_string from django.utils.safestring import mark_safe from .models import Reporter @admin.register(Reporter) class ReporterAdmin(admin.ModelAdmin): # ... readonly_fields = ("recent_calls",) def recent_calls(self, instance): return mark_safe( render_to_string( "admin/_reporter_recent_calls.html", { "reporter": instance, "recent_calls": instance.call_reports.order_by("-created_at")[:20], "call_count": instance.call_reports.count(), }, ) ) ``` That's it! `recent_calls` is marked as a read-only field, then implemented as a method which returns HTML. That method passes the instance to a template using `render_to_string`. That template looks like this: ```html+jinja <h2>{{ reporter }} has made {{ call_count }} call{{ call_count|pluralize }}</h2> <p><strong>Recent calls</strong> (<a href="/admin/core/callreport/?reported_by__exact={{ reporter.id }}">view all</a>)</p> {% for call in recent_calls %} <p><a href="/admin/core/location/{{ call.location.id }}/change/">{{ call.location }}</a> on {{ call.created_at }}</p> {% endfor %} ``` | <p>I figured out this pattern today for adding templated extra blocks of information to the Django admin change page for an object.</p> <p>It's really simply and incredibly useful. I can see myself using this a lot in the future.</p> <div class="highlight highlight-source-python"><pre><span class="pl-k">from</span> <span class="pl-s1">django</span>.<span class="pl-s1">contrib</span> <span class="pl-k">import</span> <span class="pl-s1">admin</span> <span class="pl-k">from</span> <span class="pl-s1">django</span>.<span class="pl-s1">template</span>.<span class="pl-s1">loader</span> <span class="pl-k">import</span> <span class="pl-s1">render_to_string</span> <span class="pl-k">from</span> <span class="pl-s1">django</span>.<span class="pl-s1">utils</span>.<span class="pl-s1">safestring</span> <span class="pl-k">import</span> <span class="pl-s1">mark_safe</span> <span class="pl-k">from</span> .<span class="pl-s1">models</span> <span class="pl-k">import</span> <span class="pl-v">Reporter</span> <span class="pl-en">@<span class="pl-s1">admin</span>.<span class="pl-s1">register</span>(<span class="pl-v">Reporter</span>)</span> <span class="pl-k">class</span> <span class="pl-v">ReporterAdmin</span>(<span class="pl-s1">admin</span>.<span class="pl-v">ModelAdmin</span>): <span class="pl-c"># ...</span> <span class="pl-s1">readonly_fields</span> <span class="pl-c1">=</span> (<span class="pl-s">"recent_calls"</span>,) <span class="pl-k">def</span> <span class="pl-en">recent_calls</span>(<span class="pl-s1">self</span>, <span class="pl-s1">instance</span>): <span class="pl-k">return</span> <span class="pl-en">mark_safe</span>( <span class="pl-en">render_to_string</span>( <span class="pl-s">"admin/_reporter_recent_calls.html"</span>, { <span class="pl-s">"reporter"</span>: <span class="pl-s1">instance</span>, <span class="pl-s">"recent_calls"</span>: <span class="pl-s1">instance</span>.<span class="pl-s1">call_reports</span>.<span class="pl-en">order_by</span>(<span class="pl-s">"-created_at"</span>)[:<span class="pl-c1">20</span>], <span class="pl-s">"call_count"</span>: <span class="pl-s1">instance</span>.<span class="pl-s1">call_reports</span>.<span class="pl-en">count</span>(), }, ) )</pre></div> <p>That's it! <code>recent_calls</code> is marked as a read-only field, then implemented as a method which returns HTML. That method passes the instance to a template using <code>render_to_string</code>. That template looks like this:</p> <div class="highlight highlight-text-html-django"><pre><<span class="pl-ent">h2</span>>{{ reporter }} has made {{ call_count }} call{{ call_count|pluralize }}</<span class="pl-ent">h2</span>> <<span class="pl-ent">p</span>><<span class="pl-ent">strong</span>>Recent calls</<span class="pl-ent">strong</span>> (<<span class="pl-ent">a</span> <span class="pl-e">href</span>=<span class="pl-s"><span class="pl-pds">"</span>/admin/core/callreport/?reported_by__exact={{ reporter.id }}<span class="pl-pds">"</span></span>>view all</<span class="pl-ent">a</span>>)</<span class="pl-ent">p</span>> <span class="pl-e">{%</span> <span class="pl-k">for</span> <span class="pl-s">call</span> <span class="pl-k">in</span> <span class="pl-s">recent_calls</span> <span class="pl-e">%}</span> <<span class="pl-ent">p</span>><<span class="pl-ent">a</span> <span class="pl-e">href</span>=<span class="pl-s"><span class="pl-pds">"</span>/admin/core/location/{{ call.location.id }}/change/<span class="pl-pds">"</span></span>>{{ call.location }}</<span class="pl-ent">a</span>> on {{ call.created_at }}</<span class="pl-ent">p</span>> <span class="pl-e">{%</span> <span class="pl-k">endfor</span> <span class="pl-e">%}</span></pre></div> | <Binary: 54,400 bytes> | 2021-02-25T17:49:17-08:00 | 2021-02-26T01:49:17+00:00 | 2021-02-27T12:34:46-08:00 | 2021-02-27T20:34:46+00:00 | 4f6dce09e12d1b504c5bcac65757c888 | extra-read-only-admin-information |