How To Add Markdown And Syntax Highlighting To Django

django tutorial markdown css

2023-11-21

This tutorial is based off of an excellent one that I followed when I was building this blog. You are looking at the results of that tutorial now as you read this. Unfortunately, not long after I had found it, the website went offline. In case it ever comes back, here is the link to the original: ljinlabs.com I reverse engineered the process from my own code so that you and I will know how to add Python-Markdown to our Django projects for future reference.

Table Of Contents

Introduction

I’ll assume you already have a Django project setup and have a blog app, or some other text-heavy input. There are certainly alternatives to this method for adding Markdown to Django like django-markdownx. I’ve really enjoyed this approach. I like the customizability and the minimalism of it.

Install Markdown and Pygments

To install the necessary libraries, Python-Markdown and Pygments, run the following commands in the terminal of your Django project’s virtual environment:

python3 -m pip install Markdown Pygments

Create A Custom Django Template Tags

This step is the core of adding Markdown to your Django project. Here you will create the custom template tag that will allow your template to convert text written in Markdown into HTML. For more info on creating custom template tags, checkout Django’s docs.

Folder Structure

Creating custom template tags requires you to follow a few code layout practices. Typically custom tags are specified within a Django app. For this tutorial, we will assume you are creating the custom tags inside of a Django blog app. Follow these steps to create the necessary folder and files:

Your final folder structure will look like this:

blog
├── __init__.py
├── apps.py
├── models.py
├── static
├── templates
├── templatetags
│   ├── __init__.py
│   └── markdown_extras.py
├── tests.py
└── views.py

Write Custom Markdown Tag

Now the fun begins. Let’s write some Python Django code! Open your freshly created markdown_extras.py file and enjoy that new file smell. Start by adding the necessary imports:

from django import template
from django.template.defaultfilters import stringfilter 

from markdown import markdown

Here is a quick overview of what these imports are for:

Next, add the decorators:

register = template.Library()

@register.filter
@stringfilter
def to_markdown(text):
    ...

After creating the register object, we use it to decorate the markdown function and register it as a template filter. The function also gets decorated with stringfilter.

Let’s finish and write the markdown function:

def to_markdown(text):
    return markdown(text, extensions=[
        'markdown.extensions.fenced_code',
        'markdown.extensions.codehilite',
        'markdown.extensions.nl2br',
    ])

There is not much to this function. It takes a single argument and returns it as an argument in the markdown function along with the desired extension options. The Python-Markdown library provides quite a few extensions. You can read more about them in their documentation and add additional extensions if desired. The first two extensions allow for fenced code and syntax highlighting of said code. The last extension treats newlines as hard breaks. This is a quality of life thing for me.

Putting this all together, your markdown_extras.py file should look like this:

from django import template
from django.template.defaultfilters import stringfilter

from markdown import markdown


register = template.Library()

@register.filter
@stringfilter
def to_markdown(text):
    return markdown(text, extensions=[
        'markdown.extensions.fenced_code',
        'markdown.extensions.codehilite',
        'markdown.extensions.nl2br',
    ])

Use The Custom Template Tag

Now it’s time to use your brand new, custom template tag. To do so, we will use the Django Template Language(DTL). Open the template you want use your new template tag in (e.g. detail.html) and insert the following:

{% extends 'base.html' %}

{% block content %}

<h1>{{ post.title }}</h1>
<p>{{ post.published }}</p>
{{ post.text }}

{% endblock content %}

As you can see, there is very little HTML in this HTML file. That is the beauty of the Django Template Language and Django’s template tags. We won’t go into everything here as it is outside the scope of this tutorial, but again, assuming you already have a Django project that you are adding Markdown to, a lot of this should be familiar.

If you have a model in models.py that has title, published, and text fields you can leave this as is. Otherwise change the DTL variables to match the model you are using. Similarly, if the context you are passing through your view in views.py is named post you can leave the variables, otherwise make the necessary changes to so that your page loads properly.

Start up your development server, navigate to the admin panel, and create a new post. Write something using Markdown and save the post:

josephmadsen_markdown_1

Navigate to the appropriate URL to view your new post. You should see something like this:

josephmadsen_markdown_2

That’s not quite what we want, is it? That’s because we haven’t loaded in our custom template tag and added it to the {{ post.text }} variable. Let’s do that now. Back in your detail.html file add the following:

{% extends 'base.html' %}


{% block content %}

{% load markdown_extras %} <!-- This loads in your custom tag -->

<h1>{{ post.title }}</h1>
<p>{{ post.published }}</p>
{{ post.text | to_markdown | safe }} <!-- This is where you use it -->

{% endblock content %}

We are loading in the file we created earlier called markdown_extras.py, and from that file we are using the function we created called to_markdown. It just looks a little different when it’s being used inside of the DTL. You’ll notice after our to_markdown tag there is another filter called safe. This lets Django know that the text we passing into the variable does not need to have the HTML escaped. Since we created it, we know it is not malicious.

Now reload your post page. It should look like this:

josephmadsen_markdown_3

That looks much better! But it is still kind of dull. Where is the syntax highlighting?

Add Codehilite CSS

This is where Pygments comes in. Python-Markdown’s codehihlite extension uses Pygments to generate CSS classes with the appropriate rules to highlight your code syntax. First, we need to establish the proper directory for the the CSS file to live in. From your blog app’s root level you will need to create a folder called static if you don’t already have one. Inside static create another folder called blog, and inside blog create one more folder called css. The reason for this is detailed in Django’s Official Tutorial. Basically, Django will collect all of your static files from all of your apps into one main static folder. The nested folder structure is required in order to avoid confusion.

Once that is in place, run the following command from the top-level directory of your project:

pygmentize -S default -f html -a .codehilite > blog/static/blog/css/codehilite.css

Your blog app’s folder structure should now look similar to this:

blog
├── __init__.py
├── admin.py
├── apps.py
├── models.py
├── static
│   └── blog
│       └── css
│           └── codehilite.css
├── templates
├── templatetags
├── tests.py
└── views.py

Now we have to load the static files into the template and link to codehilite.css:

{% extends 'base.html' %}


{% block content %}

{% load static %}
{% load markdown_extras %} 

<link rel="stylesheet" href="{% static 'blog/css/codehilite.css' %}">


<h1>{{ post.title }}</h1>
<p>{{ post.published }}</p>
{{ post.text | to_markdown | safe }}

{% endblock content %}

Run the development server again, navigate to your page, and now you should see something like this:

josephmadsen_markdown_4

Nice! That syntax is highlighted! This is Pygments’s default color scheme. Pygments comes with quite a few built in styles. You can check them out here. It is easy to change styles. Just rerun the command used to create the codehilite.css file, and swap out default for the style you want. For example, to use Monokai:

pygmentize -S monokai -f html -a .codehilite > blog/static/blog/css/codehilite.css

CSS Tweaks

Finally, here are a few things you can tweak in your separate style.css file to make your new Markdown empowered Django app look even better. These tweaks will:

blockquote {
    background: lightgray;
    padding: 1px 1px 1px 15px;
    border-radius: 5px;
    max-width: 70ch;
}

code {
    white-space: pre-wrap;
}

.codehilite {
    padding: 3px 3px 3px 15px;
    border-radius: 5px;
    max-width: 70ch;
}

Thank you for reading. I hope you find this tutorial helpful! If you have any comments or corrections, please let me know. I can be found on LinkedIn and Mastodon.




Go Back