Archive for category JavaScript

Adding object tools to Django admin pages

Sometimes I want to add an item to the list of “object tools” on the change form for a model. The base admin/change_form.html template, however, does not (as of Django 1.0.2) include a block inside the object tools list:

<ul class="object-tools">
  <li><a href="history/" class="historylink">{% trans "History" %}</a></li>
  {% if has_absolute_url %}
      <a href="../../../r/{{ content_type_id }}/{{ object_id }}/" 
         class="viewsitelink">{% trans "View on site" %}</a>
  {% endif%}

I don’t want to reproduce this code to add something to the list, so instead I write a JavaScript function with jQuery:

function appendObjectTools(tools) {
    /* Appends items to the object tool list.
     * 'tools' param is an Array of Objects in which each Object
     * has 'url' and 'title' properties corresponding to the
     * URL and href for each link.
    html = '';
    for (var i=0; i < tools.length; i++) {
	html += '<li><a href="' + tools[i].url + '">' + tools[i].title + '</a></li>';
    $('#content-main .object-tools').append(html);

I can then override the change form for a model:

{% extends "admin/change_form.html" %}
{% block extrahead %}
    {{ block.super }}
    <script type="text/javascript">
$(function() {
    tools = [
            url: "{% url collections %}",
            title: "Collections"
{% endblock %}

jQuery and the custom script function can be added using ModelAdmin media definitions.

Now on my change form I get a pretty oval button. 🙂


, ,

Leave a comment

jQuery sugar sweetens URLFields on Django admin forms

You know you want it. You know you need it. A way for someone to preview a URL entered into a URLField on a Django admin form. Oh, and you gotta deal with that nasty leading and trailing white space that inevitably creeps in from copying/pasting.  It appears that whitespace trimming will be added to the Django code in some form — see ticket #6362 — but currently there’s no easy way to add this to URLFields because URL verification (with the verify_exists option) happens before any custom validation.


The JavaScript functions

Add this code to a new or existing JavaScript file (customized as necessary):

function addURLFormFeatures() {
    // add features to a form that has URLFields.

function trimURLsOnSubmit() {
    // trims leading and trailing whitespace from URL form input values
    // on form submission.
    $('#content-main form').submit(trimURLs);

function trimURLs() {
    // trims leading and trailing whitespace from URL input values.
    $('input.vURLField').each(function() {
	    var trimmed = jQuery.trim($(this).val());

function addURLPreviewLinks() {
    // adds "preview" links next to URL form inputs.
    var event = 'return URLPreview(this);';
    var title = 'Preview this URL';
    var style = 'margin-left: 0.5em';
    var image = new Image(16, 16);
    image.src = '/django/local/media/images/icons/link_go.png';
    image.alt = title;
    var link = '<a href="#" onclick="' + event + '" 
	                        style="' + style + '" 
                            title="' + title + '"><img 
                        src="' + image.src + '" 
                        alt="' + image.alt + '" 
	                    height="' + image.height + '" 
                        width="' + image.width + '" 

function URLPreview(link) {
    // executes on clicking "preview" link added by addURLPreviewLinks().
    var windowName = 'previewLink';
    var windowOptions = 'width=640,height=480,location=no,menubar=no,scrollbars=yes,resizable=yes,toolbar=no,status=no,left=100,top=100';
    var field = $(link).prev();            // field = URL input form control
    var url = jQuery.trim($(field).val());
    if (url) {
    	var url_re = new RegExp(/^https?:///);
    	if (url_re.test(url)) {, windowName, windowOptions);
    	} else {
            alert('URL must begin with "http://" or "https://".nnPlease correct and retry.');
    } else {
    	alert('Please enter a URL and retry.');
    return false;

Tying it together

If you just want to apply this customization to the admin forms for a single model, you can add a media definition to the ModelAdmin class for that model (thanks to Uzi for pointing this out) :

class MyModelAdmin(admin.ModelAdmin):
    class Media:
        js = ('/path/to/jquery.js', '/path/to/custom/script.js')

You’d also need another script to do the initialization (I didn’t include that in the custom script above):

$(function() {

If, however, you want this functionality to be available across all model admin forms, you can override the admin/change_form.html template:

{% extends "admin/change_form.html" %}
{% block extrahead %}
    {{ block.super }}
    <script src="/path/to/jquery.js" type="text/javascript"></script>
    <script src="/path/to/custom/script.js" type="text/javascript"></script>
    <script type="text/javascript">
        $(function() {
{% endblock %}

You could also replace the admin/change_form.html template to make this functionality globally available in Django’s admin, but you’d want to update the template with the new template from the distribution whenever you upgraded Django.

To Do

Adding a “browse” function as an alternative to copy/paste for entering URLs is left as an exercise for the reader.  You could also do more to check that the URL is at least “well-formed” before attempting to get it with the browser. The reason I only look for the https?:// prefix is for sanity (sure, you can add ftp://, etc.) because browsers will prepend http:// by default if no protocol is entered in a location, whereas Django’s built-in URL validation (using standard Python modules) isn’t going to do that, so users get confused. Otherwise, doing a full regex on the URL is sort of redundant, since if it’s a bad URL you ain’t gettin’ nothin’ anyway.  If you really want to be cool, figure out a way to create a custom option on URLField to toggle this functionality on/off (it might not be too hard).

, , , ,