1. À quoi sert le stylesheet ici ?
À l'étape 6, le contrôleur a rendu la page QWeb /training/tags. Elle montre les tags, mais avec seulement un titre et une liste brute. Un stylesheet ne crée ni model, ni record, ni route : il choisit l'apparence du HTML que le serveur a déjà envoyé.
Nous allons donc styliser cette page QWeb, pas la client action OWL de la section 2. L'objectif est immédiatement visible : la même URL affichera une page lisible avec une carte et des tags distincts.
2. Reliez la page QWeb au stylesheet
Remplacez le template de l'étape 6 par cette version complète. La balise <link> dit au navigateur où charger le CSS ; le chemin commence par le nom technique du module, training_hello.
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="training_tags_page">
<html>
<head>
<title>Training tags</title>
<link rel="stylesheet"
href="/training_hello/static/src/css/training_tags.css"/>
</head>
<body class="training-tags-page">
<main class="training-tags-card">
<h1>Training tags</h1>
<ul class="training-tags-list">
<li t-foreach="tags" t-as="tag">
<t t-out="tag.name"/>
</li>
</ul>
</main>
</body>
</html>
</template>
</odoo>body class="training-tags-page"limite le style à cette page URL.training-tags-carddésigne le bloc principal ;training-tags-listdésigne la liste.- Le
t-foreachest inchangé : le contrôleur fournit toujours les tags, le template les transforme en HTML, puis le navigateur applique le CSS.
3. Créez le stylesheet
Créez ce fichier à l'emplacement indiqué par la balise <link> :
.training-tags-page {
margin: 0;
min-height: 100vh;
padding: 3rem 1.5rem;
background: #f3f6fa;
color: #17233f;
font-family: system-ui, sans-serif;
}
.training-tags-card {
max-width: 42rem;
margin: 0 auto;
padding: 2rem;
border: 1px solid #cbd5e1;
border-radius: .75rem;
background: #fff;
box-shadow: 0 .5rem 1.5rem rgb(15 23 42 / 8%);
}
.training-tags-list {
display: flex;
flex-wrap: wrap;
gap: .75rem;
padding: 0;
list-style: none;
}
.training-tags-list li {
padding: .45rem .75rem;
border-radius: 999px;
background: #e7efff;
color: #1557ad;
font-weight: 700;
}Le préfixe training-tags- évite qu'une règle comme li ou h1 modifie par accident une autre page. Les tags gardent leurs données et leurs droits : seuls leur fond, leur espacement et leur disposition changent.
4. Chargez-le et vérifiez
Pour cette première petite page, le lien vise directement le fichier sous static/. Il n'y a donc pas d'entrée à ajouter dans assets du manifeste. En revanche, le template QWeb modifié reste chargé depuis data : mettez le module Hello Odoo à niveau après avoir modifié views/templates.xml.
- Vérifiez que
"views/templates.xml"est toujours présent dansdatadecustom_addons/training_hello/__manifest__.py. - Mettez le module Hello Odoo à niveau pour enregistrer la nouvelle version du template.
- Connectez-vous à Odoo puis ouvrez http://127.0.0.1:8069/training/tags. Rechargez simplement le navigateur après une modification du fichier CSS.
La même URL montre le titre Training tags dans une carte blanche centrée. Chaque tag apparaît comme une pastille bleue. Si le HTML apparaît sans style, vérifiez d'abord, dans cet ordre, le chemin du fichier CSS, l'attribut href de la balise <link>, puis rechargez le navigateur.
5. Réduisez la friction : rechargement et mise à niveau
Il n'existe pas de « mise à niveau automatique à chaque sauvegarde » sûre dans Odoo. Une mise à niveau peut réimporter des données XML, modifier des views, des droits, des actions ou le schéma de la base. La lancer sans distinction à chaque modification finirait par masquer les effets de bord au lieu de vous faire gagner du temps.
En revanche, le cycle de développement peut être très court si vous choisissez la bonne action selon le fichier modifié :
| Vous modifiez… | Action la plus légère |
|---|---|
static/src/css/training_tags.css | Rechargez simplement la page /training/tags. |
| Un fichier Python, comme un contrôleur | Démarrez Odoo avec --dev=reload : il redémarre son processus quand le fichier Python change. |
| Un template QWeb déjà chargé | Ajoutez xml à --dev pour lire le template depuis le fichier XML pendant le développement. |
Le manifeste, data, un menu, une action, des droits ou un nouveau model | Mettez explicitement le module à niveau. |
Pour une installation lancée depuis les sources, ce démarrage réduit fortement les redémarrages manuels :
./odoo-bin -d <nom_de_votre_base> -u training_hello --dev=xml,reload-u training_hello effectue une mise à niveau une fois au démarrage. Ensuite, --dev=reload surveille le Python et --dev=xml lit les templates QWeb depuis les fichiers. Ce n'est donc pas une mise à niveau répétée à chaque sauvegarde ; c'est précisément ce qui rend la boucle rapide sans réappliquer aveuglément les données du module.
Exception utile à connaître : si vous avez modifié ce template dans l'interface Odoo elle-même, une mise à niveau ou une initialisation reste nécessaire avant que le fichier XML redevienne la source lue par Odoo.
Avec Docker Compose, gardez command: ["--dev=xml,reload"] dans le service odoo, comme à l'étape 6. Lorsqu'une vraie mise à niveau reste nécessaire, exécutez-la sans recréer le conteneur :
docker compose exec odoo odoo module upgrade training_hello --database <nom_de_votre_base>Commencez toujours par l'action la plus légère : recharger la page pour le CSS, laisser --dev=reload gérer le Python, puis mettre le module à niveau uniquement quand la base ou le manifeste doit réellement changer. Une fois le model du jour 2 créé, la mise à niveau restera normale : elle fait partie du développement Odoo.
6. Ouvrez demo/demo.xml sans l'activer
Le dernier boilerplate est le fichier custom_addons/training_hello/demo/demo.xml. Il contient cinq records commentés pour le model training_hello.training_hello. Ce model n'existe pas encore dans notre module du jour 1 : c'est précisément pourquoi le scaffold a laissé ces lignes dans un commentaire.
<!--
<record id="object0" model="training_hello.training_hello">
<field name="name">Object 0</field>
<field name="value">0</field>
</record>
-->Ne retirez pas ce commentaire et n'ajoutez pas ce fichier au manifeste aujourd'hui : Odoo ne saurait pas créer un record pour un model absent. Au jour 2, lorsque vous créerez un vrai model et ses fields, vous pourrez remplacer cet exemple par des records fictifs qui correspondent à ce model.
data et demo n'ont pas la même promesse
| Entrée du manifeste | Rôle | Dans notre module aujourd'hui |
|---|---|---|
data | Records nécessaires ou utiles au module, chargés par son installation et ses mises à niveau. | Les deux tags Contacts de l'étape 2. |
demo | Records fictifs réservés à une base créée avec les données de démonstration. | Rien à charger : le modèle du scaffold n'existe pas. |
Quand le model existera, l'entrée prendra cette forme dans custom_addons/training_hello/__manifest__.py :
"demo": [
"demo/demo.xml",
],Les records de demo servent à expliquer, explorer ou tester avec des valeurs fictives. Ils ne doivent contenir ni client réel, ni mot de passe, ni configuration métier attendue en production.
Oui : c'est le rôle de la case « Charger les données de démonstration » lors de la création de la base. Si cette case est cochée, Odoo est en mode démonstration et peut charger les fichiers déclarés dans demo quand le module est installé ou mis à niveau. Si elle ne l'est pas, la liste demo du manifeste n'est pas chargée.
7. Ce que vous emportez au jour 2
Vous avez maintenant distingué quatre rôles : les données XML sous data/ amorcent la base ; une view et une action organisent un écran interne ; un template QWeb produit du HTML côté serveur ; les fichiers sous static/ arrivent dans le navigateur. Le dossier demo/ attend, lui, un model réel et une base de démonstration.