I recently ported my blog from WordPress (ick) to a new platform, based on Wagtail / CodeRed and Isso. Because CodeRed implements a content management system (CMS), it's easy to deploy a "regular" web site and blog using the same underlying software. All of the components are Python-based, and all are easy to keep up-to-date using Python's tools and virtual environments.

My goal in this blog entry is to walk you through how to set this up. I made a few very small changes to the CodeRed codebase to better support a blog and navigation, but the installation is otherwise "vanilla".

CMS: CodeRed

I'm using the CodeRed Extensions for Wagtail because Wagtail is a very clean and easy-to-install CMS, and CodeRed provides a nice overlay that provides nearly all of the features I want for a regular web site and a blog. Both are implemented as Python packages, making them easy to install. I'm not going to rehash the excellent installation documentation that CodeRed provides, so I suggest starting there and getting your basic site installed. From there, do the basic setup, including the choice of a basic blog CSS style.

Once you've got your site installed, there are a few small changes that you can make to make it work better as a blog site. There are two features I wanted that aren't available in the vanilla CodeRed installation:

  • Blog posts don't display back-links to lists of blog posts with the same topics. I wanted to have this on every blog entry.
  • There's no commenting system built into CodeRed.

Tackling the first one is relatively straightforward, and requires that you add the following lines to the end of website/models.py.

class BlogEntryPage(CoderedArticlePage):
    Blog entry-specific page

    class Meta:
        verbose_name = "Blog entry"
        ordering = ["-first_published_at"]

    # Only allow this page to be created beneath an BlogIndexPage.
    parent_page_types = ["website.BlogIndexPage"]

    template = "website/pages/blog_entry_page.html"
    search_template = "coderedcms/pages/article_page.search.html"

class BlogIndexPage(CoderedArticleIndexPage):
    Shows a list of blog entries, with menus for tags and dates.

    class Meta:
        verbose_name = "Blog Index Page"

    topics = "Blog topic"
    # Override to specify custom index ordering choice/default.
    index_query_pagemodel = "website.BlogEntryPage"

    # Only allow ArticlePages beneath this page.
    subpage_types = ["website.BlogEntryPage"]

    template = "website/pages/blog_index_page.html"

This doesn't change the behavior much, but it does allow you to easily use different rendering pages for your BlogIndexPage and BlogEntryPage. Once you've done this, run python manage.py makemigrations and then python manage.py migrate.

At this point, you have the code to support a blog index page and blog entry pages. They're nearly identical to the stock article pages, but they use a different template, and that'll allow you to display blog entry pages differently to include both tag navigation and comments.

Next, cd website/templates/website from your main site directory, and ensure that there's a pages directory, creating it if necessary. This is where your blog entry page template will go, so cd pages.

To set up the blog entry template, do the following:

  1. Copy the default article_page.html from the installation directory to the directory you're in. The path to the original will likely end in site-packages/coderedcms/templates/coderedcms/pages/article_page.html, or something similar. Rename the copy you created to blog_entry_page.html.
  2. Edit blog_entry_page.html to make the following changes:
    • Near the top, there's a line that starts {% load. Add i18n to the list of loaded libraries.
    • Replace the first section of code with the second:
         <div class="container mx-auto article-body">
           {% for block in self.body %}
           {% include_block block with settings=settings %}
           {% endfor %}
         <div class="row">
           <div class="col-md-10">
             <div class="container mx-auto article-body">
               {% for block in self.body %}
               {% include_block block with settings=settings %}
               {% endfor %}
               <div style="padding-top: 30px; padding-left: 10px; padding-bottom: 10px; padding-right: 5px; ">
                 <script data-isso="//my.comments.domain.tld/" src="//my.comments.domain.tld/js/embed.min.js"></script>
                 <section id="isso-thread"&rt;<noscript>Javascript needs to be activated to view comments.</noscript></section>
           <div class="col-md-2">
             <ul class="nav nav-pills flex-column">
             {% for term in page.classifier_terms.all %}
               <li class="nav-item">
                 <a class="nav-link" href="{% pageurl page.get_parent %}?c={{ term.slug }}">{{ term.name }}</a>
             {% endfor %}

There are a couple of spots that mention my.comments.domain.tld. Replace that with the name of the domain that'll be serving your isso comments, as we'll describe in the next section.

Now that you've done that, new BlogEntryPages will automatically include a navigation panel on the right that links back to the index page, and will include space for comments at the bottom.

Using isso for commenting

I decided to use isso, a Python package, to self-host my comments. You could, instead, use Disqus if you prefer, but then you'd either have to pay for it or host ads on your site. I didn't want to do either, so I set up isso, following the instructions at the isso site.

I set isso up as its own user for several reasons. First, it ensures that, if isso is hacked, it can't affect the core content of my site and blog. I can always disable isso and leave the core site untouched. Second, it allows me to easily use different Python virtual environments, just in case isso and CodeRed end up with different requirements.