Étape 6 sur 7

Quand utiliser templates.xml ?

À l'étape 5, nous sommes partis de views.xml : la view décrit la list, l'action l'ouvre, puis le menu rend cet écran accessible dans l'application. Nous allons lire templates.xml avec le même réflexe : le template décrit le HTML, puis un contrôleur l'appelle pour répondre à une URL. Notre list view n'en avait pas besoin.

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 menu et une list view. Utilisez une view et une action, comme à l'étape 5. Aucun nouveau template n'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 template QWeb dans views/templates.xml, appelé par un contrôleur.
  • Je veux une interface interactive dessinée par notre JavaScript dans le navigateur. Utilisez le template OWL déjà créé sous static/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 ?

FichierQui construit l'écran ?Quand l'utiliser ?
views/partner_category_list.xmlLe client web JavaScript d'Odoo, dans le navigateur.Une list view ou un form à l'intérieur de l'application Odoo.
views/templates.xmlLe 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.xmlNotre 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 et request.render jouent 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.

Public ne veut pas dire automatique.

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 :

custom_addons/training_hello/views/templates.xml
<?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 au template son external ID complet : 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ée tags. Le template ne 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 :

custom_addons/training_hello/controllers/training_tags.py
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 les records du model des tags. Il n'y a pas de sudo() : les droits et les record rules de l'utilisateur continuent de s'appliquer.
  • request.render(...) appelle notre template et lui fournit la valeur tags. C'est ce nom qui est repris dans t-foreach="tags".

Testez l'URL locale

Pour que cet exemple fonctionne réellement, Odoo doit pouvoir importer le contrôleur et charger le template :

  1. Créez custom_addons/training_hello/controllers/training_tags.py avec le code ci-dessus.
  2. Dans custom_addons/training_hello/controllers/__init__.py, ajoutez from . import training_tags. Vérifiez aussi que custom_addons/training_hello/__init__.py contient from . import controllers.
  3. Ajoutez "views/templates.xml" dans la liste data de custom_addons/training_hello/__manifest__.py.
  4. 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

  1. Lors de la mise à niveau, le manifeste charge views/templates.xml depuis sa liste data. Odoo enregistre alors le template.
  2. Un utilisateur connecté demande /training/tags.
  3. Le contrôleur recherche les tags que cet utilisateur a le droit de lire.
  4. request.render injecte ces records dans le template sous le nom tags.
  5. 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 :

docker-compose.yml · service odoo
command: ["--dev=reload"]

Appliquez ce changement une seule fois en recréant le service :

Dossier qui contient docker-compose.yml
docker compose up -d --force-recreate odoo

Sans Docker, ajoutez la même option à votre commande habituelle de démarrage :

Installation locale Odoo
odoo --dev=reload

# ou, depuis les sources Odoo
./odoo-bin --dev=reload
Ce que l'auto-reload fait — et ne fait pas.

Il 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.

Le point à retenir

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.