Writing custom Pelican themes
The built in configuration options in Pelican are fairly flexible, however Pelican can also be extended using custom themes. Following on from the previous post, this post is going to look at writing a simple custom theme for Pelican.
Using themes
By default Pelican uses the notmyidea
theme, however it also ships with a
simple
theme. Both themes can be found in the themes
directory. The THEME
setting in penicanconf.py
can be used to set a theme explicitly:
THEME = 'simple'
Alternatively the -t
option can be used when the pelican
command is run:
pelican -t some_theme -r $INPUTDIR -o $OUTPUTDIR -s $CONFFILE $PELICANOPTS
The first place you should look for additional themes is
www.pelicanthemes.com. This provides a quick overview of the
themes in the pelican-themes repository. To use a
third party theme, just download it and use the THEME
setting to point to the
theme directory:
THEME = '/home/user/pelican/themes/example_theme'
Note: either absolute or relative paths can be used.
Using the pelican-themes command
Pelican comes with a pelican-themes
command. This can be used to list
available themes:
$ pelican-themes --list
notmyidea
simple
It's also possible to install new themes:
# Download and unpack a copy of the pelican-themes repo
curl -L -o pelican-themes.tar.gz https://github.com/getpelican/pelican-themes/archive/master.tar.gz
tar xzf pelican-themes.tar.gz
# Install a theme
pelican-themes --install pelican-themes-master/bricks/
Once a theme is installed it can be referenced by the THEME
setting without
requiring a path to the directory. Themes can also be removed using the
--remove
option:
pelican-themes --remove bricks
Theme structure
Pelican themes are made up of two directories, static
and templates
. The
templates
directory contains Jinja templates which are used to
create HTML output; and the static
directory contains any supporting static
content such as style sheets or images.
The Pelican docs on creating themes list the following structure:
+-- static
¦ +-- css
¦ +-- images
+-- templates
+-- archives.html // to display archives
+-- period_archives.html // to display time-period archives
+-- article.html // processed for each article
+-- author.html // processed for each author
+-- authors.html // must list all the authors
+-- categories.html // must list all the categories
+-- category.html // processed for each category
+-- index.html // the index (list all the articles)
+-- page.html // processed for each page
+-- tag.html // processed for each tag
+-- tags.html // must list all the tags. Can be a tag cloud.
However you can get away with a subset of the templates above if you're not
using a particular feature. For example if you've set ARCHIVES_SAVE_AS = ''
the archives.html
template will not be required.
Creating a custom theme
The Pelican docs on creating themes suggest starting with
the "simple" theme . For the example in this post I'm
going to run through an even more straightforward example which just covers the
index.html
and article.html
templates. However you may want to dives
straight into the simple
theme if you're already happy with Jinja templates.
Pelican config
Initially it's often useful to focus on a subset of the require templates. The
config below disables the tag, author, category, and archive pages generated by
Pelican, and sets the theme to custom
:
ARCHIVES_SAVE_AS = ''
AUTHOR_SAVE_AS = ''
AUTHORS_SAVE_AS = ''
CATEGORY_SAVE_AS = ''
CATEGORIES_SAVE_AS = ''
TAGS_SAVE_AS = ''
THEME = 'custom'
Note: interestingly CATEGORY_SAVE_AS
and AUTHOR_SAVE_AS
will both cause
errors if they are set to None
.
Folder structure
Running the following commands will set up an initial folder structure:
mkdir -p custom/{templates,static/css}
touch custom/templates/{index,article}.html
touch custom/static/css/main.css
The resulting structure should look something like this:
+-- custom
+-- static
| +-- css
| +-- main.css
+-- templates
+-- article.html
+-- index.html
From here it should be possible to run Pelican and produce output similar to the following:
+-- theme
| +-- css
| +-- main.css
+-- hello-world.html
+-- index.html
Note: the generated files will initially be empty until the templates are updated.
Article template
A very simple article template might look something like this:
<!DOCTYPE html>
<html>
<head>
<title>{{ article.title }}</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="/{{ THEME_STATIC_DIR }}/css/main.css">
</head>
<body>
{{ article.content }}
</body>
</html>
Variables inside {{ }}
will be replaced with content by Pelican. Variables
such as article.title
are taken from the article metadata, and
THEME_STATIC_DIR
is taken from the Pelican configuration file. The Pelican
docs give more detail on available article
attributes.
Index template
An index template that lists available articles could look something like this:
<!DOCTYPE html>
<html>
<head>
<title>{{ SITENAME }}</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="/{{ THEME_STATIC_DIR }}/css/main.css">
</head>
<body>
<h1>Articles</h1>
<ul>
{% for article in articles_page.object_list %}
<li><a href="/{{ article.url }}">{{ article.title }}</a> </li>
{% endfor %}
</ul>
</body>
</html>
Code inside {% %}
will be run when the template is rendered and will not be
present in the final output. Again the Pelican theme
docs have more info on available variables.
Template inheritance
The two templates above have a lot of shared content. To avoid duplication
templates can be extended. The first thing to do is setup a base template
called templates/base.html
with content similar to the following:
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}{{ SITENAME }}{% endblock %}</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="/{{ THEME_STATIC_DIR }}/css/main.css">
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>
The block
sections are used to mark content which can be overridden by
templates which extend the base. It's worth noting that blocks can have
defaults, for example the title above defaults to the value of SITENAME
.
The article template above can now be changed to something similar to the following so that it extends the base template:
{% extends "base.html" %}
{% block title %}{{ article.title }}{% endblock %}
{% block content %}
{{ article.content }}
{% endblock %}
The index template can also be simplified in a similar manner:
{% extends "base.html" %}
{% block content %}
<h1>Articles</h1>
<ul>
{% for article in articles_page.object_list %}
<li><a href="/{{ article.url }}">{{ article.title }}</a> </li>
{% endfor %}
</ul>
{% endblock %}