Ole Morten Halvorsen

Aug. 18, 2008
Django: Inserting and Positioning Images

A few days ago I stumbled upon a thread over at the Django users mailing list discussing how to handle media in Django. It raised the interesting question of how do you associate images and files with your articles, blog posts, etc. When I started this blog I — too — realized I needed a way to insert and position images in my blog posts. Django only gives you a way to store text and images and leaves the rest up to us, the users of Django.

My solution uses Markdown as the markup language. All my post are written and stored in the database as Markdown. Markdown might be too low-level for end users or non technical editors. It is more than user-friendly enough for me, but your milage may vary on this depending who’s creating the content on your site.

One of the nice things about Markdown is that it has a very handy syntax for inserting images without having to type any HTML:

1
2
![Alt text][id]
[id]: url/to/image "Optional title attribute"

Running the above through Markdown will convert it into a nice <img> tag. However, I would have to manually figure out and insert the path to all my images, not fun. To fix this annoying figure-out-the-url-manually, I added some pre-processing to the Markdown which dynamically creates the Markdown image references. This removes the need of manually figuring out image paths.

As an example, something like this:

1
2
3
4
5
6
7
8
# Hello, World!
Lorem ipsum dolor sit amet, consectetur adipisicing elit

![ImageOne][]
![ImageTwo][]

Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris 
nisi ut aliquip ex ea commodo consequat

Will be turned into:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Hello, World!
Lorem ipsum dolor sit amet, consectetur adipisicing elit

![ImageOne][]
![ImageTwo][]

Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris 
nisi ut aliquip ex ea commodo consequat

[ImageOne]: /media/imageone.png
[ImageTwo]: /media/imagetwo.png

By having a ManyToMany relationship to Image on my Post model I can associate images and position them using by insert ![ImageName] into the markdown. You can see how this works out in the admin from the screenshot below.

Image Insertion in Admin

The Markdown processing is done using a helper function, markdown_to_html. It generates Markdown image references from a list of images, inserts the references at the bottom of the Markdown text and turns the Markdown into HTML.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# models.py
import datetime
import markdown

from django.db import models
from django.conf import settings

def markdown_to_html( markdownText, images ):    
    image_ref = ""

    for image in images:
        image_url = settings.MEDIA_URL + image.image.url
        image_ref = "%s\n[%s]: %s" % ( image_ref, image, image_url )

    md = "%s\n%s" % ( markdownText, image_ref )
    html = markdown.markdown( md )

    return html

class Image( models.Model ):
    name = models.CharField( max_length=100 )
    image = models.ImageField( upload_to="image" )

    def __unicode__( self ):
        return self.name

class Post( models.Model ):
    title = models.CharField( max_length=100 )
    body = models.TextField()
    images = models.ManyToManyField( Image, blank=True )

    def body_html( self ):
        return markdown_to_html( self.body, self.images.all() )

Please note that the markdown_to_html function uses image.image.url to grab the image URL. This only works after the file storage re-factoring.

As you can see on line 32, the Post class has a body_html method which calls markdown_to_html. We can call this in the templates to get the final HTML output. If performance is important, you probably want to cache the conversion result when saving your model.

I’m very interested to hear about other solutions. If you have tackled this type of issue, please add a comment describing how you did it.

Comments

#1 Waylan Limberg

Posted: Aug. 18, 2008

Ole,

This is interesting. I like it. I have thought about that before, but never tried to solve that problem. Nice job.

I was going to ask if you are aware that Markdown has an extension API, but then it occurred to me that a full-blown extension is probably a little much for this. However, there is no need to append the image urls as markdown text to the document. You could add them to the link store on the Markdown class directly. It’d save you a little bit of parsing time at least. Perhaps something like this (untested):

def markdown_to_html(markdownText, images):
    # create instance of Markdown class (note capital "M")
    md = markdown.Markdown()

    for image in images:
        image_url = settings.MEDIA_URL + image.image.url

        # append image reference to Markdown instance
        # md.reference[id] = (url, title)
        md.reference[image.name] = (image_url, '')

    # parse source text
    return md.convert(markdownText)

If you notice, I passed in a blank string for each image’s “title” attribute. You could easily add another field to your “Image” model to store a title.

If your curious as to how/why this works, note that this is actually what Markdown does. In a pre-processor, it finds and removes all references and stores then this way. Then, later during parsing, when it finds a link (or image link in this case) it looks in md.references for a matching id and builds the html link. I’d suggest reading the extension docs and markdown sourcecode for more details.

Waylan Limberg http://achinghead.com

#2 Will Larson

Posted: Aug. 18, 2008

This is how I handled this problem as well, in my LifeFlow blog software. I’ve actually implemented it as a pre-processor as Waylan suggests, so you could take a gander at the code (the above link is to its github repository) if you’re interested in doing what Waylan suggests.

#3 Jeff Triplett

Posted: Aug. 18, 2008

Good idea. I have a link database that I use the same technique on for so that I don’t have to type in web addresses.

#4 onno

Posted: Aug. 18, 2008

I just use filebrowser en tinymce

#5 Ole Morten Halvorsen

Posted: Aug. 18, 2008

@WAYLAN, @WILL: Brilliant! Thanks for the tip regarding md.reference. That’s of course an even better way of doing it :)

#6 Peter Baumgartner

Posted: Aug. 18, 2008

Take a look at django-admin-uploads. I haven’t been good about updating it, but when I first created it, I built some hooks to Textile, so converting to Markdown shouldn’t be a big deal.

#7 camr

Posted: Aug. 19, 2008

I am doing something similar on a personal site I’m working on, (first website / django project :) ).

The images are set to inline edit with their posts in the admin.
I then use regular expressions to replace [image#] with html template code for neat image+caption inclusion, during pre-processing before initial save of the post. Lastly, I have a similar method to your “body_html” to pre-render this “template-in-a-field” through Django’s template engine at run-time. (this final pre-rendering might be unnecessary with latest Django? not sure, I remember seeing a discussion about rendering contents of context variables as templates)

#8 web design company

Posted: Aug. 20, 2008

hmmmm…. very interesting…

Leave a comment

Commenting has been disabled. This entry was posted more than 2 weeks ago.

Created by Ole Morten Halvorsen.   Powered by Django.