How To Add Markdown And Syntax Highlighting To Django
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
- Install Markdown and Pygments
- Create A Custom Django Template Tag
- Use The Custom Template Tag
- Add Codehilite CSS
- CSS Tweaks
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:
- Inside of your
blog
app create a directory calledtemplatetags
at the same level asapps.py
,models.py
, etc. - Create
markdown_extras.py
and__init__.py
inside of thetemplatetags
directory
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:
template
will be used to register the new template tag with Djangostringfilter
converts an object into a string before passing it to the functionmarkdown
provides the function that will take care of converting Markdown text into HTML
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:
Navigate to the appropriate URL to view your new post. You should see something like this:
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:
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:
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:
- add a nicely shaped background to your blockquotes
- ensure your code stays inside the boundaries of the fenced code
- make the fenced code area look a little more pleasing to the eye
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