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 !
Sommaire
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 tag s ) 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) :
(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 ?!
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 :
-
authors.html
: http://localhost:8000/authors -
categories.html
: http://localhost:8000/categories -
tags.html
: http://localhost:8000/tags
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 ! À 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
.
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 exemplecategory/cookie
pour n’importe quel numéro de page de la catégorie cookie).
Page
L’objet
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).
Paginator
L’objet
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 :
.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 :
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"><</a>
{% else %}
<span><</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">></a>
{% else %}
<span>></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 ?
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.