23 Jan'12

Markdown image handling in Django

It’s an interesting question: how to deal with images in django admin which claims to be hardly extensible.

The default package comes with markdown integration, which could be easily installed by obtaining python-markdown from your favorite package manager.

Next, we should attach images to the record. We’ll do that by embedding special model of entry-specific image. To do the markdown, we need to have images’ paths or reference them in some way. The spec says we can invoke reference by ![][ref_id] and then it’ll be substituted.

def get_markdown(self):
    # create instance of Markdown class (note capital "M")
        md = markdown.Markdown()

        for image in self.chapterimage_set.all():
            image_url = settings.MEDIA_URL + image.image.url

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

            md.references[title.lower()] = (image_url, '')

        # parse source text
        convert = md.convert(self.body)
        refs = md.references
#        assert False
        return convert

Here we’ll notice the .lower() call. At first, I was surprised by selected action of the method, and after reading the source code on github, I got it! Assert false is a handy piece of code to fire up debug panel!

class ReferencePattern(LinkPattern):
    """ Match to a stored reference and return link element. """

    NEWLINE_CLEANUP_RE = re.compile(r'[ ]?\n', re.MULTILINE)

    def handleMatch(self, m):
            id = m.group(9).lower()
        except IndexError:
            id = None
        if not id:
            # if we got something like "[Google][]" or "[Goggle]"
            # we'll use "google" as the id
            id = m.group(2).lower()

        # Clean up linebreaks in id
        id = self.NEWLINE_CLEANUP_RE.sub(' ', id)
        if not id in self.markdown.references: # ignore undefined refs
            return None
        href, title = self.markdown.references[id]

        text = m.group(2)
        return self.makeTag(href, title, text)

    def makeTag(self, href, title, text):
        el = util.etree.Element('a')

        el.set('href', self.sanitize_url(href))
        if title:
            el.set('title', title)

        el.text = text
        return el

class ImageReferencePattern(ReferencePattern):
    """ Match to a stored reference and return img element. """
    def makeTag(self, href, title, text):
        el = util.etree.Element("img")
        el.set("src", self.sanitize_url(href))
        if title:
            el.set("title", title)
        el.set("alt", text)
        return el

It’s then used in a new way when invoked in template:

{#    {{ chapter.body|markdown }}#}
    {{ chapter.get_markdown|safe}}