Overriding templates in Pelican when using multiple configs



This website and all its pieces now needs 4 pelican config files. And the latest addition requires different direct templates than the others. So I needed a way to override the direct templates without royally fucking anything else up. This is what I came up with.

Setup

I have 4 pelican configs. The main config blog_conf.py is what builds this website. The second one 'secret_conf.py' builds my secret portion of the website. The third thumbs_conf.py builds only the thumbnails so I do not waste space on my HDD by using symlinks. The fourth product.py builds the product page I am working on now, which relies on the css and general templates I have already created.

Thankfully I have a few key abstractions in place that made this go surprisingly smooth.

  • All of my configs inherit from the blog_conf.py to be as DRY as possible which eliminates duplication of efforts. Especially in the plugins department
  • I use the webassets plugin to handle my css and js linking
  • My css is broken into small pieces so it's easily manageable which styles end up which page. See this post
  • I use a base.html jinja template that all other templates inherit from

FIX:

FIRST, update generators.py so it will search EXTRA_TEMPLATES_PATHS first. Otherwise you cannot use extra tempaltes for override

generators.py

This is what I changed to get this magic to happen... Look between the "THESE LINES" lines :p

class Generator(object):
    """Baseclass generator"""

    def __init__(self, context, settings, path, theme, output_path,
        ....
        # templates cache
        self._templates = {}
        # __________THESE LINES __________
        self._templates_path = self.settings['EXTRA_TEMPLATES_PATHS']
        self._templates_path.append(os.path.expanduser(
            os.path.join(self.theme, 'templates')))
        # ______________________________

Example

Then in our extra folder we can "override" the templates we need to modify. Because of our setup with the base.html and webassets with piecemeal css it is easy to slap in the extra css needed to override styles from the original theme. Although this does kill my "one css file per page rule". Whatever.

blog_conf.py

This is the main pelican conf that builds the root content

THEME                      = 'theme'
DIRECT_TEMPLATES           = ['index', 'archives']
PAGINATED_DIRECT_TEMPLATES = ['index']
EXTRA_TEMPLATES_PATHS      = []

product.py

This is the pelican conf that builds the product content in /product. Note that I import the main blog conf to avoid repetition in simplify changes in the future. DRY as can be!

from blog_conf import *
THEME                      = 'theme'
DIRECT_TEMPLATES           = ['index', 'archives']
PAGINATED_DIRECT_TEMPLATES = ['index']
EXTRA_TEMPLATES_PATHS      = ['theme/templates/product']

This is my folder structure

theme/templates/
    base.html
    archives.html
    index.html
    fragments/
        sidebar.html
        footer.html
    product/
        base.html
        index.html
        fragments/
            menu.html
            footer.html

theme/templates/base.html

This is the ROOT base.html I use. Note the sidebar and footer fragments

<!DOCTYPE html>
<html lang="{{ DEFAULT_LANG }}">
<head>
    <title>{% block page_title %}{% endblock page_title %} | Main Blog</title>
    {% include 'fragments/head.html' %}
    {% block assets %} {% endblock %}
</head>
<body>
    {% include 'fragments/sidebar.html' %}
    <div class="content">
        {% block title %} {% endblock %}
        {% block content %} {% endblock %}
        {% include 'fragments/footer.html' %}
    </div>
</body>
</html>

theme/templates/product/base.html

NOTE: I remove the above sidebar and put in the menu. I add in another asset reference that will override some of the other default css from the original theme using the webassets plugin. Pelican plugins FTW! :+1:

<!DOCTYPE html>
<html lang="{{ DEFAULT_LANG }}">
<head>
    <title>{% block page_title %}{% endblock page_title %} | Product</title>
    {% include 'fragments/head.html' %}
    {% block assets %} {% endblock %}
    {% assets "css-product-base" %}
        <link rel="stylesheet" href="{{ SITEURL }}/{{ ASSET_URL }}">
    {% endassets %}
</head>
<body>
    <div class="content">
        {% include 'fragments/menu.html' %}
        {% block title %} {% endblock %}
        {% block content %} {% endblock %}
        {% include 'fragments/footer.html' %}
    </div>
</body>
</html>

theme/templates/archives.html

This is where it gets awesome. While this relative url in the archive folder looks like it is pointing to the same folder, the environment picks up the base.html in the product folder first. So we need to change nothing in order to reuse the same template but get all the same benefits. The power of abstraction right? :rofl:

{% extends "base.html" %}
Comments are loading... I hope ;)