L’objectif
Créer un modèle de document, qu’on pourra exporter en PDF, et qui se remplit automatiquement avec les données d’une table. Par exemple, une lettre personnalisée avec la date et le nom, prénom et adresse du destinataire :
La base
Dans une première table, on crée notre modèle de document en langage html. Ce code contient notre texte modèle, un genre de phrase « avec des trous » - ou plutôt des variables de template qui seront remplacées par des valeurs au moment du rendu. Ces variables sont reconnaissables car entourées d’accolades {}.
Exemple :
Dans une seconde table, on stocke les informations à afficher via ces variables. Et on ajoute une colonne avec une formule qui permettra de trouver quelle donnée devra être affichée dans quelle variable - la correspondance se fera entre le nom de la variable et le nom d’une colonne de la table de données. Cela va permettre de « remplir les trous » de notre texte.
Cette formule s’écrit :
# Classe utilisée pour la recherche des données
class Find_Data(dict):
def __missing__(self, key):
return getattr(rec, key)
# 1. Récupère le modèle du document
template = Template_Facture.lookupOne().Modele
# 2. Formate le modèle avec les champs de la table actuelle
template.format_map(Find_Data())
Dans notre exemple, le champ {nom} du modèle sera remplacé par les données de la colonne « nom » de la table courante.
Dans l’image ci-dessus, pour chaque ligne, on peut observer que le nom a bien été remplacé, c’est donc que notre formule fonctionne bien !
Si nous n’avions pas de colonne appelée « nom » dans notre table courante, la formule retournerait une erreur
AttributeError : Table 'Liste_de_noms' has no column 'nom'
Nous souhaitons désormais avoir une vue finale de notre document, plutôt que d’avoir le html brut affiché dans une colonne.
Nous allons pour cela utiliser le widget « Markdown », pour l’interprétation du contenu html :
- Cliquer sur Nouveau > Ajouter une vue > Personnalisée > choisir « Markdown » en sélectionnant la table contenant les données dynamiques
- Dans la configuration de la vue > Vue, autoriser l’accès complet au document, et dans le champ « content », choisir la colonne formule
- Dans la configuration de la vue > Données source, lier la vue à la table en la sélectionnant dans le champ « Sélectionner par »
Et c’est tout ! Désormais, lorsque vous parcourez la table, vous verrez apparaître votre modèle personnalisé selon les données sélectionnées. ![]()

Un modèle de lettre
Maintenant que nous avons compris le concept de base, nous allons créer un modèle plus complexe : une lettre qui contient une image, de la mise en forme et, évidemment, des variables.
Le code html est le suivant :
Je suis là
<div style="font-family:arial;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<img src="https://upload.wikimedia.org/wikipedia/fr/2/22/Republique-francaise-logo.svg" width="150" />
</div>
<div style="text-align:right;">
{prenom} {nom}
{adresse}
Paris, le {date}
</div>
<div>
Madame, Monsieur,
Vous faites partie de la fabuleuse communauté des Gristeur·euses, et nous vous en remercions.
Nous vous prions l’assurance de nos sentiments les meilleurs.
</div>
<div align="right">
L'équipe Grist.Gouv
</div>
</div>
Comme précédemment, nous stockons ce code dans une table « Template_lettre », colonne « Modèle ».
Il contient les champs dynamiques suivants : {prenom}, {nom}, {adresse}, {date}. Il faudra donc que notre table de données dispose de colonnes qui portent ces mêmes noms.
Notre table de données a également une colonne « formule » contenant la formule de remplacement des variables. Il faut l’éditer pour indiquer le nom de la table dans laquelle aller chercher le modèle (ici, « Template_lettre »).
Pour éviter d’avoir des lignes à rallonge dans la table de données - le code html est souvent très long - il peut être pratique de masquer la colonne « formule ».
Nous ajoutons ensuite la vue Markdown, et voici le résultat :

Enregistrer le document
Cluiquer sur les ··· en haut à droite de la vue > Imprimer la vue > Enregistrer au format PDF. Il est aussi possible d’imprimer directement le document.

Afficher des nombres
Si l’on souhaite afficher des nombres, le formatage que l’on choisit sur Grist (par exemple 2 chiffres après la virgule) ne s’applique pas dans la vue Markdown, qui récupère la donnée source non formatée.
Par exemple :
Dans ce cas, on peut modifier la formule pour arrondir la valeur, avec le code
round(valeur , 2)
Formule avec arrondi
import numbers
# Classe utilisée pour la recherche des données
class Find_Data(dict):
def __missing__(self, key):
attr = getattr(rec, key)
# Arrondit les nombres à deux décimales
if isinstance(attr, numbers.Number):
return f"{attr:.2f}"
else:
return getattr(rec, key)
# 1. Récupère le modèle du document
template = Template_Facture.lookupOne().Modele
# 2. Formate le modèle avec les champs de la table actuelle
template.format_map(Find_Data())
Résultat :
Pour des entiers, pensez à bien formater votre colonne en « Entier » et non en « Numérique », pour ne pas afficher les décimales dans votre document (par exemple, afficher 6 plutôt que 6.0)
Afficher des dates au format français
Si l’on souhaite afficher des dates, le formatage que l’on choisit sur Grist (par exemple DD-MM-YYYY) ne s’applique pas dans la vue Markdown, qui récupère la donnée source non formatée.
Il faut modifier la formule pour faire ce formatage avec
if isinstance(attr, datetime.date):
return attr.strftime(format = '%d-%m-%Y')
Formule avec formatage date et arrondi nombre
import datetime
import numbers
# Classe utilisée pour la recherche des données
class Find_Data(dict):
def __missing__(self, key):
attr = getattr(rec, key)
# Formate la date en français
if isinstance(attr, datetime.date):
return attr.strftime(format = '%d-%m-%Y')
# Arrondit les nombres à deux décimales
if isinstance(attr, numbers.Number):
return round(attr, 2)
else:
return getattr(rec, key)
# 1. Récupère le modèle du document
template = Template_Facture.lookupOne().Modele
# 2. Formate le modèle avec les champs de la table actuelle
template.format_map(Find_Data())
Ajouter le CSS au début du code HTML
Jusqu’ici nous avons intégré le style css élément par élément, à l’aide de l’attribut style. Par exemple nous avons écrit : <div style="text-align:right;">
Cette méthode peut convenir pour des documents très simples, mais devient ingérable lorsque l’on souhaite faire une mise en forme plus avancée : il faudrait répéter le style pour chaque balise de même type.
La bonne pratique est d’ajouter le css « à part », dans l’en-tête du code HTML, dans l’élément <head>. La structure sera la suivante :
<!DOCTYPE html>
<html>
<head>
<style>
h1
{
color:blue;
font-size:14px;
}
</style>
</head>
<body>
<h1>body ohdy</h1>
</body>
</html>
Le problème qui se pose, c’est qu’écrire le style css de cette manière nous fait utiliser des {}, et notre formule de génération du Markdown considère les accolades comme des variables… Or, elles ne servent ici qu’à définir le style ! Pour éviter des erreurs (par exemple ici Grist nous retournerait une erreur « AttributeError : Table 'Ma_table' has no column 'h1' ») , nous allons utiliser une astuce : « échapper » les accolades en les doublant. Par exemple :
h1
{{
color:blue;
font-size:14px;
}}
Voici le résultat d’un document au style avancé : une facture pour le bois d’affouage : Template Grist : Factures personnalisées
Happy Gristing! 
Liens utiles :
- Les modèles utilisés pour ce post : https://grist.incubateur.anct.gouv.fr/o/tutos-templates/doc/dxZkC1YDn3MN~gyJ6asHTSrHaSvgtK4bcuT~6921/p/1
- Le modèle « Publipostage Lauréats » dans la galerie de l’instance ANCT : Publipostage lauréats - Grist

















