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.
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.
#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):
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…