Développer des modules Odoo 19 avec Claude
Développer un module Odoo custom avec une IA ne s'improvise pas : sans méthode, on accumule les erreurs silencieuses, les caches non purgés et les mises à jour qui échouent sans un mot dans les logs. Cette page présente le digest d'une méthode de développement de modules Odoo 19 Community Edition sur VPS Ubuntu, pilotée par Claude (Cowork et Claude Code) : environnement type, modes de travail, protocole d'échange, commandes clés et table des pièges connus — le tout validé sur le terrain, module après module.
Environnement serveur type
La méthode s'appuie sur une installation standardisée : mêmes chemins, mêmes services, mêmes conventions sur chaque VPS. Seuls les noms de bases et de domaines changent d'un projet à l'autre.
| Élément | Valeur type | À personnaliser |
|---|---|---|
| OS | Ubuntu 24.04 LTS | Non |
| Odoo | 19 Community Edition | Non |
| Utilisateur Odoo | odoo | Non |
| Config | /etc/odoo/odoo.conf | Non |
| Modules custom | /opt/odoo/custom_addons/ | Non |
| Modules natifs | /usr/lib/python3/dist-packages/odoo/addons/ | Non |
| Log | /var/log/odoo/odoo-server.log | Non |
| Service | odoo (systemctl) | Non |
| Log Nginx accès | /var/log/nginx/access.log | Non |
| Base dev | [NOM_PROJET]_dev | À définir |
| Base prod | [NOM_PROJET]_prod | À définir |
| Domaine dev | dev.[DOMAINE] | À définir |
| Domaine prod | [DOMAINE] | À définir |
Structure de dossiers du projet de développement
Côté poste de travail, chaque projet suit la même arborescence : fichiers de pilotage
(roadmap, suivi, bugs), boîte aux lettres pour les échanges entre agents, et un dossier
dev/odoo/ qui sépare les modules en cours des modules validés.
[NOM_PROJET]/
├── CLAUDE.md
├── NOTES_CLAUDE.md
├── ROADMAP.md
├── DAR.md
├── Documentation.md
├── BUGS.md
├── CONTRATS.md
├── SUIVI.md
├── boite_aux_lettres/
├── DOCS/
│ ├── specs/
│ ├── sources/
│ └── captures/
├── dev/
│ └── odoo/
│ ├── modules/ ← modules custom en développement
│ └── _staging/ ← modules validés, prêts pour la prod
└── index.db
Règle _staging/ : quand un module est validé sur dev
(tests OK, logs propres), il est copié dans _staging/ — le signal qu'il
est prêt pour le déploiement en production.
Trois modes de travail : A, B, C
Selon les outils activés sur le poste de l'utilisateur, trois modes de collaboration peuvent coexister. Ils se combinent : du dev simple en mode A seul jusqu'au cycle complet dev → déploiement → test en A + B + C.
Mode A — Cowork seul (Claude Cowork + VS Code SSH)
Le mode de base, toujours disponible. L'humain reste la courroie de transmission :
- Cowork prépare les modifications (contenu des fichiers)
- L'utilisateur colle dans VS Code ouvert en SSH sur le serveur dev
- L'utilisateur lance la commande de mise à jour du module
- L'utilisateur fournit les logs en cas d'erreur
- L'utilisateur confirme → mise à jour des fichiers de suivi
Mode B — Cowork + Code (collaboration via boîte aux lettres)
Recommandé pour les travaux lourds. Claude Code tourne sur le PC de
l'utilisateur et pilote le VPS via ssh/scp — il n'est
jamais installé sur le serveur. Cowork et Code ne se parlent
pas directement — ils échangent via des fichiers déposés dans
boite_aux_lettres/ (protocole REQ/RES, détaillé plus bas).
[Claude Cowork] [boite_aux_lettres/] [Claude Code — PC]
│ écrit REQ_*.md │ │
│─────────────────────────>│ lit REQ_*.md │
│ │<─────────────────────────────│
│ │ exécute via SSH → VPS │
│ lit RES_*.md │ écrit RES_*.md │
│<─────────────────────────│─────────────────────────────>│
│ archive → DOCS/ │ │
Mode C — Claude in Chrome (test navigateur direct)
Activé si l'extension Claude in Chrome est installée et connectée. Elle permet à Claude
de naviguer sur dev.[DOMAINE] et lire les pages rendues, lire les erreurs
de la console JS, inspecter les requêtes réseau (webhooks, routes), tester le responsive
et interagir avec plusieurs sessions simultanées (ex. propriétaire + enchérisseur).
Configuration type pour les tests multi-utilisateurs :
| Session | Rôle |
|---|---|
| Firefox normal | Admin back-office |
| Firefox privé | Propriétaire (user1) |
| Chromium normal | Intervenant (user2) |
| Chromium privé* | 2e intervenant ou 2e validation |
*Autoriser l'extension en mode privé : chrome://extensions →
Claude in Chrome → « Autoriser en mode navigation privée ».
Limite : les pages authentifiées nécessitent que l'utilisateur soit
déjà connecté — Claude ne gère pas les mots de passe.
Combinaisons recommandées
| Contexte | Modes actifs |
|---|---|
| Dev simple (modifs mineures) | A seul |
| Dev avec déploiement automatisé | A + B |
| Debug visuel navigateur | A + C |
| Cycle complet (dev → déploiement → test) | A + B + C |
Le protocole REQ_ / RES_ : deux IA qui s'écrivent
Cœur du mode B : un protocole d'échange par fichiers, traçable et archivable. Cowork
écrit une requête (REQ_), Claude Code l'exécute sur le VPS et répond
par un compte-rendu (RES_). Rien ne se perd, tout s'archive.
Convention de nommage
| Préfixe | Signification | Auteur |
|---|---|---|
REQ_ | Requête en attente de traitement | Cowork |
RES_ | Réponse à une REQ | Code |
U_ | Document urgent à traiter | — |
NP_ | Document non prioritaire | — |
D_ | Document à différer | — |
Structure d'une REQ_
---
type: REQ
de: Cowork (NOM_PROJET)
pour: Code (NOM_PROJET)
date: YYYY-MM-DD
statut: en attente
objet: [Description courte]
---
# REQ — [Titre de la demande]
## Contexte
[Fichiers modifiés + fonctionnalité concernée]
## Étapes
[Commandes exactes / code Python prêt à exécuter]
## Données source
[JSON, CSV ou références aux fichiers DOCS/sources/]
## À livrer dans RES_
[Compte-rendu attendu : logs + résultat des tests]
Cycle de vie d'un échange
- Cowork écrit
REQ_*.md→ dépose dansboite_aux_lettres/ - Code lit, exécute sur le VPS, écrit
RES_*.md - Cowork lit le RES_, résout les blocages si besoin
- Les deux fichiers sont archivés dans
DOCS/archives/echanges/ - La boîte aux lettres doit être vide à la fin de chaque session
Règles d'usage
- Cowork ne suppose jamais que Code a exécuté — il attend le
RES_avant de valider. - Code n'exécute jamais sans
REQ_préalable (sauf commande directe de l'utilisateur). - Les commandes destructives (DROP, DELETE, opérations en production) exigent une confirmation humaine explicite dans la REQ.
- La boîte aux lettres n'est pas un journal permanent — on archive après traitement.
Automatisme : une REQ après chaque modification de code
Dès que Cowork modifie ou crée un fichier de code (.py, .xml,
.csv, .js), il dépose automatiquement une REQ_
listant les fichiers modifiés, la commande de mise à jour adaptée et les points à tester.
La commande dépend du type de modification :
| Type de modification | Action requise |
|---|---|
.py controller uniquement | sudo systemctl restart odoo |
.py model / .xml / .csv | -u [module] + restart |
Nouveau fichier dans data/ | -u [module] |
__manifest__.py | -u [module] |
REQ de type SCAFFOLD : un module complet en une passe
Le scaffold est une REQ spéciale : Cowork écrit le code complet de tous les
fichiers d'un nouveau module, et Code se contente de les créer, d'installer le module
et de lancer les tests. À ne pas confondre avec le odoo-bin scaffold
officiel, qui ne génère qu'un squelette vide — ici, le module est entier et prêt à
l'installation.
Quand l'utiliser : création d'un nouveau module (aucun fichier existant à écraser), cahier des charges complet, modèles bien définis, pièges connus intégrés au code fourni dès la rédaction.
---
type: REQ
objet: Scaffold complet — module [NOM]
---
## PDCA à appliquer
[Tableau des pièges à respecter]
## Étapes
1. Créer les N fichiers dans dev/modules/[NOM]/
2. py_compile sur tous les .py
3. systemctl stop odoo
4. -i [NOM] -d [DB] --test-enable --stop-after-init
5. systemctl start odoo
6. Rédiger le RES
## Fichiers à générer
### [NOM]/__init__.py
[code complet]
### [NOM]/__manifest__.py
[code complet]
...
Le RES attendu détaille : nombre de tests détectés/exécutés/échoués, extrait des logs d'installation, nouveaux pièges détectés, état global du module.
Bilan terrain (mesuré) : sur 4 scaffolds consécutifs, de 0 à 6 corrections minimes par module grâce à l'intégration des pièges connus en amont — le dernier : 0 correction, réussite à la première tentative.
Commandes Odoo de référence
Les commandes essentielles du cycle de développement, telles qu'exécutées sur le VPS.
Mise à jour / installation de module
# Un module
sudo -u odoo /usr/bin/odoo -c /etc/odoo/odoo.conf -u [MODULE] -d [DB_DEV] --stop-after-init
sudo systemctl restart odoo
# Plusieurs modules
sudo -u odoo /usr/bin/odoo -c /etc/odoo/odoo.conf -u [MOD1],[MOD2] -d [DB_DEV] --stop-after-init
# Installation d'un nouveau module
sudo -u odoo /usr/bin/odoo -c /etc/odoo/odoo.conf -i [MODULE] -d [DB_DEV] --stop-after-init
sudo systemctl restart odoo
Logs
sudo tail -f /var/log/odoo/odoo-server.log # temps réel
sudo tail -50 /var/log/odoo/odoo-server.log # 50 dernières lignes
sudo tail -100 /var/log/odoo/odoo-server.log | grep -E "ERROR|WARNING"
sudo journalctl -u odoo -n 100 --no-pager # logs système (crash)
Service
sudo systemctl restart odoo # après modif Python/controllers
sudo systemctl stop odoo # avant un -u propre
sudo systemctl start odoo # après le -u
sudo systemctl status odoo # vérifier l'état
Cache bytecode (si un .py modifié n'est pas pris en compte)
find /opt/odoo/custom_addons/ -name "*.pyc" -delete
sudo systemctl restart odoo
PostgreSQL
sudo -u odoo psql -d [DB_DEV]
# État des modules
sudo -u odoo psql -d [DB_DEV] -c "SELECT name, state FROM ir_module_module WHERE name LIKE '[PREFIX]%' ORDER BY name;"
Mode debug interface
https://dev.[DOMAINE]/web?debug=1 — vérifier les menus, inspecter les vues,
consulter ir.config_parameter.
Structure d'un module custom
Chaque module suit la structure canonique Odoo : modèles, vues, sécurité, données initiales, controllers pour les routes website.
nom_module/
├── __init__.py
├── __manifest__.py ← version, dépendances, données
├── models/
│ ├── __init__.py
│ └── nom_model.py
├── views/
│ └── nom_model_views.xml
├── security/
│ ├── ir.model.access.csv
│ └── security.xml (si groupes custom)
├── data/
│ └── data.xml (données initiales optionnelles)
├── controllers/
│ ├── __init__.py
│ └── main.py (si routes website)
└── static/
└── src/
└── xml/ (templates OWL si nécessaire)
Le manifeste type
{
'name': 'Nom du module',
'version': '19.0.1.0.0',
'category': 'Custom',
'depends': ['base', 'web'], # ajouter les dépendances réelles
'data': [
'security/ir.model.access.csv',
'views/nom_model_views.xml',
],
'installable': True,
'auto_install': False,
}
Valider et promouvoir : TEST et STAGING
Deux commandes de projet encadrent la validation : TEST vérifie la syntaxe
et met à jour le module sur dev en lisant les logs ; STAGING promeut le
module validé vers le dossier de pré-production.
Protocole TEST
# 1. Syntaxe Python
python3 -m py_compile /opt/odoo/custom_addons/[MODULE]/models/[FICHIER].py
# 2. Mise à jour sur dev
sudo -u odoo /usr/bin/odoo -c /etc/odoo/odoo.conf -u [MODULE] -d [DB_DEV] --stop-after-init
sudo systemctl restart odoo
# 3. Logs
sudo tail -50 /var/log/odoo/odoo-server.log | grep -E "ERROR|WARNING|[MODULE]"
Protocole STAGING
- Tests OK sur dev (logs propres, fonctionnalité validée)
- Copier le module dans
dev/odoo/_staging/[MODULE]/ - ROADMAP : tâche marquée validée, prête pour le staging
- Mettre à jour les notes de travail
Convention de versioning
Le format Odoo standard est conservé : 19.0.[MAJEUR].[MINEUR].[PATCH].
Pour les projets custom, la convention A.B.C.D encode aussi le rythme
des sessions de travail :
| Lettre | Signification |
|---|---|
| A | Version majeure du projet |
| B | Version mineure (fonctionnalité majeure) |
| C | Numéro de session (incrémenté à chaque session de travail) |
| D | Nombre de modifications cumulées dans la session |
Exemple : v1.2.5.12 = majeure 1, mineure 2, 5e session, 12 modifications.
La version est tracée dans le fichier de suivi à chaque mise à jour.
Pièges connus — table de triage rapide
La vraie valeur d'une méthode se mesure à sa mémoire des erreurs. Cette table de triage, alimentée en continu par le terrain, permet de diagnostiquer en quelques secondes les pannes les plus fréquentes du développement Odoo 19.
| Problème | Cause | Solution |
|---|---|---|
| Mise à jour silencieuse (aucun log) | Erreur XML/Python bloquante silencieuse | Lire le log après -u |
| Logs vides dans le terminal | logfile configuré dans odoo.conf | Lire /var/log/odoo/odoo-server.log |
.py modifié non pris en compte | Cache bytecode .pyc | find ... -name "*.pyc" -delete + restart |
Menu invisible après -u | Erreur XML silencieuse / action manquante | Vérifier logs + mode debug |
attrs deprecated | Odoo 17+ supprime attrs | invisible="condition" directe |
t-set en back-office | Directive OWL interdite en vue formulaire | Templates QWeb website uniquement |
res.partner.mobile → AttributeError | Champ absent en Odoo 19 CE | Utiliser phone et email |
| 502 Bad Gateway | Odoo planté au démarrage | sudo tail -50 /var/log/odoo/odoo-server.log |
| CSRF manquant dans un form POST | Token requis dans tout | |
| Champ inconnu dans un domain filter | Module dépendant non déclaré | Ajouter la dépendance dans __manifest__.py |
/website/info expose les modules | Route publique listant les modules — risque sécurité | Site Web → Pages système → désactiver /website/info |
_sql_constraints → upgrade silencieux | Odoo 19 n'utilise plus _sql_constraints | Migrer vers models.Constraint en attribut direct |
models.Constraint dans une liste → AttributeError | Syntaxe liste interdite | name_unique = models.Constraint('UNIQUE(name)', 'Message') |
groups_id dans un domain ORM → erreur | Non searchable en Odoo 19 | .filtered(lambda u: u.has_group('base.group_system')) |
StripeObject.get() → AttributeError | SDK Stripe récent : .get() non supporté | pi[key] dans un helper _pi_field(pi, key, default) |
'cancelled' dans une Selection → rollback silencieux | Valeur inexistante → rollback PostgreSQL sans log | Utiliser une valeur déclarée (ex. 'rejected') |
numbercall dans ir.cron → erreur install | Champ supprimé en Odoo 19 | Ne pas l'utiliser |
active="True" dans ir.cron → ignoré | Forme texte non interprétée | |
en search → erreur | n'accepte aucun attribut | simple |
type="json" dans @http.route → KO | Deprecated | type="jsonrpc" |
Ligne ACL sans group_id → warning bloquant | Obligatoire en Odoo 19 | Déclarer un groupe explicite |
Préserver la configuration de production : le pattern noupdate
Pour un modèle singleton (configuration, clés API), il faut empêcher les mises à jour
de module (-u) de réinitialiser les données saisies en production :
Configuration
# models/config.py
@api.model
def get_config(self):
records = self.search([], limit=1)
if not records:
return self.create({'name': 'Configuration'})
return records
noupdate="1" → Odoo crée l'enregistrement à l'installation mais ne le
modifie plus lors des -u suivants. Les clés sensibles (API, webhooks)
saisies en production sont préservées.
Règles absolues du développement
Comme pour le pilotage, le développement assisté par IA repose sur un cadre de sécurité strict — c'est lui qui rend la vitesse tenable.
- Jamais d'action sur la base de production sans confirmation humaine explicite.
- Ne pas modifier
/etc/odoo/odoo.confsans discussion préalable. - Ne pas supprimer ou écraser un fichier sans le lire d'abord.
- Ne pas modifier la structure de la base de données hors ORM Odoo.
- Toujours vérifier les logs après un
-u. _staging/= signal de validation — n'y copier que les modules testés.
Cette page est le pendant « développement » de la méthode. Pour le pilotage quotidien d'Odoo en langage naturel (devis, CRM, factures, contacts), voir la page Piloter Odoo en langage naturel avec Claude.
Méthode IA&TIC — © 2026 IA&TIC — Freddy Maillard & Fabrice-Loïc Barbier — CC BY-SA 4.0