Using Pelican to generate and manage static websites.

May 2014
Fri 30
0
0
0

Want to avoid a backend? Here's an option

Creating a website inevitably involves a fundamental choice:

  • A static, client-side site (HTML, css, JavaScript)
  • A dynamic, server-side site (PHP, SQL, CGI-enabled)

These two options can also be incorrectly labelled as:

  • Static: Amateur, basic, difficult to manage lots of data
  • Dynamic: Professional, sophisticated, Content-Manangement-System (CMS) enabled

Why is this incorrect? Maybe historically this was closer to the truth, with the default option for a professional website was to involve databases and server-side engines. Clearly for large-scale sites with significant amounts of dynamically changing data this is still the way to go but what about the murky middle ground between a single static page and an all-singing all-dancing dynamic site?

A typical example that falls into this undefined category is a blog. Blogs require a certain level of sophistication that separates them from purely static webpages - a result of the management and rendering of the blog data, i.e. the posts. This problem was recognised a long time ago and blogging platform such as Blogger and WordPress have cornered the market. In fact, as these services are hosted server-side they are free to use a variety of languages and tools. This has resulted in blogging platforms, particularly WordPress, being used for more than blogging. But... and getting back to the subject of this post, what if you want complete control of your site and see a blogging platform as unnecessary overhead? Maybe you want to keep fees to a minimum and also understand what is going behind the scenes of your site? Well a static website generator and file-based CMS maybe the way to go.

Static website generator - A tool for generating and managing HTML/css/js content using a templating framework.

Pelican

There are numerous static site generators out there, a popular choice recently is Jekyll which is written in Ruby. However, given my mother tongue, I was more than happy to find a static website generator written in Python. Pelican is a tool that is built upon the Jinja templating framework. It includes great documentation and themes that provide an instant impression of what Pelican is capable of. There are a few particularly cool features I'd like to point out:

  • Management of content and directory structure.
  • Utilises Fabric automation tools so building and reserving the site is automatic.
  • Integration with Disqus for comment sections.
  • New Meta-Tags added to the static content are readily accessible in the templates.
  • It's written in Python.

Quick start

After downloading and installing Pelican, a site can be 'kickstarted' with the command:

pelican-quickstart

this builds your development directory structure. The most important file in the root of this directory structure is pelicanconf.py which contains global configuration information and is itself a Python script - this is very useful and maximises user customisation.

Themes

The next thing to do is to copy a theme to your dev directory. By default these are installed in your site-packages/pelican directory. It is a good idea to copy the theme notmyidea as this includes the most features. Once copied, point your Pelican build to this theme in your pelicanconf.py file:

THEME = 'my_theme'

Content

With your dev directory in place complete with a theme, it is time to add some test content. In your dev directory you will have a content subdirectory for storing your content in your favourite format, which for me is Markdown. Each item of content requires meta-data that is used by the templating language in your theme to build the website. For example in Markdown this post looks like this (if you haven't guessed by now this site is generated using Pelican):

Title: Using Pelican to generate and manage static websites.
Date: 2014-05-30 10:20
Category: Python
Tags: Pelican, CMS, websites, Markdown
Slug: pelican_python
Author: Marc Robinson
Summary: Want to avoid a backend? Here's an option

Creating a website inevitably involves...

The meta-data in the header above provides anchors for the html templates.

Templates

Within your theme there will be two directories:

  • static - images, css and js
  • templates - html templates

The contents of static are self explanatory and contain files that will be included as is. It is in the templates directory where the magic happens. Of the html files in templates, index.html and base.html are the most important. These contain the main body of the site. Let's have a look at index.html which is used to populate the index pages containing the summaries of each post:

{% extends "base.html" %} {% block content_title %}{% endblock %} {% block content %}
{% if articles %}
{% for article in articles_page.object_list %}
<div class="post_container">
    <div class="post_short_content" >
            <div id="tag_container">
                {% include 'taglist.html' %}
            </div>
            <h1 class="entry-title">
                <a href="{{ SITEURL }}/{{ article.url }}">{{ article.title }}</a>  
            </h1>
            {% include 'article_infos.html' %}
            <h2 id="summary_container">{{ article.summary }}</h2>    
    </div>
</div>

{% endfor %}

<div class="paginator_container">{% include 'pagination.html' %}</div>

{% else %}
<section id="content">
    <h2>Pages</h2>
    {% for page in PAGES %}
    <li><a href="{{ SITEURL }}/{{ page.url }}">{{ page.title }}</a>
    </li>
    {% endfor %}
</section>
{% endif %}
{% endblock content %}

The tags contained in {% and {{ are Jinja entry points and allow us to perform basic Python operations such as if statements and for loops as well functions specific to the templating such as include and extend. As may be apparent from the above example, {% tags contain operations where as {{ tags simply evaluate and print.

Aside: As the Jinja tags are wrapped around the html, it is important to understand a variables scope when defined inside each Jinja block.

Concentrating on the outer for loop we can see global python objects created by Pelican, such as articles and article_page. Looping through each article we can use meta-data such as article.summary to populate <div> blocks. We can also see object properties that were not defined in the Markdown meta-data such as 'article.url' which contains the relative link to the post.

Pages and Archives

In addition to the post content that is indexed, Pelican also provides a means of including static content that can be used for common website features such as About or Contact pages. This content is stored in the content/pages directory and again can be formatted using a variety of markup languages.

The default Pelican themes also include templates for an archive page to list posts by date. This can be easily customised in both the template and in css to produce any sort of calender or list based style.

Searching

Now this isn't as easy as simply rendering content. Searching a website is traditionally done via the backend with a more powerful server-side language. However, as we not dealing with a huge amount of data it is possible to use client-side JavaScript. I must show my appreciation at this point to Talha Mansoor who has produced a Pelican theme that uses Tipue Search - a JQuery search engine plugin. Tipue Search can be used in many ways but the way it is used in Pelican is through JSON. The content of the website is serialised into JSON using BeautifulSoup which can be loaded and quickly searched using Tipue Search. To enable searching of you Pelican website do the following:

  1. Download the Tipue Search plugin for Pelican.

  2. Enable the Tipue Search plugin in your pelicanconf.py file:

    PLUGIN_PATH = ["plugins", "/path/to/pelican-plugins"]
    PLUGINS = ['tipue_search',]
    
  3. Download tipuesearch which includes a few js and css files.

  4. Put the tipuesearch directory into your my_theme/static/js dev directory.

  5. Add a search.html template file to my_theme/templates:

    {% extends "base.html" %}
    {% block content_title %}
    {% endblock %}
    {% block content %}
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
    <script type="text/javascript" src="{{ SITEURL }}/my_theme/js/tipuesearch/tipuesearch_set.js"></script>
    <script type="text/javascript" src="{{ SITEURL }}/my_theme/js/tipuesearch/tipuesearch.js"></script>
    
    <script>
    $(document).ready(function() {
         $('#tipue_search_input').tipuesearch({
             {% if 'tipue_search' in PLUGINS %}
                 'mode' : 'json',
             {% else %}
                 'mode': 'live',
             {% endif %}
             'show': 10,
             'newWindow': false,
              'showURL' : false,
             {% if 'tipue_search' in PLUGINS %}
                 'contentLocation': 'tipuesearch_content.json'
             {% else %}
                 'liveDescription': '.article-content'
             {% endif %}
         });
    });
    </script>
    <div id="post_container">
    <div id="tipue_search_content"><div id="tipue_search_loading"></div></div>
    </div>
    {% endblock content %}
    
  6. Add search.html to the DIRECT_TEMPLATES tuple in pelicanconf.py:

    DIRECT_TEMPLATES =  (('index', 'tags', 'categories', 'archives',
                          'authors','search','disqus_script'))
    
  7. Add a search form which links to search.html on your main template page (i.e. base.html):

    <form class="search_form" id="main_search_form" action="{{ SITEURL }}/search.html" onsubmit="return validateForm(this.elements['q'].value);">
    <div class= "search_form" id="search_button" ></div>
    <span class="search_form" ><input class="search_form" type="text" placeholder="Search" id="tipue_search_input" name="q"/></span>
    </form>
    

Polishing with CSS and JavaScript.

If you are serious about producing a professional looking site, hopefully you'll have a solid design in mind before beginning any coding. One of the main things to ensure these days is that your site is viewable and usable of different sized devices. Dynamic resizing or hiding/showing sections can be achieved through css @media tags or through JavaScript. Modifying the site based on scrolling (i.e. if you want a header to shrink as on this site) can be easily accomplished using the JavaScript window.onscroll event to modify <div> heights or margins.

Benefits

Once you have created your site you now have the flexibility to host it pretty much anywhere. The liberation from a back-end server (which also helps with the development of the site as a local server such as Apache is not needed) means you can host using resources such as GitHub Pages for free.

You have maximum control of the design and implementation of the site. Recent experiences with blogging platforms have left me a bit frustrated and in the dark to what's going on under the hood.

Content can be stored locally, on Dropbox or in a repository or anywhere you like. You can then use your favourite markup-based editor to write your content away from the site directory itself.

There are also some features I'm yet to try, such as producing posts as drafts to share before publishing.

Overall I'm very impressed with this Python based offering and highly recommend Pelican for anyone wanting a well managed, simple yet sophisticated site.




Comments