Writing a Pelican Plugin to output a carousel
A simple plugin to take a comma separated list of images and generate a bootstrap carousel.
Last modified: 2018-06-02
Pelican is deliberately lightweight and therefore doesn't necessarily have everything you might want from it out of the box. I use it to generate my portfolio and projects pages and now, thanks to my increasing frustration with WordPress my blog, it's quick and easy to use particularly for things like this which don't need a full CMS backing them. However, for my portfolio page I wanted each project to have a carousel of images associated with it, something which Pelican can't do natively.
So I wrote a very simple plugin which literally just takes a comma separated list of image files and allows you to output them in the format you prefer.
Input
Carousel: image1.jpg, image2.png, image3.svg, does_not_exist.png, no_file_extension, wrong_file_type.pdf
The input should be a list of image files which you place into content > images
however human error being what it is, the plugin should first check to make sure:
- that the file has an extension
- that the extension is in the list of acceptable image files
- that the image does in fact exist
only if those conditions are met should it output the image url.
Output
for image in article.carousel
should iterate over the valid image files in the list.
Jinja2 / Bootstrap 4 carousel
{% if article.carousel %}
<div class="carousel slide rounded" id="{{ article.slug }}Carousel" data-interval="false">
<div class="carousel-inner" role="listbox">
{% for image in article.carousel %}
<div class="carousel-item{{ ' active' if loop.index == 1 }}">
<img class="d-block img-fluid rounded" src="{{ image }}">
</div>
{% endfor %}
<a class="carousel-control-prev" href="#{{ article.slug }}Carousel" role="button" data-slide="prev">
<i class="fa fa-chevron-left" aria-hidden="true"></i>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#{{ article.slug }}Carousel" role="button" data-slide="next">
<i class="fa fa-chevron-right" aria-hidden="true"></i>
<span class="sr-only">Next</span>
</a>
</div>
</div>
{% endif %}
The back end
In order to make a plugin for Pelican the only thing that is absolutely required is to define a register
callable that to map a signal to your plugin logic. There's a fair number of signals and which one you need depends on what you're trying to do. In this instance I want to jump in on article generation so the signal I need is article_generator_write_article
.
Technically, once registered, via adding the following to pelicanconf.py
:
PLUGIN_PATHS = ["plugins/carousel"]
PLUGINS = ["carousel"]
this plugin would work with just the following:
from pelican import signals
def generate_carousel():
pass
def register():
signals.article_generator_write_article.connect(generate_carousel)
But that's neither terribly interesting nor useful. So lets make it do some stuff. If we want to be able to change defaults and check if images exist we need to do some additional setup:
EXTENSIONS = ['jpg', 'png', 'svg']
def set_defaults(settings):
settings.setdefault('CAROUSEL_IMAGES_FOLDER', 'images')
def init_default_config(pelican):
from pelican.settings import DEFAULT_CONFIG
set_defaults(DEFAULT_CONFIG)
if pelican:
set_defaults(pelican.settings)
In order to use the data in the article you need to pass it to your function:
def generate_carousel(generator, content):
if 'carousel' in content.metadata.keys():
if len(content.metadata['carousel']) > 0:
carousel = content.metadata['carousel'].split(',')
del_these = []
folder = generator.settings.get('CAROUSEL_IMAGES_FOLDER')
# remove all whitespace and check that the filename is valid
for idx, item in enumerate(carousel):
if item[-3::] in EXTENSIONS and exists('{}/{}/{}'.format(generator.settings['PATH'], folder, item.lstrip())):
carousel[idx] = '{}/'.format(folder) + item.lstrip()
else:
del_these.append(item)
# remove any filenames with extensions that aren't in the list
for item in del_these:
carousel.remove(item)
# make it accessible from the app context
content.carousel = carousel
Make sure the defaults are registered.
def register():
signals.initialized.connect(init_default_config)
signals.article_generator_write_article.connect(generate_carousel)
Add a __init__.py
to the folder with the following content:
from .carousel import *
Congratulations, you have a pelican plugin.
Comments powered by Talkyard.