Many companies these days use a dynamic content management solution to build their own site. More often than not people slap a WordPress installation with a highly customized theme and a bunch of tailored plugins. We at Stackful.io are Python fans, so we tried to do the same thing just using Mezzanine instead since it builds on top of the excellent Django framework.

We have been running happily with small customizations and a site design similar to the default one that Mezzanine ships. Until the time came to do our own thing... We were digging through the Mezzanine documentation and default templates looking for the easiest way to customize them, when it hit us. We don't really need a full content management solution for a simple site with a blog and a bunch of static pages. Besides we love the hacker approach of storing all your blog posts and pages in a simple Git repository. It makes it very easy to experiment with different designs and drastic site changes in separate branches.

Overall Structure and Approach

The entire solution is based on Pelican. It is a static site generator that lets you write your content in an easy to manage format like reStructuredText or Markdown and then generates the full website from that. It is well documented and really easy to get started. The only downside is that it is targeted at building personal sites that consiste mostly of a blog with several static pages thrown in next to the blog post. We wanted something that could let us grow the non-blog area into a full site albeit a static one.

What if we could host our Pelican in a blog subfolder and generate the rest of our company pages above it? That way we would get a static site with several sections and the blog will be simply a site sub-area. We can certainly do that, but we have to figure a way to share HTML templates and web assets (scripts, stylesheets, and images) across our top site and the blog subfolder.

We can glue the solution together with some Fabric magic. The idea is to build the blog, then build the rest of the site and assemble both of them together in the output folder. We can then rsync the folder contents to our live server. Or, if you prefer so, you can use GitHub pages or some other static file host.

Pelican to Build the Blog

Pelican blog sites can be created with a small make file that can be used to generate the blog HTML. The magic command is usually make html. Here we are calling that from our fabfile residing in the parent folder:

1
2
3
4
5
6
def blog():
    with lcd("blog"):
        local("mkdir -p output")
        local("make html")

    local("rsync -a --delete blog/output/ '%s'" % OUTPUT_BLOG)

The interesing part comes last -- Pelican generates its html in blog/output and we really want it in output/blog (with the rest of the site going to output). rsync to the rescue -- we use it to quickly copy (sync really) the blog output dir with its proper location in our site output.

Using Jinja to Build the Rest of the Site

So, how do we build a static page that will reuse templates defined in our Pelican blog skin? How do we pass all the template variables that Pelican passes to its templates to the page templates? The simplest thing that could possibly work is to embed the Jinja template engine (That's what Pelican uses internally) and use it to render the page templates you need. To access Pelican blog templates, you need to add the Pelican theme templates subdir to the template paths given to the FileSystemLoader object. Here is the Fabric function that renders a template to a specified destination:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from jinja2 import Environment, FileSystemLoader

#...

def render(template, destination, **kwargs):
    jenv = Environment(loader=FileSystemLoader(['.',
                            os.path.join(THEME_PATH, "templates")]))
    params = dict(TEMPLATE_CONTEXT, **kwargs)
    params["PAGE"] = template
    params["SITEURL"] = os.path.relpath(os.path.abspath("."),
                            os.path.dirname(os.path.abspath(template)))
    text = jenv.get_template(template).render(params)
    with open(destination, "w") as output:
        puts("Rendering: %s" % template)
        output.write(text.encode("utf-8"))

The code above uses the Jinja API to create an environment, get a template from it, and render it using the specified parameters. We have three types of parameters here:

  • TEMPLATE_CONTEXT: a statically defined dictionary with template vars defined for all templates. We use it to stick variables that we want available in all templates. Think logo paths, important links, stuff like that.
  • Dynamic variables that are defined for all templates, but have different values. We pass the PAGE name and the SITEURL relative path (more on that one below).
  • Additional parameters: pass those as keyword arguments when calling the render function.

Reusing HTML Templates

Our ultimate goal is to share HTML templates across our main site and our blog. We want to have a consistent design across both areas of the site and share the overall navigation areas and various UI widgets. With the Jinja template rendering code above you already have everything you need. The blog theme has already been configured as a template root, so you can just refer to blog templates by name:

1
{% include 'top-menu.html' %}

Note that you need to take care to avoid name collisions across templates in your two template roots: the site root and the theme one.

You can even go as far as share a single base layout across the blog and the rest of the site, or have the blog layout inherit from the site one. Go wild and tell me what you came up with!

Rough Edges: Relative URLs

It seems Pelican likes relative URLs. That is every page or blog post has a special SITEURL variable defined that will point to the root of the site relative to the current URL. Read that again -- it took me a while to get it. Suppose you are rendering the http://mysite/blog/november/hello-world.html blog post and the Pelican blog root is at http://mysite/blog. That means your SITEURL for the hello-world.html page will be ../../. You can use it to generate links and refer to resources off your blog root. Yes, that means, that an HTML template can refer to a theme image using a Jinja expression like:

1
2
<link rel="stylesheet" type="text/css"
    href="{{ SITEURL }}/theme/css/pygments.css"/>

The above shows a simplified version of the code we use to refer to our Pygments syntax highlighting CSS, for example.

Relative URLs can get annoying really soon. Absolute root-based URL's like /blog are a lot easier to type, but for the time being we can't have those. We have made all that a bit less painful using a Jinja template filter that will append the SITEURL path portion for us. Here is how we use it:

1
2
<img src="{{article.media|media_url(SITEURL)}}"
    alt="post image"/>

The media_url filter has some additional smarts built in and it will not append the site URL portion if it detects an absolute URL. Here's the Python code:

1
2
3
4
5
def media_url(url, site_root):
    if re.match("^https?://", "https://yahoo.com", re.IGNORECASE):
        return url
    else:
        return "%s/%s" %(site_root, url)

I am pretty sure we could access the Jinja template context from the template filter and get the SITEROOT value automatically without having to pass it explicitly, but I leave that as an exercise to the reader.

Another problem with relative URLs is the location of the theme assets. We need images, stylesheets and scripts and when we load them from within the blog we refer to them as ./theme/somefile. The situation is different in the root site where the relative URL now becomes ./blog/theme/somefile. To fix this we define a THEME_URL template variable and set it to different values in the different layout templates. The blog layout has it set in the beginning to:

1
{% set THEME_URL = "theme/" %}

While the root site template has it like this:

1
{% set THEME_URL = "blog/theme/" %}

Stringing a theme asset URL isn't really nice yet. Right now we need an incantation like {{SITEURL}}/{{THEME_URL}}, which is really annoying. We can probably solve the problem with a theme_url template filter similar to the media_url one we already saw.

What's Next?

I find it really convenient to be able to branch off the entire site and blog and experiment with a different look. Having multiple branches for WIP posts and pages is really cool too -- we can merge and rearrange posts as we wish. The deployment is really easy too -- we just have a folder with static content configured in Nginx. We have a Fabric that task takes care of syncing the build output with the server copy, so publishing to the live server is a simple fab deploy command away.

I have pushed an example site that shares templates with a nested Pelican blog much in the same way our company site does on GitHub. It contains a fully-functional site based on the "bootstrap2" Pelican theme along with all the scripts needed to build everything. You can find the Nginx config below the server folder with some rewrites that make up for nicer URLs.