Django and Syntax Highlighting

There seems to be a huge number of solutions for enabling syntax highlighting in Django applications. This was an issue I had to tackle before I deployed my blog, so I did a little bit of research, and ended up combining a few different methods to build a good solution.

The easy part of the research was to determine which syntax highlighting library to use. Pygments is obviously leading the pack in this area - it’s very simple to install, supports a large number of languages and markup formats, and even has an easy to use implementation for Markdown, so I quickly did an easy_install on my development and production boxes. There wasn’t anything else required.

Pygment’s Markdown processor is easily pluggable into the parsing process, but I wanted to take it one step further. Django provides the markdown template filter to parse any variable using the markdown processor. However, I don’t see the point of parsing the same content every time the page is rendered. So I decided to use Django’s cache framework.

Here I am parsing the body for my blog application’s Post model when the body_html property is called for the first time, then caching it for 30 days:

from django.core.cache import cache
from django.utils.safestring import mark_safe
 
class Post(models.Model):
...
@property
def body_html(self):
"""
Parses the body field using markdown and pygments, caches the results
"""
key = "blog_posts_body_%s" % self.pk
html = cache.get(key)

if not html:
html = parse_markdown(self.body)
cache.set(key, html, 60 * 60 * 24 * 30)

return mark_safe(html)

I tend to edit my posts a few times after I publish them, so I also made sure that the cache was deleted if a post was modified, using a custom save method in the model:

class Post(models.Model):
def save(self):
if self.pk:
cache.delete("blog_posts_body_%s" % self.pk)

super(Post, self).save()

The parse_markdown method is a very simple function that adds the Pygments processors to the Markdown pre-processors list before the content is rendered:

from markdown import Markdown
 
def parse_markdown(value):
"""
Parses a value into markdown syntax, using the pygments preprocessor
"""
md = Markdown()
md.textPreprocessors.insert(0, CodeBlockPreprocessor())

return md.convert(value)

I also ended up modifying the actual Pygments processors slightly, because I don’t like their [sourcecode:lexer][/sourcecode] format. Instead, I changed the pattern to be like this:

pattern = re.compile(r'@@ (.+?)\r\n(.+?)\r\n@@ end‘, re.S)

The final step was to pick a Pygments style and generate the CSS file:

pygmentize -S colorful -f html > media/css/pygments_colorful.css

And that’s it, we got ourselves a pretty good markup solution with syntax highlighting!