Evgenii Legotckoi
Evgenii LegotckoiMay 17, 2023, 1:52 a.m.

Django - Lesson 064. How to write a Python Markdown extension

On this site and a couple of other projects, I actively use markdown to write text, and then generate html code from it. And in the end, I wrote a couple of extensions that perform some specific functionality, which somewhat facilitates the support for generating html code on my projects.

For example, in one of the projects, images are added to the text that are added to the site and attached to the post. Accordingly, images have a specific url that nginx sends this image to. But I made the markdown image embed code look simpler for images that are uploaded to the site.

Image embed code description

That is, the image insertion code in this case looks like this


In this code, there is only the name of the image file e272ed2bca9e46e8b017c20eddd1e5b2.webp . On the project where this is implemented, all images are loaded into one directory with a unique generated name using uuid, and the image is compressed and converted to WEBP format. You can learn more about how to do this in the article How to convert an image to WEBP format when saving to an ImageField .

So, when images are attached to a post, they are displayed on that project as a gallery under the text, but can also be added inside the post text. But in order not to confuse users with a long url to the image in markdown markup, and also to simplify further potential modification of image paths, a function was implemented to generate an html img tag from this simplified image code.

Adding python markdown extension

Generate html text with python markdown

The Python-Markdown package is used to generate the HTML. The generation code might look like this:

def generate_html(markdown_text):
    return markdown.markdown(

As you can see, when generating it is possible to enumerate extensions that can affect the result of code generation. It is in these extensions that we will add our extension.

Add extension

Let's say in a Django project we have an app called core. Let's create an extensions directory inside it with a __init__.py file that initializes the directory as a package, as well as a file with the name of the extension. It should look like this.

  • core/
    • extensions/
      • __init__.py
      • image.py

Image.py file

Now let's add the contents of the image.py file.

# -*- coding: utf-8 -*-

import markdown
from markdown.util import etree

class ImageExtension(markdown.Extension):

    def add_inline(self, md, name, klass, re):
        pattern = klass(re)
        pattern.md = md
        pattern.ext = self
        md.inlinePatterns.add(name, pattern, "<reference")

    def extendMarkdown(self, md, md_globals):
        self.add_inline(md, 'internal_image', InternalImage, r'!\[\]\((?P<internal_image>[a-zA-Z0-9]+).webp\)')

class InternalImage(markdown.inlinepatterns.Pattern):

    def handleMatch(self, m):
        return render_image(m.group('internal_image'))

def render_image(internal_image_name):
    img = etree.Element('img')
    img.set('src', '/media/photos/{}.webp'.format(internal_image_name))
    img.set('class', 'd-block mx-auto mw-100 mvh-75')
    return img

def makeExtension(**kwargs):
    return ImageExtension(**kwargs)

In this case, we are creating an ImageExtension extension that extends the Markdown markup using the InternalImage template.
This pattern is applied to all image tags that match the regular expression that is passed to the add_inline method, which ultimately adds the pattern to the list of patterns with the markdown code handler.

The makeExtension function is called under the hood of Python-Markdown and registers the extension.


As a final word, let's add your new extension to the generation of your html code.

def generate_html(markdown_text):
    return markdown.markdown(
We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

Do you like it? Share on social networks!


Only authorized users can post comments.
Please, Log in or Sign up

C++ - Test 001. The first program and data types

  • Result:53points,
  • Rating points-4

C++ - Тест 003. Условия и циклы

  • Result:57points,
  • Rating points-2

C++ - Test 005. Structures and Classes

  • Result:100points,
  • Rating points10
Last comments
JonnyJoMay 25, 2023, 8:24 p.m.
How to make game using Qt - Lesson 2. Animation game hero (2D) Евгений, благодарю!
Evgenii Legotckoi
Evgenii LegotckoiMay 25, 2023, 10:49 a.m.
How to make game using Qt - Lesson 2. Animation game hero (2D) Код на строчка 184-198 вызывает перерисовку области на каждый 4-й такт счётчика. По той логике не нужно перерисовывать объект постоянно, достаточно реже, чем выполняется игровой слот. А слот вып…
JonnyJoMay 21, 2023, 4:49 p.m.
How to make game using Qt - Lesson 2. Animation game hero (2D) Евгений, благодарю! Всё равно не совсем понимаю :( Если муха двигает ножками только при нажатии клавиш перемещение, то что, собственно, делает код со строк 184-198 в triangle.cpp? В этих строчка…
Evgenii Legotckoi
Evgenii LegotckoiMay 21, 2023, 11:57 a.m.
How to make game using Qt - Lesson 2. Animation game hero (2D) Добрый день. slotGameTimer срабатывает по таймеру и при каждой сработке countForSteps увеличивается на 1, это не зависит от нажатия клавиш, нажатая клавиша лишь определяет положение ножек, котор…
JonnyJoMay 20, 2023, 5:27 p.m.
How to make game using Qt - Lesson 2. Animation game hero (2D) Евгений, здравствуйте! Подскажите, а почему при нажатии одной клавиши переменная countForSteps увеличивается не на 1, на 4, ведь одно действие даёт увеличение этой переменной только на единицу? …
Now discuss on the forum
Evgenii Legotckoi
Evgenii LegotckoiApril 16, 2023, 10:07 a.m.
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Да, это возможно. Но подобные вещи лучше запускать через celery. То есть drf принимает команду, и после этого регистрирует задачу в celery, котроый уже асинхронно всё это выполняет. В противном …
Алексей БобровDec. 15, 2021, 1:03 a.m.
Sorting the added QML elements in the ListModel I am writing an alarm clock in QML, I am required to sort the alarms in ascending order (depending on the date or time (if there are several alarms on the same day). I've done the sorting …
Evgenii Legotckoi
Evgenii LegotckoiMarch 29, 2023, 10:11 a.m.
Замена поля ManyToMany Картинки точно нужно хранить в медиа директории на сервере, а для обращения использовать ImageField. Который будет хранить только путь к изображению на сервере. Хранить изображения в базе данных…
Evgenii Legotckoi
Evgenii LegotckoiApril 24, 2023, 9:22 a.m.
Пакеты данных между сервером и клиентами Привет. Если классы имеют что-то общее в полях, а также общую идеологию и их можно вписать в иерархию наследования, то в первую очередь переписать так, чтобы один базовый класс объединял в…

Follow us in social networks