voidynullness

(mis)adventures in software development...

    
25 June 2015

How to add a list of recent posts to a Pelican blog

Share
Category Web Development

And some other random Pelican tips and tricks...

List of Recent Posts

If you’re a blogger, and if your blogging platform of choice is Pelican (And if not why not? Pelican really is quite nice!), and if you’d like to have a list of recent posts somewhere in the page layout, but the Pelican theme you’re using doesn’t have built in support for this, it’s not too difficult to add, as long as you’re comfortable messing about with the Jinja templates of the theme in question.

The Jinja template code to accomplish this is reasonably straightforward, although you need to take into account that the articles variable means something different depending on which index page is being rendered. In index.html, articles is a list of all articles. In all other index pages (i.e. author, category, tag), it’s only a subset relevant to that index — the list of all articles is instead available as a variable called all_articles.

Here is an example that will render the 6 most recent posts as an unordered list.

<h4>RECENT POSTS</h4>
{% set recent = articles if not all_articles else all_articles %}
<ul id="recent-posts">
{% for a in recent %}
  {% if loop.index <= 6 %}
    <li>
      <a href="{{ SITEURL }}/{{ a.url }}">{{ a.title }}</a>
    </li>
  {% endif %}
{% endfor %}
</ul>

Place the above snippet in an appropriate place in your theme’s Jinja templates and style to taste.

Now With Added JSON

But what if — maybe, for some reason — you’d like to get details of recent posts in JSON format instead? Why would anyone want to do that, you ask?

Well, to provide a contrived example of using Pelican’s Template Pages functionality, for one thing.

I recently discovered the TEMPLATE_PAGES setting, and have been exploring its possibilities. It’s quite nifty. Yes, I used the word “nifty”. Yes, I probably need to get out more.

TEMPLATE_PAGES is also somewhat vaguely documented. It’s purpose is to basically enable creation of any kind of other custom content, besides articles and pages. The idea is you can add a template, which can have any kind of arbitrary content (and need not inherit from the theme’s base.html), and specify where the output should go.

Since the template can be pretty much anything, it doesn’t have to be HTML. We can generate JSON output, if we so desire.

Let’s say we were to create a file called recent.json in our theme’s templates directory, with the following contents:

{
  "recent": [
      {% set recent = articles if not all_articles else all_articles %}
      {% for a in recent %}
        {% if loop.index <= 6 %}
          { "url": "{{ SITEURL }}/{{ a.url }}",
            "title": "{{ a.title }}"
          } {% if not loop.last and loop.index < 6 %},
          {% endif %}
        {% endif %}
      {% endfor %}
  ]
}

Then we add this to our pelicanconf.py:

TEMPLATE_PAGES = {'recent.json': 'api/recent.json', }

TEMPLATE_PAGES is a dictionary mapping template to output. So in the above example, we’re telling Pelican to render the templates/recent.json template and place the output into output/api/recent.json (assuming the Pelican output directory is configured to be “output”). That file is now accessible on the /api/recent.json URL path in the usual way, and available for all sorts of AJAX mischief.

As an example, here’s some jQuery that consumes the above JSON output to render a list of recent posts dynamically:

$(document).ready(function() {
    $( "#payload" ).html("<p>...loading...</p>");
    $.getJSON("/api/recent.json",
            function(data) {
                var items = [];
                $.each( data.recent, function( index, val ) {
                    items.push( '<li><a href="' + val.url +'">' + val.title + '</a></li>' );
                });
                var newhtml = '<ul id="recent-posts">';
                newhtml += items.join( "" );
                newhtml += '</ul>';
                $( "#payload" ).html(newhtml);
            }
    );
});

Not that I’m in any way suggesting this method of generating a list of recent posts is preferable to the previous method (static generation via Jinja template). It is merely an example of what’s possible. Sensible use cases are left as an exercise for the reader…and possibly future blog posts.

Fail Faster

Another occasionally useful but vaguely documented Pelican setting is WRITE_SELECTED.

If you have a blog with a considerable amount of content, you may notice it takes entire seconds to generate your static site. Probably many, many seconds. Especially if Typogrify is turned on, which tends to slow things down a bit. If there’s really lots of content and/or you’re running on a really slow (virtual) machine, it might even take a minute or more to regenerate your site.

And the more content you write, the slower things will only get.

Seconds. Maybe a minute. In this world of instant gratification, who’s got time to wait seconds for a build?

Those seconds really add up when doing theme tweaks, or working on getting an article’s markup just right.

If you’re anything like me, you want The Machine to pass judgement on your substandard markup and declare it to be unparsable garbage as soon as possible.

Thankfully, Pelican has got you covered. If you’re just tweaking a page or two, you can temporarily use the WRITE_SELECTED setting in your pelicanconf.py to only output the page or pages you’re currently editing:

WRITE_SELECTED = ['output/blog/2015/01/01/path-to-blog-post/index.html',
                  'output/blog/pages/stuff/index.html',
                 ]

The slightly tricky thing is that WRITE_SELECTED apparently needs to be set to a list of full paths the the output files, including output directory. These will obviously depend on your URLs settings and output directory configuration.

With WRITE_SELECTED set only those files listed will be regenerated, which should make things substantially faster.

Just remember to remove the WRITE_SELECTED setting before publishing and uploading the final finished results, to make sure everything gets refreshed properly.