Utilisez un modèle pour tous les invités

PythonBeginner
Pratiquer maintenant

Introduction

Un dîner de quartier nécessite une invitation pour chaque personne. Vous transformerez une liste CSV d’invités, les informations JSON de l’événement et un modèle HTML fourni en un nouveau lot de fichiers personnalisés. Deux personnes portant le même nom doivent recevoir des fichiers distincts, et un nom comme Zoë, "Z" & <Friends> doit apparaître comme du texte.

Vous devez connaître les chaînes, dictionnaires, boucles et fonctions Python, ainsi que les bases du terminal. Ce laboratoire explique la lecture de fichiers, les enregistrements CSV, les modèles, l’échappement du texte HTML et les nouveaux répertoires de sortie. L’interface en ligne de commande et la mise en page sont fournies ; vous réaliserez la correspondance des champs, la validation complète et la génération individuelle. Vous ne créerez ni CSS, ni envoi de courrier, ni PDF. Durée estimée par l’auteur : 55 à 75 minutes, lecture et erreurs courantes comprises.

Ouvrez votre première invitation

Dans cette étape, vous générerez une invitation normale et relierez son contenu aux champs d’entrée. Dans WebIDE, ouvrez Terminal → New Terminal, puis attendez l’invite du shell labex. Dans une fenêtre étroite, Terminal peut se trouver dans More (...). Saisissez toutes les commandes dans ce terminal. L’espace préparé est indépendant des laboratoires précédents.

cd change le répertoire courant. Un chemin absolu commence par / ; un chemin relatif est résolu depuis le répertoire courant. Si une commande ne trouve plus un fichier après un changement de répertoire, revenez ici avec la même commande :

cd /home/labex/project/invitations

cat affiche un fichier texte. Lisez les informations de l’événement :

cat event.json

JSON utilise des accolades pour les objets et des guillemets doubles autour des clés et valeurs textuelles. Ces quatre valeurs deviendront le titre, la date, l’heure et le lieu de l’invitation. L’exemple prêt à lancer, one.py, les associe déjà à une personne dans le modèle fourni. python3 exécute un fichier Python. Générez l’exemple dans le nouveau répertoire batches/first :

python3 one.py

Résultat attendu :

Created batch: batches/first

Passez de l’onglet de plateforme WebIDE à Web 8080. La préparation a déjà démarré le lecteur d’invitations ; Go Live est inutile. Actualisez l’aperçu et repérez first/g001.html. Cliquez sur Open invitation : le véritable fichier HTML s’ouvre dans un nouvel onglet du navigateur. Lisez Maya Patel, The Long Table, l’heure et le lieu. Fermez cet onglet pour revenir à Web 8080. Download HTML enregistre le même fichier pour l’utiliser hors du laboratoire.

Une invitation complète pour Maya Patel

La formule d’adresse identifie la personne ; les deux parties inférieures indiquent l’heure et le lieu.

L’aperçu ne lit que les fichiers réellement présents dans batches. Il ne génère pas les invitations manquantes. Le répertoire antérieur batches/archive est conservé comme travail précédent. Votre programme final créera de nouveaux lots en gardant ces fichiers et toutes les entrées. Revenez dans WebIDE pour la suite.

Associez les enregistrements CSV au texte HTML

Dans cette étape, vous ferez placer chaque valeur au bon emplacement par le moteur de rendu réutilisable. Revenez au même terminal et au même espace WebIDE ; les chemins relatifs ci-dessous partent de /home/labex/project/invitations.

Lisez des enregistrements plutôt que de découper les virgules

CSV est un format tabulaire dont la première ligne contient les noms des champs. Une virgule sépare les champs, sauf à l’intérieur d’un champ entre guillemets ; un guillemet dans un tel champ est doublé. Ainsi, split(",") ne sait pas lire correctement toutes les lignes. Consultez la liste fournie :

cat guests.csv

Deux enregistrements Alex Chen possèdent des valeurs guest_id distinctes. Le nom de la dernière personne contient une virgule, des guillemets, & et des chevrons. Ce sont des caractères autorisés dans un nom. L’identifiant stable désigne une personne ; le nom affiché ne suffit pas.

Le programme fourni generate.py lit ces enregistrements. Lisez-le avant de modifier le rendu :

cat generate.py

with ... as source ouvre un fichier et le ferme à la fin du bloc indenté. encoding="utf-8" conserve les caractères Unicode. newline="" laisse le lecteur CSV gérer les limites des enregistrements. csv.DictReader(source) exploite l’en-tête pour produire un dictionnaire par enregistrement ; list(...) rassemble les enregistrements dans une liste. Par exemple, guest["name"] contient un nom complet, y compris une virgule appartenant au nom. json.loads(...) transforme le texte JSON de l’événement en dictionnaire. Ces lecteurs et l’interface de commande sont déjà réalisés.

Distinguez les guillemets CSV de l’échappement HTML

Une page HTML utilise <...> pour les balises et &...; pour les références de caractères. Les guillemets CSV protègent la structure du tableau ; ils n’empêchent pas un nom tel que <Friends> d’être interprété comme du HTML. html.escape(value, quote=True) transforme les caractères spéciaux en références que le navigateur affiche comme le texte d’origine. Ici, toutes les valeurs vont dans des emplacements textuels HTML. Cette méthode ne constitue pas une recette pour insérer des données dans du JavaScript, du CSS ou des URL quelconques.

Template utilise $name, $title et les autres marqueurs $field de invitation.html. substitute(mapping) remplace chaque marqueur par la valeur correspondante du dictionnaire et refuse une correspondance absente. Les six marqueurs du corps, data-field="name", message, title, date, time et venue, identifient les emplacements sémantiques. Conservez un exemplaire de chacun avec le texte correspondant. Ne modifiez pas le modèle d’entrée fourni. Des balises, dispositions et écritures d’entités équivalentes sont acceptées dans la sortie ; un nom uniquement présent dans le <title> ne remplit pas son emplacement dans le corps.

Créez le moteur de rendu suivant. cat > rendering.py écrit le fichier en remplaçant son contenu antérieur. Le délimiteur entre apostrophes du bloc here-document <<'PYCODE' traite littéralement le texte jusqu’au délimiteur final PYCODE, qui doit être seul sur sa ligne. Seul votre moteur de rendu est remplacé ; les entrées restent intactes.

Le dictionnaire associe explicitement les champs de la personne et de l’événement. values.items() produit des couples clé/valeur. La compréhension {key: ... for key, value in values.items()} échappe chaque valeur en gardant sa clé. from html import escape importe cette fonction de la bibliothèque standard.

cat > rendering.py <<'PYCODE'
from html import escape


def render_guest(guest, event, template):
    values = {"name": guest["name"], "message": guest["message"],
              "title": event["title"], "date": event["date"],
              "time": event["time"], "venue": event["venue"]}
    return template.substitute({key: escape(value, quote=True)
                                for key, value in values.items()})
PYCODE

La fonction renvoie du texte HTML sans créer de fichier. Les modules auxiliaires locaux peuvent être des fichiers .py au même niveau que generate.py ; les sous-répertoires de paquets sortent du cadre de cet exercice. L’échappement intervient une seule fois, avant la substitution : le navigateur affiche &, et non le texte visible &amp;. La présentation de l’invitation est maintenant séparée de son texte personnalisé.

Validez tous les invités avant de créer un lot

Dans cette étape, vous générerez un fichier par identifiant tout en protégeant les travaux précédents. Continuez dans le même terminal WebIDE.

Le batch.py initial utilise les noms comme noms de fichiers et autorise un répertoire existant. Deux personnes appelées Alex Chen peuvent écraser leurs fichiers respectifs. Lisez cette petite fonction :

cat batch.py

Le contrat exige un nouveau répertoire de lot contenant <guest_id>.html pour chaque ligne. Les identifiants n’utilisent que des lettres ASCII minuscules, des chiffres et des traits d’union. Les noms et messages restent du texte UTF-8 sans cette restriction. Les champs obligatoires sont guest_id, name, message pour la personne, et title, date, time, venue pour l’événement. Une valeur obligatoire vide ou composée seulement d’espaces, un identifiant répété ou un répertoire déjà existant doivent provoquer un refus expliqué. Conservez le CSV, les deux fichiers d’événement, le modèle et batches/archive.

Séparez la préparation de l’écriture

Validez d’abord tous les enregistrements et préparez toutes les pages. Créez le répertoire et écrivez les fichiers seulement après la réussite de cette phase. Sinon, un dernier enregistrement erroné pourrait laisser un lot partiel trompeur. Cette méthode protège des erreurs d’entrée enseignées ici ; elle ne garantit pas une transaction face à une panne de disque ou une coupure de courant.

isinstance(value, str) vérifie qu’une valeur est textuelle. event.get(field) renvoie None si une clé manque, permettant une erreur utile. strip() vérifie la présence d’autre chose que des espaces sans modifier le texte qui sera affiché. raise ValueError(...) signale une entrée incorrecte ; l’interface fournie l’intercepte, affiche Cannot generate invitations: ... et se termine en échec.

Un set() mémorise les identifiants rencontrés. in teste l’appartenance ; seen.add(...) ajoute un identifiant. all(...) vérifie que chaque caractère respecte la règle annoncée. pages.append((guest_id, page)) conserve un couple identifiant/page. Enfin, for guest_id, page in pages décompose ces couples.

Path(output) représente le répertoire demandé. mkdir() le crée et refuse un chemin existant, car nous ne transmettons pas exist_ok=True. L’opérateur / assemble les chemins. Une f-string telle que f"{guest_id}.html" insère l’identifiant dans le nom de fichier. write_text(..., encoding="utf-8") écrit le texte HTML complet en UTF-8.

Remplacez uniquement batch.py par cette implémentation centrale :

cat > batch.py <<'PYCODE'
from pathlib import Path
from rendering import render_guest


def write_batch(guests, event, template, output):
    for field in ("title", "date", "time", "venue"):
        if not isinstance(event.get(field), str) or not event[field].strip():
            raise ValueError(f"Missing event field: {field}")
    if not guests:
        raise ValueError("The guest list is empty")
    seen = set()
    pages = []
    for guest in guests:
        for field in ("guest_id", "name", "message"):
            if not isinstance(guest.get(field), str) or not guest[field].strip():
                raise ValueError(f"Missing guest field: {field}")
        guest_id = guest["guest_id"]
        if not all(character in "abcdefghijklmnopqrstuvwxyz0123456789-" for character in guest_id):
            raise ValueError("Guest IDs must use lowercase letters, digits, or hyphens")
        if guest_id in seen:
            raise ValueError(f"Duplicate guest ID: {guest_id}")
        seen.add(guest_id)
        pages.append((guest_id, render_guest(guest, event, template)))
    output = Path(output)
    output.mkdir()  ## An existing batch is always refused.
    for guest_id, page in pages:
        (output / f"{guest_id}.html").write_text(page, encoding="utf-8")
PYCODE

from rendering import render_guest utilise votre moteur de rendu local. La première boucle vérifie l’événement ; la suivante valide les personnes et prépare les pages ; la dernière écrit le lot complet. Aucun fichier n’est créé avant la fin de la validation et du rendu.

L’interface reçoit quatre chemins positionnels, dans cet ordre : CSV des invités, JSON de l’événement, modèle HTML et nouveau répertoire de sortie. Lancez-la avec toute la liste :

python3 generate.py guests.csv event.json invitation.html batches/evening

Résultat attendu :

Created batch: batches/evening

ls énumère les éléments du répertoire :

ls batches/evening

Vous devez trouver g101.html, g102.html et g103.html. Les deux invitations Alex ont maintenant des identifiants indépendants. Les fichiers conservent le message propre à chaque personne ; le troisième garde sa virgule, ses guillemets, ses caractères Unicode et ses chevrons littéraux.

Réutilisez le générateur et préservez le travail précédent

Dans cette étape, vous utiliserez le même programme pour un autre événement et pratiquerez la récupération d’erreurs ordinaires. Commencez dans le même terminal WebIDE. Chaque commande est indépendante : un échec attendu ne bloque donc pas la suite.

Répétez d’abord volontairement le chemin de sortie déjà rempli. L’échec est attendu : mkdir() refuse un lot existant. Le message commence par Cannot generate invitations: et mentionne un fichier existant. Le lot antérieur doit rester intact.

python3 generate.py guests.csv event.json invitation.html batches/evening

Lisez l’exemple préparé d’identifiant répété. La deuxième personne possède l’identifiant de la première :

cat invalid-duplicate.csv

Essayez de créer un lot à partir de ce fichier. Cet échec attendu affiche Duplicate guest ID: g101 avant la création du moindre répertoire :

python3 generate.py invalid-duplicate.csv event.json invitation.html batches/rejected-duplicate

L’exemple sans message échoue également volontairement avec Missing guest field: message. Même si les enregistrements précédents sont corrects, aucun lot partiel ne doit apparaître :

python3 generate.py invalid-missing.csv event.json invitation.html batches/rejected-missing

Un chemin mal saisi est un autre problème : le système ne peut pas ouvrir le fichier. La commande suivante utilise volontairement guest.csv, qui n’existe pas. Lisez l’erreur sans réinitialiser l’environnement :

python3 generate.py guest.csv daytime.json invitation.html batches/daytime

Corrigez le chemin avec le véritable fichier guests.csv. Lisez le deuxième événement, puis créez un nouveau lot avec le même programme et la même liste :

cat daytime.json
python3 generate.py guests.csv daytime.json invitation.html batches/daytime

Résultat attendu :

Created batch: batches/daytime

Passez à Web 8080 et actualisez. Ouvrez successivement evening/g101.html et evening/g102.html. Les noms sont identiques, mais les messages restent distincts. Ouvrez evening/g103.html : la virgule, les guillemets, ë, l’esperluette et <Friends> doivent être du texte littéral. Ouvrez daytime/g103.html et lisez ses nouveaux titre, heure et lieu. Fermez chaque onglet d’invitation pour revenir à la liste. Utilisez Download HTML pour l’invitation de journée, puis ouvrez le fichier téléchargé : son texte personnalisé doit correspondre à l’aperçu.

Les caractères spéciaux restent du texte littéral

Le nom conserve sa virgule, ses guillemets et ses chevrons, visibles dans la formule d’adresse.

Les deux lots sont indépendants. Le CSV, les fichiers d’événement et le modèle n’ont pas changé ; l’ancienne archive est toujours présente. Une entrée incorrecte, un lot existant et un chemin manquant ont des explications différentes : chacun peut être corrigé sans détruire les invitations précédentes.

Résumé

Vous avez produit de véritables invitations HTML téléchargeables à partir de données CSV et JSON. Vous avez distingué les identifiants stables des noms affichés, associé les champs aux emplacements textuels du modèle, conservé Unicode et les caractères spéciaux grâce à UTF-8 et à l’échappement HTML, puis validé tout le lot avant l’écriture. Le générateur peut désormais créer de nouveaux lots tout en préservant ses entrées et les travaux précédents.