«

Adding additional views to django admin

Today I wanna show you a simple way to add an additional view to the admin site.
Imagine that you want to create a view to review a model, something like send an email to the site owner with your thoughs (or your changes) about an entry.

First we need to define a simple model (of course you can use your existing model):

from django.db import models

class MyEntry(models.Model):  
    title = models.CharField(max_length=100)
    body = models.TextField()

    def __unicode__(self):
        return self.title

    class Meta(object):
        verbose_name = 'My Entry'
        verbose_name_plural = 'My Entries'</pre>

Once you have defined the model you need to register it to the admin site.

from django.contrib import admin

from my_test.models import *

admin.site.register(MyEntry)  

Now you can see your model under the django admin site:

Ok it works, let's add a simple entry like this:

Just to test if it works.

Now it's time to create the view and map it to the admin, simply subclassing the default ModelAdmin class:

from functools import update_wrapper

from django.contrib import admin  
from django.conf.urls import url  
from django.template import RequestContext  
from django.shortcuts import render_to_response

from my_test.models import *

class MyEntryAdmin(admin.ModelAdmin):  
    review_template = 'admin/my_test/myentry/review.html'

    def get_urls(self):
        def wrap(view):
            def wrapper(*args, **kwargs):
                return self.admin_site.admin_view(view)(*args, **kwargs)
            wrapper.model_admin = self
            return update_wrapper(wrapper, view)

        urls = super().get_urls()

        info = self.model._meta.app_label, self.model._meta.model_name

        my_urls = [
            url(r'(?P<id>\d+)/review/$', wrap(self.review), name='%s_%s_review' % info),
        ]

        return my_urls + urls

    def review(self, request, id):
        entry = MyEntry.objects.get(pk=id)

        return render_to_response(self.review_template, {
            'title': 'Review entry: %s' % entry.title,
            'entry': entry,
            'opts': self.model._meta,
            'root_path': self.admin_site.root_path,
        }, context_instance=RequestContext(request))

admin.site.register(MyEntry, MyEntryAdmin)  

In this example I have added a view overriding the get_urls method. Notice that we need to wrap our view in order to check permission.

One of the last thing to do is create the template for the view. Let's do it extending the admin base site template:

{% extends "admin/base_site.html" %}
{% load i18n admin_modify adminmedia %}
{% block extrahead %}{{ block.super }}
{% url admin:jsi18n as jsi18nurl %}
<script src="{{ jsi18nurl|default:" type="text/javascript"><!--mce:0--></script>  
{% endblock %}
{% block extrastyle %}{{ block.super }}
{% endblock %}
{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %}
{% block breadcrumbs %}{% if not is_popup %}
<div class="breadcrumbs">  
     <a href="../../../../">{% trans "Home" %}</a> ›
     <a href="../../../">{{ opts.app_label|capfirst|escape }}</a> ›
     <a href="../../">{{ opts.verbose_name_plural|capfirst }}</a> ›
     <a href="../">{{ opts.verbose_name|capfirst }} #{{ entry.pk }}</a> ›
     {% trans 'Review Entry' %}</div>
{% endif %}{% endblock %}
{% block content %}
<form action="." method="post">  
<fieldset class="module aligned">  
<div class="form-row">  
      <label>Entry</label>
      <textarea style="height: 136px; width: 161px; margin: 2px;" rows="10">{{ entry.body }}</textarea></div></fieldset>
<div class="submit-row">  
<input class="default" type="submit" value="{% trans 'Review' %}" /></div>  
</form>

{% endblock %}

Let's go to the url of the view, in my case http://localhost:8000/admin/my_test/myentry/1/review/. We should see something like this:


Ok, we got the view working, nice! But we don't want to manually insert the url every time. So let's add the url to the change model view, in order to get this:

We need to ovveride the default change model template creating a template under template_dir/admin/my_app/my_model/change_form.html (change this based on your configuration)

with this markup:

{% extends "admin/change_form.html" %}
{% load i18n admin_urls %}
{% block object-tools %}
{% if change %}{% if not is_popup %}
<ul class="object-tools">  
    {% url opts|admin_urlname:'review' original.pk|admin_urlquote as review_url %}
    <li><a href="{% add_preserved_filters review_url %}" class="historylink">{% trans "Review" %}</a></li>

    {% url opts|admin_urlname:'history' original.pk|admin_urlquote as history_url %}
    <li><a href="{% add_preserved_filters history_url %}" class="historylink">{% trans "History" %}</a></li>

    {% if has_absolute_url %}<li><a href="{{ absolute_url }}" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif %}
</ul>  
{% endif %}{% endif %}
{% endblock %}

That's all! If you have some problem with this code, add a comment or contact me.

Share Comment on Twitter