Implementing Code Blocks in Wagtail

Posted on March 14, 2020


codeblock

Implementing a custom code block in Wagtail

Wagtail is awesome, especially because of Streamfield editing that lets you mix and match content types and gives you immense control over styling. When it came time to build my site, one of the features I knew I wanted was the ability to add code blocks to pages. Since I'm a Sysadmin I figured I'd be sharing lots of code snippets and shell commands with their outputs. If you quickly Google "wagtail code block", you'll probably find wagtailcodeblocks on GitHub. And if you read through the project real quick, everything seems fine, right?! But this project has some flaws that are deal breakers for me:

  • You can't import a custom prism.js or prism.css for the rendering of blocks, which leaves you with whichever options the author selected (including line numbers, which I didn't want)
  • All of the themes are included and many more languages than you may need, so the files are much larger than they have to be
  • Every time you include a code block it's re-loading prism.css and prism.js, which was adding at least 500ms to my page load times on a page with 3+ code blocks

So I set out to figure out how to create my own block, and it turns out it's super easy. In my implementation detailed below, I only added code blocks to my blog app, because I don't need them on my home page or any other pages right now. You could easily adapt this to be project-wide, though! I should mention, the full source for my website is available here. Note, I highly recommend keeping website source in Git, but make sure you don't keep sensitive config items there! I have a Git hook that redeploys my site when I push changes and it automatically changes any sensitive values from the base config. Also, I highly borrowed from this website, but felt their example wasn't quite complete enough without the full project source or more details, it's also dated and includes an extra file that isn't necessary to use the block.


Download prism.js

First you're going to want to head on over to get yourself the prism source (https://prismjs.com/). Be sure to select whichever features you do or don't want (I'm looking at you, line numbers!), and include any languages you want to use on your site. Download the resulting prism.css and prism.js files that they provide, minify them if you want, and be sure to include them in any relevant templates that you're going to be using code blocks in:

<link rel="stylesheet" type="text/css" href="{% static 'css/prism.css' %}">
<script src="{% static 'js/prism.js' %}"></script>

Create a custom block

Next, we need to create our custom block. Be sure to replace the languages with whichever you included in your prism.js configuration before downloading it, and select whichever default makes the most sense for you. You can also mess with things like the icon that will show up in the streamfield editor if you want. This is from my blog app in blog/blocks.py:

from wagtail.core import blocks

CODE_CHOICES = [
    ('python', 'python'),
    ('javascript', 'javascript'),
    ('css', 'css'),
    ('markup', 'markup'),
    ('html', 'html'),
    ('go', 'go'),
    ('bash', 'bash'),
]


class CodeBlock(blocks.StructBlock):
    language = blocks.ChoiceBlock(choices=CODE_CHOICES, default="bash")
    text = blocks.TextBlock()

    class Meta:
        template = "blog/code_block.html"
        icon = "openquote"
        label = "Code Block"

Import the new block in your models.py

Again, in my case, I'm only using this custom block in my blog app and specifically for blog posts. If you want it to be available project-wide, you'll have to create it in a more general location in your project, which is totally doable! Looking at the following config snippet you'll see where I imported my custom CodeBlock and also an example of my streamfield utilizing it:

...
from blog.blocks import CodeBlock
...
    body = StreamField(
        [
            ('heading', blocks.CharBlock(classname="full title")),
            ('paragraph', blocks.RichTextBlock(features=features)),
            ('image', ImageChooserBlock()),
            ('page', blocks.PageChooserBlock()),
            ('document', DocumentChooserBlock()),
            ('media', EmbedBlock()),
            ('html', blocks.RawHTMLBlock(label='Raw HTML')),
            ('code', CodeBlock(label='Code')),
            ('table', TableBlock(template="blog/table_template.html")),

        ])

...

Create a template for rendering our CodeBlock

Lastly, if you notice in the CodeBlock class there's a reference to a template. You could omit this and pass it in your StreamField definition (like I do above for TableBlock), but I decided to just hard code it so I don't have to remember it. You can still override it, too! My template is as follows, and is located in blog/templates/blog/code_block.html, but note the language being passed in to tell prism.js how to hilight and render it on the front:

{% load wagtailcore_tags %}
<section>
    <pre><code class="language-{{ self.language }}">{{ self.text }}</code></pre>
</section>
Search


← Return to previous page