1. Posez la bonne question
Avant d'ouvrir templates.xml, demandez-vous ce que vous voulez construire.
- Je veux que quelqu'un travaille dans l'application Odoo, avec un
menuet unelist view. Utilisez uneviewet uneaction, comme à l'étape 5. Aucun nouveautemplaten'est nécessaire. - Je veux que quelqu'un ouvre une URL et reçoive une page HTML préparée par le serveur. Utilisez un
templateQWeb dansviews/templates.xml, appelé par un contrôleur. - Je veux une interface interactive dessinée par notre JavaScript dans le navigateur. Utilisez le
templateOWL déjà créé sousstatic/src/hello_world/hello_world.xml.
Le même mot template apparaît dans les deux derniers cas, mais il ne part pas au même endroit et il n'est pas rendu au même moment.
2. Server-side rendering ou rendu dans le navigateur ?
Les trois fichiers XML que nous avons rencontrés n'ont pas le même rôle. Le point décisif est le suivant : qui construit l'écran final ?
| Fichier | Qui construit l'écran ? | Quand l'utiliser ? |
|---|---|---|
views/partner_category_list.xml | Le client web JavaScript d'Odoo, dans le navigateur. | Une list view ou un form à l'intérieur de l'application Odoo. |
views/templates.xml | Le serveur Odoo, avant de renvoyer la réponse. | Une URL qui doit recevoir une page HTML : c'est le server-side rendering (SSR). |
static/src/hello_world/hello_world.xml | Notre composant OWL JavaScript, dans le navigateur. | Une interface interactive personnalisée, comme la client action de la section 2. |
La view de l'étape 5 est donc un troisième cas. Lors de la mise à niveau, Odoo enregistre sa définition. Quand l'utilisateur ouvre le menu, le serveur fournit cette définition et les records ; le client web JavaScript compose ensuite l'écran dans le navigateur. Ce n'est pas de la SSR, car le serveur ne renvoie pas une page HTML déjà construite.
Avec views/templates.xml, le trajet est différent : navigateur → URL → contrôleur → request.render → HTML → navigateur. Le contrôleur recherche les records, Odoo applique le template sur le serveur, et le navigateur reçoit directement le HTML final.
Repère si vous connaissez PHP : c'est proche d'une page PHP classique. Le serveur récupère les données, applique un
template, puis envoie le HTML terminé au navigateur. Ici, Odoo etrequest.renderjouent ce rôle. Cela n'empêche pas d'ajouter du JavaScript ensuite ; simplement, le premier HTML a déjà été construit côté serveur.
Enfin, le template OWL sous static/ est téléchargé par le navigateur avec les assets du module. Notre JavaScript l'utilise ensuite pour produire ou mettre à jour le HTML. Le dossier static/ ne signifie pas « page HTML figée » : il indique que le fichier est livré au navigateur pour être rendu côté client.
Peut-on les réutiliser ensemble ?
Un template QWeb peut afficher les mêmes records que notre list view, mais il ne réutilise pas cette view. Dans notre exemple, le contrôleur peut fournir des res.partner.category au template. La list view décide des colonnes de l'écran interne ; le template décide du HTML de la page URL. Les deux présentent les mêmes données de deux façons différentes.
Un template QWeb peut aussi inclure ou référencer des ressources sous static/, par exemple une image, du CSS ou du JavaScript. En revanche, il ne rend pas un composant OWL à la place du serveur. Un composant et son template OWL sont téléchargés puis exécutés par le navigateur, après la réponse SSR. Les deux approches peuvent donc cohabiter sur une même page, mais elles gardent chacune leur rôle.
OWL ou QWeb ?
Ce ne sont pas deux choix concurrents pour le même travail. Un template QWeb est appelé par le serveur, généralement depuis un contrôleur, pour produire une réponse HTML. Un composant OWL et son template sont exécutés dans le navigateur ; ils sont adaptés aux clics, à l'état de l'interface et aux mises à jour sans recharger toute la page.
Choisissez QWeb lorsqu'une URL doit recevoir du HTML dès la réponse du serveur. Choisissez OWL lorsqu'une interface déjà ouverte doit réagir dans le navigateur. Une page QWeb peut ensuite charger du JavaScript ou un composant OWL : le serveur fournit le point de départ, puis le navigateur ajoute l'interactivité.
Pages publiques, site vitrine et référencement
Oui, c'est un cas d'usage naturel des templates QWeb : une page de présentation, une page de catalogue, une page de portail ou toute URL dont le contenu doit être lisible dès le premier chargement. Comme le serveur renvoie déjà le HTML, un moteur de recherche ou un aperçu de lien peut lire le contenu sans devoir d'abord exécuter notre JavaScript. C'est une bonne base pour le référencement, mais ce n'est pas une garantie de visibilité ou de position dans les résultats.
Un template ne rend pas une URL publique à lui seul. Dans notre exemple, auth="user" impose une connexion : il convient à une page interne ou de portail. Une URL avec auth="public" doit être choisie délibérément, avec des records réellement publiables, des droits vérifiés et les métadonnées attendues pour une page destinée au web.
3. Un seul cas d'usage : une page URL pour les tags
Imaginons qu'un formateur connecté veuille consulter les tags de formation à l'adresse /training/tags, sans passer par les menu de l'application. La list view de l'étape 5 reste le bon outil pour travailler dans Odoo. Cette URL répond à un autre besoin : produire une petite page HTML à partir des mêmes records.
Nous allons la rendre testable plus loin dans cette étape. Elle ajoute des décisions d'accès et des fichiers Python supplémentaires, mais elle permet de suivre une chaîne complète avec un seul template.
4. Écrivez le template : il décrit le HTML, pas la recherche
Dans le fichier généré, remplacez le bloc commenté par cet exemple :
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="training_tags_page">
<h1>Training tags</h1>
<ul>
<li t-foreach="tags" t-as="tag">
<t t-out="tag.name"/>
</li>
</ul>
</template>
</odoo>id="training_tags_page"donne autemplatesonexternal IDcomplet :training_hello.training_tags_page.<h1>et<ul>sont simplement le HTML que le navigateur recevra.t-foreach="tags"demande de parcourir la valeur appeléetags. Letemplatene la crée pas : son appelant devra la fournir.t-as="tag"nomme un élément de la boucle ;t-out="tag.name"affiche son nom en l'échappant pour le HTML.
Ce template est donc une recette de HTML. Il ne s'affiche pas tout seul et ne choisit pas les records à lire.
5. Ajoutez l'appelant : le contrôleur fournit les records
Voici la pièce qui manque au template. Elle répond à la requête URL, lit les tags, puis appelle request.render :
from odoo import http
from odoo.http import request
class TrainingTagsController(http.Controller):
@http.route("/training/tags", auth="user")
def training_tags(self):
tags = request.env["res.partner.category"].search([])
return request.render("training_hello.training_tags_page", {
"tags": tags,
})@http.route("/training/tags", auth="user")associe une URL à cette méthode et demande un utilisateur connecté.request.env["res.partner.category"].search([])cherche lesrecordsdumodeldestags. Il n'y a pas desudo(): les droits et lesrecord rulesde l'utilisateur continuent de s'appliquer.request.render(...)appelle notretemplateet lui fournit la valeurtags. C'est ce nom qui est repris danst-foreach="tags".
Testez l'URL locale
Pour que cet exemple fonctionne réellement, Odoo doit pouvoir importer le contrôleur et charger le template :
- Créez
custom_addons/training_hello/controllers/training_tags.pyavec le code ci-dessus. - Dans
custom_addons/training_hello/controllers/__init__.py, ajoutezfrom . import training_tags. Vérifiez aussi quecustom_addons/training_hello/__init__.pycontientfrom . import controllers. - Ajoutez
"views/templates.xml"dans la listedatadecustom_addons/training_hello/__manifest__.py. - Avec la configuration actuelle, redémarrez Odoo pour qu'il recharge le code Python, puis mettez le module Hello Odoo à niveau pour charger le nouveau
template. La note sur l'auto-reload, à la fin de cette étape, évite ce redémarrage manuel pour les prochains changements Python.
Connectez-vous ensuite à Odoo, puis ouvrez ce lien : http://127.0.0.1:8069/training/tags. Avec auth="user", l'URL demande une session Odoo connectée.
6. Suivez le trajet complet
- Lors de la mise à niveau, le manifeste charge
views/templates.xmldepuis sa listedata. Odoo enregistre alors letemplate. - Un utilisateur connecté demande
/training/tags. - Le contrôleur recherche les
tagsque cet utilisateur a le droit de lire. request.renderinjecte cesrecordsdans letemplatesous le nomtags.- Odoo renvoie le HTML obtenu au navigateur.
À l'inverse, le template OWL de la section 2 est déclaré dans web.assets_backend. Le navigateur le charge pour que notre composant JavaScript dessine la client action. Il n'attend pas une requête request.render.
7. À propos de l'auto-reload
Maintenant que vous savez pourquoi le contrôleur devait être rechargé, vous pouvez éviter de taper docker compose restart odoo après chaque modification Python. En développement, démarrez Odoo avec --dev=reload : Odoo détecte une modification de fichier Python et redémarre son propre processus. Le conteneur Docker reste en place.
Dans ce projet Docker, ajoutez cette ligne au service odoo de docker-compose.yml :
command: ["--dev=reload"]Appliquez ce changement une seule fois en recréant le service :
docker compose up -d --force-recreate odooSans Docker, ajoutez la même option à votre commande habituelle de démarrage :
odoo --dev=reload
# ou, depuis les sources Odoo
./odoo-bin --dev=reloadIl recharge le Python : contrôleurs, modèles et autres fichiers .py. Il ne réimporte pas les fichiers XML dans la base. Après une modification de data, de views, d'un menu, d'une action ou du manifeste, mettez toujours le module à niveau. Pour un asset OWL déjà déclaré, enregistrez puis rechargez le navigateur, de préférence avec ?debug=assets pendant le développement.
Une list view structure l'écran interne Odoo. Un template QWeb structure le HTML d'une réponse URL. Les deux peuvent montrer les mêmes records, mais elles ont des appelants différents.