Huit recettes pour Pelican

Après avoir passé beaucoup de temps sur mon blog depuis le passage à Pelican, j’ai décidé de publier quelques recettes que j’utilise.

Libre à vous de les utiliser et de les améliorer. Je suis évidement intéréssé par votre retour.

Arborescence plate

Cette recette n’en est pas vraiment une, mais elle servira de base pour les chemins des autres recettes.

Je l’utilise pour avoir l’arborescence suivante dans mon dépôt git:

├── articles
│   ├── categorie1
│   │   └─ article.rst
│   └── categorie2
├── extras
│   ├─ 404.html
│   └─ robots.txt
├── images
│   ├── theme1
│   │   └─ image.png
│   ├── theme2
│   │   └─ image.png
│   └─ image.png
├── local_plugins
│   └─ plugin.py
├── pages
│   └─ page.rst
├── theme
│   ├── static
│   │   ├── css
│   │   └── images
│   └── templates
│       ├─ template1.html
│       └─ template2.html
├─ .gitignore
├─ Makefile
├─ nginx.conf
├─ pelicanconf.py
├─ publish.sh
├─ publishconf.py
└─ requirements.pip

Pour cela j’ai modifié mon fichier pelicanconf.py comme suit:

PATH = dirname(__file__)
OUTPUT_PATH = join(PATH, 'output')
ARTICLE_DIR = 'articles'
THEME = 'theme'
STATIC_PATHS = ("images", )
FILES_TO_COPY = (
    ('extras/robots.txt', 'robots.txt'),
)
PLUGINS = (
    'pelican.plugins.gzip_cache',
    'pelican.plugins.sitemap',
    'local_plugins.plugin'
)

Délégation d’authentification OpenID

Mon blog Wordpress me servait aussi à m’authentifier grâce à OpenID. Cette modification a donc été la première que j’ai réalisée. Je l’ai réalisée avec MyOpenId comme fournisseur puisque c’est celui que j’utilise, libre à vous de l’adapter à votre fournisseur.

  1. Créez un template theme/templates/myopenid.html:

    {% if MYOPENID_USERNAME %}
    <link rel="openid.server" href="http://www.myopenid.com/server" />
    <link rel="openid.delegate" href="http://{{MYOPENID_USERNAME}}.myopenid.com/" />
    <link rel="openid2.local_id" href="http://{{MYOPENID_USERNAME}}.myopenid.com" />
    <link rel="openid2.provider" href="http://www.myopenid.com/server" />
    <meta http-equiv="X-XRDS-Location" content="http://www.myopenid.com/xrds?username={{MYOPENID_USERNAME}}.myopenid.com" />
    {% endif %}
    
  2. Dans le bloc <head> du template theme/templates/base.html ajoutez:

    {% include 'myopenid.html' %}
    
  3. Dans le fichier de configuration de publication publishconf.py, ajoutez:

    MYOPENID_USERNAME = 'me'
    

Boutons Google +1, Twitter et Flattr

Cette modification de thème permet d’insérer des boutons de partage chargés de façon asynchrone.

Ces boutons ne seront visibles qu’en mode publié puisqu’ils requierent que SITEURL soit définie.

  1. Créez les templates des boutons

    • theme/templates/plusone.html:
    {% if PLUS_ONE and share_url %}
    <span class="g-plusone" data-href="{{ share_url }}" data-size="medium"></span>
    {% endif %}
    
    • theme/templates/twitter.html:
    {% if TWITTER_USERNAME and share_url and share_title %}
    <a href="http://twitter.com/share" class="twitter-share-button" data-count="horizontal" data-via="{{TWITTER_USERNAME}}" data-related="{{TWITTER_USERNAME}}" data-url="{{share_url}}" data-text="{{share_title|striptags}}">Tweet</a>
    {% endif %}
    
    • theme/templates/flattr.html:
    {% if FLATTR_USERNAME and share_url and share_title %}
    <a class="FlattrButton" style="display:none;"
        title="{{share_title}}" href="{{share_url}}"
        data-flattr-uid="{{FLATTR_USERNAME}}"
        {% if article and article.tags %}data-flattr-tags="{{article.tags|join(',')}}"{% endif %}
        {% if FLATTR_LANG  %}data-flattr-language="{{FLATTR_LANG}}"{% endif %}
        data-flattr-button="compact"
        data-flattr-category="text">
        {{share_title}}
    </a>
    {% endif %}
    
  2. Créez les templates des scripts de chargement asynchrone:

    • theme/templates/plusone_script.html:
    {% if PLUS_ONE and SITEURL %}
    <script type="text/javascript">
      window.___gcfg = {lang: '{{PLUS_ONE_LANG}}'};
      (function() {
        var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
        po.src = 'https://apis.google.com/js/plusone.js';
        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
      })();
    </script>
    {% endif %}
    
    • theme/templates/twitter_script.html:
    {% if TWITTER_USERNAME and SITEURL %}
    <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="https://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
    {% endif %}
    
    • theme/templates/flattr_script.html:
    {% if FLATTR_USERNAME and SITEURL %}
    <script type="text/javascript">
    (function() {
        var s = document.createElement('script');
        var t = document.getElementsByTagName('script')[0];
        s.type = 'text/javascript';
        s.async = true;
        s.src = '//api.flattr.com/js/0.6/load.js?mode=auto';
        t.parentNode.insertBefore(s, t);
    })();
    </script>
    {% endif %}
    
  3. A chaque emplacement où vous désirez voir apparaitre ces boutons, insérez le bloc suivant:

    {% if SITEURL %}
        {%- set share_url = [SITEURL, article.url]|join('/') -%}
        {%- set share_title = article.title -%}
        {% include 'twitter.html' %}
        {% include 'plusone.html' %}
        {% include 'flattr.html' %}
    {% endif %}
    

    Modifiez biensur les variable share_url et share_title en fonction du contexte.

  4. Modifiez votre template theme/templates/base.html pour y insérer à la fin du bloc <body>:

    {% if SITEURL %}
        {% include 'twitter_script.html' %}
        {% include 'plusone_script.html' %}
        {% include 'flattr_script.html' %}
    {% endif %}
    
  5. Renseignez vos identifiant et paramètres dans votre configuration de publication pelicanconf.py:

    TWITTER_USERNAME = 'me'
    
    FLATTR_USERNAME = "me"
    FLATTR_LANG = "fr_FR"
    
    PLUS_ONE = True
    PLUS_ONE_LANG = 'fr'
    

Pour adapter les paramètres à vos besoins, consultez les documentations officielles:

Filtre “urlencode” pour Jinja

Jinja 2 ne propose plus de filtre urlencode par défaut (Issue #17 et Pull Request #62).

Si pour une raison ou une autre vous en avez besoin, vous pouvez le rajouter vous même dans votre fichier pelicanconf.py:

import urllib
from jinja2.utils import Markup

def urlencode_filter(s):
    if type(s) == 'Markup':
        s = s.unescape()
    s = s.encode('utf8')
    s = urllib.quote_plus(s)
    return Markup(s)

JINJA_FILTERS = {
    'urlencode': urlencode_filter,
}

Marquer la page active dans le menu

Si vous desirez marquer la page active dans le menu, vous pouvez vous appuyer sur l’astuce Highlighting Active Menu Items.

Voici ma recette adaptée à Pelican:

  1. Modifier votre template theme/templates/base.html pour y ajouter le menu et y déclarer la variable du context active_page:

    {%- set active_page = active_page|default(None) -%}
    <nav id="menu">
        <ul>
            <li {% if active_page == 'index' %}class="active"{% endif %}>
                <a href="{{ SITEURL }}/">Accueil</a>
            </li>
            <li {% if active_page == 'category' %}class="active"{% endif %}><a href="{{ SITEURL }}/categories">Catégories</a>
                <ul>
                    {% for cat, null in categories %}
                    <li><a href="{{ SITEURL }}/{{ cat.url }}">{{ cat }}</a></li>
                    {% endfor %}
                </ul>
            </li>
            {% if DISPLAY_PAGES_ON_MENU %}
            {% for pag in PAGES %}
                <li {% if page == pag %}class="active"{% endif %}>
                    <a href="{{ SITEURL }}/{{ pag.url }}">{{ pag.title }}</a>
                </li>
            {% endfor %}
            {% endif %}
        </ul>
    </nav>
    
  2. Modifiez les pages qui doivent déclarer un nom de page, dans mon cas:

    • theme/templates/index.html:
    {% extends "base.html" %}
    {% set active_page = 'index' -%}
    
    • theme/templates/categories.html:
    {% extends "base.html" %}
    {% set active_page = "category" %}
    
    • theme/templates/category.html:
    {% extends "base.html" %}
    {% set active_page = 'category' -%}
    

    Adaptez-le suivant votre hierarchie de templates et de menu.

Balises META

Cette recette permet déclarer les balises <meta> dans le fichier de configuration.

  1. Déclarez un dictionnaire META dans votre fichier pelicanconf.py:

    from pelican import __version__ as PELICAN_VERSION
    METAS = {
        'author': u'Me',
        'description': u"My blog description",
        'keywords': u'some, keywords, for, seo',
        'generator': u'Pelican %s' % PELICAN_VERSION,
    }
    
  2. Ajoutez dans le bloc <head> du template theme/templates/base.html:

    {% for name, content in METAS.iteritems() %}
    <meta name="{{name}}" content="{{content}}" />
    {% endfor %}
    

Page d’erreur 404

Cette recette fourni une page d’erreur 404 personnalisée avec le thème de votre blog. Elle fonctionne avec NGinx mais est adaptable à tout autre serveur.

  1. Créez le template de votre page d’erreur 404, dans mon cas extras/404.html:

    {% extends "base.html" %}
    {% block content_title %}Erreur 404{% endblock %}
    {% block content %}
    <section class="body page">
        <h1 class="page-title">Erreur 404</h1>
        La page que vous cherchez n'existe pas.
    </section>
    {% endblock content %}
    
  2. Déclarez cette page dans votre configuration pelicanconf.py:

    TEMPLATE_PAGES = {'extras/404.html': '404.html'}
    
  3. Indiquez à NGinx où trouver cette page:

    server {
        # ...
        error_page 404 /404.html;
        # ...
    }
    

Publication par git post-receive hook

Cette recette me permet de déployer mon blog dès que je push sur mon serveur.

A la fin de chaque déploiement, je ping les moteurs de recherche pour les notifier des modification du fichier sitemap.xml généré par le plugin Sitemap.

La configuration de NGinx et les dépendances Python sont elles aussi stoquées dans mon dépôt git, respectivement dans les fichiers nginx.conf et requirements.pip. Dans mon cas, j’utilise virtualenv mais vous pouvez l’adapter à votre configuration.

  1. Sur votre serveur, modifiez ou créez le fichier blog.git/hooks/post-receive:

    #!/bin/sh
    
    STAGING="/home/me/staging/blog"
    
    GIT_WORK_TREE=$STAGING git checkout -f
    cd $STAGING
    ./publish.sh
    

    Ce fichier doit être exécutable.

  2. Dans votre dépôt git, ajoutez le fichier publish.sh à la racine:

    #!/bin/sh
    
    VENV="venv"
    PUBLIC="/path/to/your/public/blog"
    SITEMAP=http://your.blog/sitemap.xml.gz
    
    # Setup virtualenv
    if [ ! -d "$VENV" ]; then
        virtualenv --distribute $VENV
    fi
    . $VENV/bin/activate
    pip install -r requirements.pip --use-mirrors
    
    # Deploy blog
    pelican -v -o $PUBLIC -s publishconf.py
    cp -f nginx.conf /etc/nginx/sites-available/your.blog
    sudo service nginx reload
    
    # Ping sitemap
    GOOGLE=http://www.google.com/webmasters/tools/ping?sitemap=$SITEMAP
    BING=http://www.bing.com/webmaster/ping.aspx?siteMap=$SITEMAP
    
    for url in $GOOGLE $BING; do
        curl -s -w "%{http_code} %{url_effective}\\n" "$url" -o /dev/null
    done
    

    Pour ne pas perdre de temps à chaque push, je commente la ligne qui installe les dépendances python et je la décommente uniquement lorsque je les modifie.

  3. Donnez les droits pour recharger NGinx via sudo à votre utilisateur:

    # Reload nginx command
    Cmnd_Alias NGINX_RELOAD = /usr/sbin/service nginx reload
    
    # User privilege specification
    me  ALL=NGINX_RELOAD, NOPASSWD: NGINX_RELOAD