Adding Custom Views and Templates to Django Admin

The Django admin feature is great for quickly spinning up basic CRUD actions without giving someone direct database access, but gives you very little control over the feature set or content. This is perfect for quickly spinning up the ability to create or update rows in the database, but sometimes you need just a little more.

In my case, I was creating a template for a PDF that I wanted to be preview-able directly from the admin page where the template was created, so people could see what the result would be of the object they’d just created. Django admin has a view_on_site feature that I considered using, but it uses the model’s get_absolute_url method, which would mean redirecting the user off of the admin site entirely to the actual application — I didn’t want my admin users to possibly have to reauthenticate on the main site when they’d already authenticated on the admin site.

I really just needed a single link with a view that would show a PDF for me, but even something that basic is not immediately obvious in Django admin. Let’s take a look at how to do it! As always, here are the docs for Django admin — this post assumes you’ve already got a basic Django admin site set up and are just looking to add a bit of customization to it.

Your admin site has a get_urls method that doesn’t need overwriting if you just want a page for each admin page you’ve registered. But adding our own custom view to preview our template is a great example of where we’ll need to add to this:

from django.conf.urls import url
from django.contrib import adminclass 

CustomAdminSite(admin.AdminSite):      
    def get_urls(self):        
        urls = super(CustomAdminSite, self).get_urls() 
        custom_urls = [            
            url(r'desired/path$', 
            self.admin_view(organization_admin.preview), name="preview"),        
        ]        
        return urls + custom_urls

admin_view is explained in the docs linked above, but in short, it’s a wrapper that:

  • Means authentication is enforced on the view
  • Means the view will not be cached

Now that we’ve created a url, we need to create the view that it will send a user to when visited.

My preview view does the logic to render the PDF I want to preview and returns an HttpResponse with content_type='application/pdf', this isn’t any different than any other view you’d write — go forth and do whatever custom thing you’ve set out to do!

Your admin class is typically defined in an admin.py file within the same app and at the same level at the models.py file in which you’ve defined the model for which you’re creating an admin page. You can do all the admin things you’d typically do, including defining its fields, form, list display, etc., with the addition of the change_form_template, like so:

class TemplateAdmin(admin.ModelAdmin):    
    ...    
    change_form_template = 'admin/preview_template.html'

custom_admin_site.register(models.Template, TemplateAdmin)

The html file referenced in the admin class above should live within the same app as your admin and models files, within templates/admin/directories. The whole file structure might look something like this:

├── top-level-repo
│   ├── django-app
│   │   ├── templates
│   │   │   ├── __init__.py
│   │   │   ├── admin
│   │   │   │   ├── __init__.py
│   │   │   │   ├── preview_template.html
│   │   ├── models.py
│   │   ├── admin.py
│   │   ├── views.py
│   │   ├── __init__.py

Because I just wanted to add a single link to the bottom of my page, I’m extending the base change form, which means all the fields or forms I’ve specified on my TemplateAdmin class above will be displayed, along with the usual save and delete buttons. I’m putting my custom link inside a block after_related_objects to put it at the bottom of the page — block after_field_sets is also an available option.

There are plenty of other templates you can override and extend if you wish! See a full list here.

{% extends 'admin/change_form.html' %}
{% block after_related_objects %}    
    {% if object_id %}        
        <a href="{% url 'custom_admin_site:preview' object_id %}" target="_blank">Preview </a>    
    {% endif %}
{% endblock %}

The addition of the if object_id statement means that the preview link won’t appear on the add form, but only on the edit form. The object_id variable is automatically available to you in this template.

At this point, we officially have a functioning preview link at the bottom of our Django admin form! 🎉