Page de « listing »

Avec le chapitre précédent c’est un bon gros morceau qui est abattu ! Ce chapitre ici fera un peu office de pause :) Il sera plus léger et les pages que nous allons réaliser sont moins primordiales pour les visiteurs. J’espère cependant que vous le trouverez tout aussi intéressant !

Nous allons ici créer les pages de « listings ». Ce sont les pages qui liste les articles correspondants à une catégorie, un auteur ou un tag. Il existe plein de manières de présenter ces différentes pages (comme par exemple faire un nuage de mots pour une page qui recense tous les tags), mais nous nous contenterons de faire simple ici et toutes les pages auront donc la même présentation, des listes !

Les pages de listes

Comme dit dans l’intro, plusieurs pages de "listings" existent et on peut les classer en deux catégories :

  • Les pages de "Haut niveau" qui listent les auteurs, les catégories ou encore les tags ;
  • Les pages de "listes d’articles" qui vont lister tout les articles d’une page de haut niveau, par exemple tout les articles écrits par Eskimon.

Les pages de haut niveau

On trouve 3 pages de haut niveau, que nous avons déjà très brièvement vu dans l’arborescence des fichiers du thème. Il s’agit des fichiers suivants :

  • authors.html : Qui liste tous les auteurs ayant au moins écrit un article sur votre site ;
  • categories.html : Qui liste toutes les catégories contenant au moins un article ;
  • tags.html : Qui liste tous les tags contenant au moins un article.

Vous remarquez que ces noms de fichiers sont en anglais, afin de bien illustrer qu’ils listent les éléments eux mêmes (les tags) par exemple) et non pas le contenu d’un élément (les articles d’un tag). En effet, ces derniers sont gérés par les pages de listes d’articles.

Les pages de listes d’articles

Et c’est avec cette superbe transition que je vais vous présenter les pages qui vont servir à donner le détail du contenu d’une page de haut niveau. Par exemple, tous les articles écrits par Eskimon et personne d’autres.

Ces pages ont le même nom que leur pendant de haut-niveau, mais au singulier. Pratique non ? Logiquement, on trouve donc :

  • author.html : Qui liste tous les articles d’un auteur ;
  • category.html : Qui liste tous les articles d’une catégorie ;
  • tag.html : Qui liste tous les articles d’un tag.

Maintenant que les présentations sont faites, voyons comment nous allons construire tout cela.

Ces listes ne sont pas immuables. En effet, certains plugins (comme le fameux subcategories) peuvent rajouter de nouveaux fichiers de listings qu’il faudra alors créer.

Les listes de « haut niveau »

Nous allons dès à présent voir le principe de réalisation de nos différentes pages. Là encore nous allons le faire en deux étapes, les pages de haut-niveau et celle listant des articles. Deux astuces vont être utiles ici, la généralisation de codes et l’utilisation de la balise include. Commençons par les pages de haut niveau.

Modèle générale

Prenons comme exemple la page listant les catégories et partons du principe que nos articles se divisent pour le moment en deux catégories : "Cookies" et "Gateaux". L’objectif de la page categories.html sera donc de nous lister ces deux catégories, avec en bonus le nombre d’articles que chacune possèdent.

Lorsque l’on va faire un tour sur la documentation de création de thème de Pelican, on voit que toutes les pages recoivent les variables contenant tout les auteurs (authors), les catégories (categories) ou encore les (tags). Il nous faut donc utiliser la bonne sur la bonne page. Ensuite, une simple boucle for permet de générer la liste et hop, le tour est joué !

Par exemple, voilà ce que l’on pourrait faire :

{% extends "base.html" %}


{% block content %}

<h1 class="list-title">Liste des catégories</h1>
{% for cat, articles in categories|sort %}
<section class="list-element">
    <h2 class="list-element-title">
        <a href="/{{ cat.url }}">{{ cat }}</a>
    </h2>
    <div class="list-element-count">
        ({{ articles|count }} article{% if articles|length > 1 %}s{% endif %})
    </div>
</section>
{% endfor %}

{% endblock %}

C’est somme toute assez simple. Tout d’abord, on "étend" le squelette base.html, sans quoi on n’aurait pas la base même de la page. Ensuite, on personnalise le block "content". Dans ce dernier, on placera tout d’abord un titre ("Liste des catégories") puis ensuite, une boucle for va itérer sur la variable categories pour en resortir à chaque tour une catégorie (variable cat) et la liste d’articles contenu dans cette dernière (variables articles). Enfin, on construit une section dans la boucle for pour placer tout ses éléments.

Voici un exemple de rendu que l’on obtient en visitant la page http://localhost:8000 (rappel, il faut que le serveur de développement soit en marche) :

Rendu de la liste des catégories
Rendu de la liste des catégories

(CSS ci-dessous)

.content {
  flex-grow: 1;
  margin-left: 20px;
  border: 1px solid rgba(0,0,0,.125);
  border-radius: .25rem;
  padding: 20px;
}

h1, h2, h3, h4, h5 {
  font-weight: 500;
}

.list-title {
  margin-top: 0;
}

.list-element {
  padding: 5px;
  border-left: 1px solid rgba(0,0,0,.375);
  border-radius: .25rem;
  margin: 10px;
  padding-left: 10px;
}

.list-element-title {
  margin-top: 0;
  margin-bottom: 10px;
}

Avez-vous remarqué l’utilisation de la structure {% if ... %} ... {% endif %} pour mettre au pluriel le mot article ?

Un peu d’optimisation

Bon c’est chouette, on a un truc qui tourne et donne un rendu. Maintenant il suffit de copier/coller ça pour les tags et les auteurs, modifier le titre et en avant Guingamp n’est ce pas ?!

:waw:

Mauvais réflexe ! Le copier-coller n’est pas toujours la solution ! Imaginons nous faisons une modification sur le nom d’une classe par exemple, eh bien il ne faudrait surtout pas oublier de la refaire dans les autres fichiers, sinon on aurait un site qui serait propre par endroit, mais avec des vieux morceaux à d’autres !

Bon ok je chipote un peu pour 3 fichiers, mais quand même, pour le principe (et pour l’exercice) on va faire en sorte de rendre notre code le plus générique possible. Ainsi, en donnant un bon gros coup de hache dedans, on va se retrouver avec ça :

{% extends "base.html" %}

{% block content %}
<h1 class="list-title">Liste des catégories</h1>
{% set _object = categories %}
{% include 'parts/high-level-list.part.html' %}
{% endblock %}

Léger non ? Que s’est-il passé ?

Tout d’abord, on a gardé le titre du contenu, puisque ce dernier ne peut pas être deviné. Ensuite, on crée une variable avec le mot-clé set et la syntaxe {% set ma_variable = ma_valeur %}. Ici la variable s’appellera _object et aura pour valeur la variable globales de Pelican categories (l’underscore devant le nom servant par convention à symboliser une variable privée dans de nombreux langages).

Ensuite, on insère un fragment de code situé dans le dossier parts et se nommant high-level-list.part.html. C’est ce dernier qui ira construire notre liste de catégories (en fait quelque soit la liste dans _object) comme précédemment, seule la variable utilisée change.

{% for obj, articles in _object|sort %}
<section class="list-element">
    <h2 class="list-element-title">
        <a href="/{{ obj.url }}">{{ obj }}</a>
    </h2>
    <div class="list-element-count">
        ({{ articles|count }} article{% if articles|length > 1 %}s{% endif %})
    </div>
</section>
{% endfor %}

Vous cernez la flexibilité du truc ? Maintenant pour les pages tags.html et authors.html il y a vraiment un minimum de choses à reprendre et modifier ! On gagne du temps et si jamais on veut bidouiller la liste on a un seul fichier à modifier.

Par exemple, pour la liste des auteurs, dans authors.html on aura :

{% extends "base.html" %}


{% block content %}
<h1 class="list-title">Liste des auteurs</h1>
{% set _object = authors %}
{% include 'parts/high-level-list.part.html' %}
{% endblock %}

On a juste eu besoin de remplacer le titre et la variable à utiliser et tout le reste s’est fait auto-magiquement !

Je vous laisse faire la page tags.html, ça ne devrait pas vous poser trop de soucis !

Pour visiter ces trois pages et en voir le rendu, rendez-vous aux adresses suivantes :

Bien entendu cette astuce de factorisation marche bien si vous voulez avoir un rendu homogène sur ces 3 pages. En revanche, si vous souhaitez faire un style différent à chaque fois dans ce cas il n’y a évidemment pas d’astuce particulière, il faudra personnaliser le template idoine de la bonne façon selon vos rêves et désirs !

Les listes d'articles

Il ne nous reste plus qu’à faire les listes d’articles et nous aurons fait le tour de ce type de page !

Si vous avez bien compris la section précédente, alors celle-ci sera du gâteau :lol: ! À vrai dire vous pourriez presque déjà la faire sans moi, il suffit juste de trouver quelles variables utiliser pour générer la liste.

Comme vu précédemment, les listes d’articles sont les détails des listes de haut niveau. Ainsi, on va là encore trouver trois pages à créer / éditer : author.html, category.html et tag.html. Comme l’explique la documentation, chacune de ses pages proposes une variable correspondante au contenu de haut niveau en cours d’affichage, donc respectivement author, category et tag. On y trouvera aussi une variable articles qui contiendra les articles de la page étudiée.

Ainsi, avec toutes ses informations on peut faire un premier jet de réalisation similaire à la précédente. Cette fois-ci nous afficherons un peu plus d’informations, en effet les articles sont riches en métadonnées, profitons-en pour aider le lecteur à choisir un contenu qui lui plaît ! Voici pour une page présentant une catégorie. Je pense qu’avec tout ce que nous avons vu jusqu’à présent, ce code ne devrait pas vous poser trop de souci :) .

{% extends "base.html" %}


{% block content %}
<h1 class="list-title">Article de la catégorie "{{ category }}"</h1>

{% for article in articles %}
<section class="list-element">
    <h2 class="list-element-title">
        <a href="/{{ article.url }}">{{ article.title }}</a>
    </h2>
    <div class="list-element-content">
        <p>
            <em>Écrit par <a href="/{{ article.author.url }}">{{ article.author }}</a> le {{ article.locale_date }}.</em>
        </p>
        <p>
            {{ article.summary }}
        </p>
    </div>
</section>
{% endfor %}

{% endblock %}

Si vous souhaitez utiliser une liste d’articles triés par date et non pas par titre, vous pouvez utiliser la variable dates au lieu de articles.

Rendu d'une liste d'articles
Rendu d'une liste d'articles

Avez-vous remarqué que les variables pour parcourir tout les contenus sont articles (au pluriel, contenant la liste des articles de cette catégorie) et article (au singulier, l’article actuellement étudié à chaque tour dans la boucle for). C’est une convention d’écriture assez habituelle, alors n’ayez pas peur de l’utiliser : for singulier in pluriel

Factorisation

Comme pour les listes de haut-niveau, là encore nous pouvons déplacer une bonne partie du code dans un fichier part et ainsi réutiliser sans peine le code pour toutes nos listes d’articles. Voici ma version allégée, le code ne changeant pas énormément et ne voulant pas répéter ce que vous connaissez déjà je vous laisse l’analyser vous-même. Aussi, il ne faut pas oublier de reproduire ce schéma pour les pages tag.html et author.html.

{% extends "base.html" %}


{% block content %}

<h1 class="list-title">Article de la catégorie "{{ category }}"</h1>

{% include 'parts/articles-list.part.html' %}

{% endblock %}
{% for article in articles %}
<section class="list-element">
    <h2 class="list-element-title">
        <a href="/{{ article.url }}">{{ article.title }}</a>
    </h2>
    <div class="list-element-content">
        <p>
            <em>Écrit par <a href="/{{ article.author.url }}">{{ article.author }}</a> le {{ article.locale_date }}.</em>
        </p>
        <p>
            {{ article.summary }}
        </p>
    </div>
</section>
{% endfor %}

Annexe : La pagination

Qui dit liste, dit souvent pages. En effet, notre exemple ici ne paie pas de mine, mais on est pas à l’abri qu’un jour on ait beaucoup d’articles. On risquerait alors de se retrouver avec une liste longue comme le bras, avec une page mettant du temps à charger et pénible à naviguer.

Une solution : La pagination.

J’ai brièvement évoqué cet aspect dans le début du tutoriel, il est temps de le mettre en pratique.

Tout d’abord, il faut repérer le paramètre DEFAULT_PAGINATION dans le fichier pelicanconf.py. La valeur de ce dernier va servir à régler combien d’articles par page seront affichés. Une valeur de 5 par exemple permettera d’afficher 5 articles sur chaque page. Donc si on a 20 articles dans une catégorie, l’affichage du contenu de cette dernière se fera sur 4 pages distinctes.

Puisque nous parlons de l’exemple des catégories, retournons voir la documentation de ces dernières. Tout d’abord, on apprend que si plusieurs pages sont nécessaires pour afficher la liste des articles, alors elles seront formatées selon la convention category/{category_name}{number}.html. La page 1 contiendra les articles 1 à 5, la n°2 les 6 à 10 etc.

Ensuite, le tableau nous affiche le nom et l’utilité des variables supplémentaires spécifiques à cette page. Cinq attirent notre attention :

  • articles_paginator : Un objet de type paginator, utile pour gérer la pagination ;
  • articles_page : Un objet de type Page représentant la page courante. Je reviens tout de suite là dessus ! ;
  • articles_previous_page : Un objet de type Page représentant la page précédente ;
  • articles_next_page : Un objet de type Page représentant la page suivante ;
  • page_name : Nous servira à contruire les liens pour accéder aux différentes pages. Ce dernier représente le lien de "base" de la page (par exemple category/cookie pour n’importe quel numéro de page de la catégorie cookie).

L’objet Page

Ce type de variable transporte plein d’informations intéressantes, notamment pour créer notre joli pagination. Je ne vais pas rentrer dans le détail de tout ce qu’il propose, cependant sachez que son code-source-pas-si-compliqué est disponible ici : https://github.com/getpelican/pelican/blob/master/pelican/paginator.py.

Le premier élément nous qui nous intéresse sera la variable object_list de cet objet. Cette dernière nous met à disposition la liste des articles contenus dans uniquement cette page (donc au plus 5 articles si la pagination est paramétré à 5 articles par page). Via les variables mentionnées précédemment, on utilisera articles_page.object_list pour les articles de la page courante.

Un autre élément est l’accès aux fonctions has_previous et has_next. Ces deux fonctions renvoient un booléen si il y a effectivement une page précédente ou suivante par rapport à la courante. On pourra alors utiliser previous_page_number et next_page_number pour avoir le numéro de la page précédente et celui de la suivante.

Enfin, on peut aussi obtenir l’url de la page via la variable url (category/cookie2.html pour la deuxième page de la catégorie cookie par exemple).

L’objet Paginator

Cet objet est lui aussi assez simple à utiliser. Son code se trouve au même endroit que celui de Page. Seules certaines variables contenues dans Paginator vont vraiment nous intéresser :

  • count : Le nombre total d’articles dans cette pagination (par exemple 20 pour 20 articles, avec 5 articles par page) ;
  • num_pages : Le nombre total de pages composant la pagination (par exemple '4' pour 20 articles, avec 5 articles par page) ;
  • page_range : Un générateur allant de 1 au nombre de pages nécessaire pour tout parcourir (par exemple [1..4] pour afficher 20 articles, avec 5 articles par page).

Mettre tout cela en œuvre

Maintenant que nous savons tout cela, il va falloir modifier notre code pour afficher la liste d’articles de la meilleure manière qu’il soit. Et bonne nouvelle, il ne faudra le faire qu’à un seul endroit puisque nous avons factorisé tout le code d’affichages des listes d’articles dans le fichier articles-list.part.html !

Pour commencer, on ne va afficher que les articles concernant la page courante. Pour cela on va remplacer le for :

{% for article in articles %}

devient

{% for article in articles_page.object_list %}

Donc au lieu d’afficher 20 articles on en affiche plus que 5.

Maintenant, il va falloir créer l’affichage qui nous permettra de sélectionner la page que nous souhaitons afficher.

Pour cela, on va utiliser le générateur page_range de articles_paginator dans un for pour créer une liste de toutes les pages (en dehors de la boucle for de la liste d’articles) :

{% if articles_paginator.num_pages > 1 %}
<div class="paginator">
    {% for cpt in articles_paginator.page_range %}
    <a href="/{{ page_name }}{{ cpt if cpt > 1 else '' }}.html">{{ cpt }}</a>
    {% endfor %}
</div>
{% endif %}

Comme vous pouvez le remarquer, j’ai ajouté un if sur le nombre de pages. En effet, inutile de construire le sélecteur de pages s’il y en a qu’une seule !

On trouve aussi une condition pour construire le lien des pages. En effet, la toute première page ne se nomme pas categorie/cookie1.html mais juste category/cookie.html (sans le 1 final).

Avec un peu de CSS, voici ce que l’on peut déjà obtenir :

Paginateur simple
Paginateur simple
.paginator {
  margin: 10px;
}

.paginator a {
  display: inline-block;
  padding: 10px 15px;
  margin-left: -6px;
  color: #007bff;
  background-color: #fff;
  border: 1px solid #dee2e6;
  text-decoration: none;
}

.paginator a:hover {
  color: #0056b3;
  text-decoration: none;
  background-color: #e9ecef;
  border-color: #dee2e6;
}

C’est un bon début, mais on peut faire un peu mieux…

Améliorer le paginateur

En utilisant à bon escient les différentes variables, on peut obtenir un résultat fort sympathique. Voici par exemple ce qui est possible :

Zoom sur le paginateur
Zoom sur le paginateur

On voit ainsi apparaitre des chevrons à droite et gauche permettant de naviguer en avant/arrière d’une page à la fois, et aussi un indicateur visuel permettant de savoir sur quelle page nous nous trouvons. Les chevrons ainsi que la page courante sont rendus non cliquables dans les cas où c’est utile (si nous sommes sur la première ou dernière page par exemple).

Pour obtenir ce résultat, voici le code utilisé :

{% if articles_paginator.num_pages > 1 %}
<div class="paginator">
    {% if articles_previous_page %}
    {% set num = articles_page.previous_page_number() %}
    <a href="/{{ page_name }}{{ num if num > 1 else '' }}.html">&lt;</a>
    {% else %}
    <span>&lt;</span>
    {% endif %}

    {% for cpt in articles_paginator.page_range %}
        {% if cpt == articles_page.number %}
        <span class="active">{{ cpt }}</span>
        {% else %}
        <a href="{{ SITEURL }}/{{ page_name }}{{ cpt if cpt > 1 else '' }}.html">{{ cpt }}</a>
        {% endif %}
    {% endfor %}

    {% if articles_next_page %}
    <a href="/{{ page_name }}{{ articles_page.next_page_number() }}.html">&gt;</a>
    {% else %}
    <span>&gt;</span>
    {% endif %}
</div>
{% endif %}

Pour construire ce beau paginateur, on fait appel à plusieurs conditions if. La première et dernière condition servent toutes deux à afficher les chevrons. Ils sont soit affichés dans un span (si nous sommes à la première/dernière page), soit dans un lien pour se rendre à la page précédente/suivante lorsqu’elle existe (il faut là encore ruser pour créer l’adresse de la toute première page).

Au milieu, on retrouve notre boucle for. Son contenu a cependant été un peu amélioré pour pouvoir afficher un span au lieu d’un lien lorsque il faut afficher le numéro de la page courante.

Et voici le CSS mise à jour :

.paginator {
  margin: 10px;
}

.paginator a, .paginator span {
  display: inline-block;
  padding: 10px 15px;
  margin-left: -6px;
  color: #007bff;
  background-color: #fff;
  border: 1px solid #dee2e6;
  text-decoration: none;
}

.paginator a:hover {
  color: #0056b3;
  text-decoration: none;
  background-color: #e9ecef;
  border-color: #dee2e6;
}

.paginator span {
  color: rgba(0,0,0,.3);
}

.paginator span.active {
  color: #0056b3;
  background-color: #e9ecef;
}

Je reconnais que ce code est un peu plus compliqué, mais finalement, avec un peu de réflexion et pas mal d’essais on finit par obtenir des trucs sympas non :) ?

Paginateur amélioré
Paginateur amélioré

Et voilà encore un beau morceau d’abattu, avec quelques nouveautés comme les boucle for mais surtout l’occasion de (re)travailler tout ce qui avait pu être vu pendant le chapitre précédent.

Maintenant nous pouvons passer au cœur du site en créant le design d’un article lui-même.