Using Dojo Rich Editor with Django's Admin

Post Info

Author

Eugene Lazutkin

Date

Posted: Sunday, March 13, 2011

Categories

Development::Python::Django (47)
Development::Web::AJAX::Dojo (33)
Tutorials::Python::Django (5)

Many years ago I decided to replace plain text areas in Django's Admin with rich text editor, so I can edit HTML on my blog using WYSIWYG. Six (yes, 6) years ago I looked around and selected TinyMCE. Over time it turned out that I was forced to upgrade TinyMCE and the link script I had because new browsers continue breaking my rich editor editing. Finally it stopped working again in all modern browsers, and I decided that enough is enough. It is time to replace it. This time I settled on Dojo's Rich Editor hosted on Google CDN — simple, functional, less work to set up.

Selecting features

Adding Dojo's editor to Django's Admin was very simple. The most complex part turned out to be selecting what cool features I want to use. See, dijit.Editor is fully pluggable, and it is easy to extend with your own plugins. Some day I'll do just that, but for now let's use what's available.

dijit.Editor comes with two sets of plugins. Let's go over them and select plugins we want to use.

The first set is in Dijit's editor plugin repository:

The second set of plugins comes from DojoX' editor plugin repository:

So we made our selection. BTW, if you follow links above to plugin repositories and can read up on each plugin and see small examples of them in action.
Like I said I don't want to host Dojo myself and will use Google CDN for that. It simplifies a lot of things: no need for copying stuff around; I am bypassing custom builds hoping that it'll be fast enough for me; all I need to do is to add two files: a JavaScript link, and a CSS file to tweak visuals. Let's do it.

JavaScript

This part is about loading plugins we need, and instantiating dijit.Editor on our <textarea> nodes:

dojo.require("dijit.Editor");

// extra plugins
dojo.require("dijit._editor.plugins.FontChoice");
dojo.require("dojox.editor.plugins.TextColor");
dojo.require("dojox.editor.plugins.Blockquote");
dojo.require("dijit._editor.plugins.LinkDialog");
dojo.require("dojox.editor.plugins.InsertAnchor");
dojo.require("dojox.editor.plugins.FindReplace");
dojo.require("dojox.editor.plugins.ShowBlockNodes");
dojo.require("dojox.editor.plugins.PasteFromWord");
dojo.require("dijit._editor.plugins.ViewSource");
dojo.require("dijit._editor.plugins.FullScreen");
dojo.require("dojox.editor.plugins.InsertEntity");

// headless plugins
dojo.require("dojox.editor.plugins.CollapsibleToolbar");
dojo.require("dojox.editor.plugins.NormalizeIndentOutdent");
dojo.require("dojox.editor.plugins.PrettyPrint");	// let's pretty-print our HTML
dojo.require("dojox.editor.plugins.AutoUrlLink");
dojo.require("dojox.editor.plugins.ToolbarLineBreak");

dojo.ready(function(){
  var textareas = dojo.query("textarea");
  if(textareas && textareas.length){
    dojo.addClass(dojo.body(), "claro");
    textareas.instantiate(dijit.Editor, {
      styleSheets: "/appmedia/style.css;/appmedia/blog/style.css",
      plugins: [
        "collapsibletoolbar",
        "fullscreen", "viewsource", "|",
        "undo", "redo", "|",
        "cut", "copy", "paste", "|",
        "bold", "italic", "underline", "strikethrough", "|",
        "insertOrderedList", "insertUnorderedList", "indent", "outdent", "||",
        "formatBlock", "fontName", "fontSize", "||",
        "findreplace", "insertEntity", "blockquote", "|",
        "createLink", "insertImage", "insertanchor", "|",
        "foreColor", "hiliteColor", "|",
        "showblocknodes", "pastefromword",
        // headless plugins
        "normalizeindentoutdent", "prettyprint",
        "autourllink", "dijit._editor.plugins.EnterKeyHandling"
      ]
    });
  }
});

As you can see it has three major logical blocks:

  1. List of dojo.require() calls — we request dijit.Editor and all plugins we selected above.
  2. Function inside of dojo.ready(), which looks for <textarea> nodes and instantiate dijit.Editor on them. Why dojo.ready()? Because we use CDN and load all resources asynchronously ⇒ we need to wait while they all are loaded.
  3. List of all plugins including built-in ones. Why? I want to make the toolbar to look good and be functional, so I manually specified what I want to see. "|" produces a vertical separator. "||" breaks a line.

This is how it looks now:

Django Admin's textarea replaced with Dojo's dijit.Editor.

Everything is exactly where I want it to be.

But in order to look like that we should create a CSS file.

CSS

Now we want to include necessary CSS files and make sure that our editor play nice with Django Admin's CSS:

/* Import standard Dojo CSS files */
@import "https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dijit/themes/claro/claro.css";

/* Import custom style sheets for plugins */
@import "https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojox/editor/plugins/resources/css/FindReplace.css";
@import "https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojox/editor/plugins/resources/css/ShowBlockNodes.css";
@import "https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojox/editor/plugins/resources/css/InsertEntity.css";
@import "https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojox/editor/plugins/resources/css/CollapsibleToolbar.css";
@import "https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojox/editor/plugins/resources/css/Blockquote.css";
@import "https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojox/editor/plugins/resources/css/PasteFromWord.css";
@import "https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojox/editor/plugins/resources/css/InsertAnchor.css";
@import "https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojox/editor/plugins/resources/css/TextColor.css";
@import "https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojox/editor/plugins/resources/css/SpellCheck.css";
@import "https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojox/editor/plugins/resources/css/PasteFromWord.css";
@import "https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojox/editor/plugins/resources/css/PasteFromWord.css";
@import "https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojox/editor/plugins/resources/css/PasteFromWord.css";

/* update CSS rules to cater for Django Admin */
.dijitEditor {display: inline-block;}
.dijitEditor .dijitToolbar .dijitTextBox {width: 20ex;}
.dijitEditor .dijitToolbar label {display: inline; float: none; width: auto;}

As you can see it includes external CSS from Google CDN required by certain plugins.

At the end it contains three rules, which make sure that our editor looks good and not distorted by Django Admin's CSS.

Let's include both files in our Admin pages.

Django Admin

In order to enable what we wrote above we should add it to our admin.py files. For that let's define a shell class to hold our additional media files:

class CommonMedia:
  js = (
    'https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojo/dojo.xd.js',
    '/appmedia/admin/js/editor.js',
  )
  css = {
    'all': ('/appmedia/admin/css/editor.css',),
  }

We include two JavaScript files: Dojo 1.6 from Google CDN, and our JavaScript file detailed above. And one CSS file described above. It is as simple as it can be. No copying more files, no hosting hundreds of static files, no additional server-side code.

As you can see it is uber simple and described in Django's documentation.

Now all we need to do is to reference this class in our model registrations, like that:

site.register(models.Category,
  list_display  = ('full_name',),
  search_fields = ['full_name',],
  Media = CommonMedia,
)

That's it!

Conclusion

To help people to add nice WYSIWYG HTML editor to their Django Admin pages I published snippets described above as a gist: https://gist.github.com/868595.

I made this guide as simple as possible. Obviously the implementation can be improved. For example, you can do a custom build effectively collapsing all JavaScript and CSS files — it will improve the performance greatly provided that you can host this custom build as efficiently as Google. I didn't do it because I am the only user of my Django Admin, it is not exposed to my readers, so I don't expect it to be performance critical.

Have fun!

Save/recommend this post:  del.icio.us  Digg  Reddit  StumbleUpon  Facebook    Subscribe to this blog:  Bloglines  Netvibes

If you want to rate this post or write a review for others to read — use the gadget in the top-right corner of the page.

Leave a comment below:

Made with Django.