Bonjour,
Je vous partage un tutoriel sur la gestion des pièces jointes (fichiers, images…) importé dans un document Grist.
Importer des pièces jointes dans une table Grist
Grist offre une grande flexibilité pour organiser et exploiter vos données, mais lorsqu’il s’agit de gérer des fichiers (images, pdf, word…), il peut être utile de centraliser ces pièces jointes dans une table dédiée.
Pour ce faire, on va créer une table spécifique, ATTACHMENT_FILES, qui servira de référentiel de toutes vos pièces jointes de votre document Grist, avec la structure suivante :
| Nom de la colonne | Type de colonne | Description |
|---|---|---|
| ATTACHMENT | Pièce jointe | Colonne permettant l’importation (upload) des fichiers dans la table. |
| ID_FILE | Formule Python | Identifiant unique du fichier généré par Grist. |
| FILENAME | Formule Python | Nom du fichier d’origine. |
| FILESIZE | Formule Python | Taille du fichier dans un format lisible (ex: 9.43 Mo). |
| FILE_URL_PRIVATE | Formule Python | URL générée pour télécharger le fichier via la REST API (nécessite les droits d’accès Grist). |
Voila les principales étapes pour créer cette table.
-
Ajouter une colonne ATTACHMENT de type [Pièce jointe]. Cette colonne affichera désormais une icône permettant d’ajouter des fichiers
-
Ajouter une colonne ID_FILE, pour consulter l’identifiant automatiquement assigner au fichier lors de son import dans le document, avec la formule suivante.
$ATTACHMENT.id[0]` -
Ajouter une colonne FILENAME de type [Formule], pour afficher le nom du fichier importé, avec la formule suivante.
$ATTACHMENT.fileName[0]` -
Ajouter une colonne FILESIZE, pour afficher son taille dans un format lisible (e.g. 9.43 Mo), avec la formule suivante :
def format_nb(n): s = "%.2f" % n entier, dec = s.split(".") entier = " ".join( [entier[max(i-3, 0):i] for i in range(len(entier), 0, -3)][::-1] ) return entier + "." + dec size = _grist_Attachments.lookupOne(id=$ATTACHMENT.id[0]).fileSize if size is None: size_formated = "" elif size < 1024: size_formated = format_nb(size) + " o" elif size < 1024*1024: size_formated = format_nb(size/1024) + " Ko" elif size < 1024*1024*1024: size_formated = format_nb(size/(1024*1024)) + " Mo" else: size_formated = format_nb(size/(1024*1024*1024)) + " Go" return f"{size_formated}" -
Ajouter une colonne FILE_URL_PRIVATE, pour générer un lien de téléchargement (URL) avec la formule Python suivante :
full_url = SELF_HYPERLINK() parts = full_url.split('/') doc_id = parts[-2] base_url = "/".join(parts[:3]) attachment_id = $ATTACHMENT.id[0] if $ATTACHMENT else None if not doc_id: return "Erreur : Impossible d'extraire l'ID du document" if not attachment_id: return "Erreur : Aucune pièce jointe détectée" return f"{base_url}/api/docs/{doc_id}/attachments/{attachment_id}/download"
Attention cette URL, générée via l’API REST, n’est pas forcément un « lien public » comme pourrait l’être un lien Microsoft Onedrive ou Google Drive, enfin sauf si le document Grist est lui même public sans restriction d’accès.Dans le cas où le document Grist a des restrictions d’accès, ce lien ne peut être utilisé que si l’utilisateur a une session active dans l’instance Grist, et qu’il dispose à minima du droit de lecture dans ce document. Si ce n’est pas le cas, le serveur renverra une erreur 403.
Vous pouvez maintenant importer (uploader) une pièce jointe dans une ligne de cette table, par drag-&-drop ou en cliquant sur l’icône de la pièce jointe.
Une fois l’import effectué, vous pouvez cliquez sur la colonne ATTACHMENT pour afficher un aperçu du document (si le format est compatible), et accéder aux opérations usuelles pour renommer, modifier ou supprimer le fichier.
Comment utiliser l’URL pour afficher une image dans un widget ?
Pour afficher une image, stockée en pièces jointe de votre document Grist, par exemple dans un panneau dédiée, vous pouvez être tenter d’utiliser d’utiliser le « Custom Widget Builder », ou d’écrire votre propre Widget Grist. Mais lorsque on utilise un Custom Widget, Grist charge le code de votre application web (HTML, JavaScript) à l’intérieur d’une <iframe>, et pour des raisons de sécurité, cette isolation pose deux problèmes majeurs pour l’affichage des images stocké dans une table Grist.
-
L’absence des cookies de session : Bien que vous soyez connecté à Grist sur votre navigateur, l’iframe du widget est considérée comme un environnement distinct. Elle n’a pas forcément accès aux « cookies » de session qui prouvent à Grist que vous avez le droit de voir l’image.
-
La politique de sécurité (CORS) : Grist bloque souvent les requêtes directes vers ses API depuis une iframe pour empêcher l’éxcution de scripts malveillants.
Ainsi, lorsque vous insérez simplement une URL classique dans une balise <img src="https://getgrist.com/api/docs/...i/attachments/1/download">, vous obtiendrez une erreur 403 Forbidden. Pour contourner ce problème, il faut donner à l’image une URL signée dynamiquement avec un token d’accès. La libriaire JavaScript « Grist plugin API » fournit justement la méthode getAccessToken qui permet de récupérer simplement ce token d’accès.
Il suffit donc de suivre le process suivant :
-
Le widget demande à Grist un token d’accès éphémère via la méthode
grist.docApi.getAccessToken(). -
Grist génère un jeton qui prouve que l’utilisateur actuel a bien le droit de lire ce document précis.
-
On ajoute ce jeton en paramètre GET dans l’URL de l’image :
?auth=LE_TOKEN.
Ce qui donne par exemple, le widget suivant :
<!DOCTYPE html>
<html>
<head>
<style>
img { max-width: 100%; max-height: 300px; border-radius: 4px; }
</style>
<script src="https://docs.getgrist.com/grist-plugin-api.js"></script>
</head>
<body>
<div id="container"></div>
<script>
const container = document.getElementById('container');
grist.ready({ requiredAccess: 'read table' });
grist.onRecord(async (record) => {
try {
const { baseUrl, token } = await grist.docApi.getAccessToken({ readOnly: true });
const attachmentId = record.Image || record.id;
if (!attachmentId) throw new Error("Aucune image trouvée.");
const imageUrl = `${baseUrl}/attachments/${attachmentId}/download?auth=${token}`;
container.innerHTML = `
<img src="${imageUrl}" alt="Image Grist" /><br/>
<a href="${imageUrl}" target="_blank">Télécharger l'image</a>
`;
} catch (error) {
container.innerHTML = `<div class="error">Erreur : ${error.message}</div>`;
}
});
</script>
</body>
</html>
Remarquons que le téléchargement d’un fichier, contrairement à l’affichage d’une image, ne pose généralement aucun souci technique. Le comportement du navigateur est en effet fondamentalement différent lorsqu’il traite une balise
par rapport à un lien de téléchargement.
En cliquant sur un lien, on sort du contexte restreint de l’iframe du widget. La requête est directement prise en charge par le navigateur dans un flux de navigation standard avec un accès au cookies de session.
Enjoy ^^…

