Comment créer des modules Odoo personnalisés : guide du développeur sur OWL, ORM et héritage
Lorsque les 82 modules officiels et plus de 40 000 modules communautaires d'Odoo ne couvrent pas exactement les besoins de votre entreprise, le développement de modules personnalisés comble cette lacune. Ce guide couvre les principes fondamentaux de la création de modules Odoo en 2026, y compris la structure des modules, le cadre frontal OWL, les modèles ORM, les mécanismes d'héritage et les meilleures pratiques alignées sur les directives de l'OCA (Odoo Community Association).
Qu'est-ce qu'un module Odoo personnalisé ?
Un module Odoo personnalisé est un package autonome de logique backend Python, de définitions de vues XML, de composants frontend JavaScript et de règles de sécurité qui étendent les fonctionnalités d'Odoo. Les modules peuvent ajouter des fonctionnalités entièrement nouvelles, modifier le comportement existant ou intégrer Odoo à des systèmes externes. Chaque élément d'Odoo lui-même est un module, ce qui rend l'architecture intrinsèquement extensible.
Structure du répertoire des modules
Un module bien organisé suit cette structure standard :
my_custom_module/
├── __init__.py # Python package init
├── __manifest__.py # Module metadata and dependencies
├── models/
│ ├── __init__.py
│ └── my_model.py # Business logic and data models
├── views/
│ ├── my_model_views.xml # Form, tree, and kanban views
│ └── menu.xml # Menu items and actions
├── security/
│ ├── ir.model.access.csv # Access control list
│ └── security.xml # Record rules and groups
├── data/
│ └── data.xml # Default data records
├── static/
│ └── src/
│ ├── js/ # OWL components
│ ├── css/ # Stylesheets
│ └── xml/ # QWeb templates
├── wizard/ # Transient models for wizards
├── reports/ # QWeb report templates
└── tests/ # Unit tests
Le fichier manifeste
Le fichier __manifest__.py définit l'identité de votre module :
{
'name': 'My Custom Module',
'version': '18.0.1.0.0',
'category': 'Custom',
'summary': 'Short description of what the module does',
'description': """
Long description with details about
features and configuration.
""",
'author': 'ECOSIRE',
'website': 'https://ecosire.com',
'license': 'LGPL-3',
'depends': ['base', 'sale', 'stock'],
'data': [
'security/ir.model.access.csv',
'views/my_model_views.xml',
'views/menu.xml',
'data/data.xml',
],
'assets': {
'web.assets_backend': [
'my_custom_module/static/src/js/**/*',
'my_custom_module/static/src/css/**/*',
'my_custom_module/static/src/xml/**/*',
],
},
'installable': True,
'application': False,
'auto_install': False,
}
Convention de version : {odoo_version}.{major}.{minor}.{patch} (par exemple, 18.0.1.0.0).
Travailler avec l'ORM
Le mappage objet-relationnel (ORM) d'Odoo est la base de tout développement backend. Les modèles sont mappés aux tables de base de données et l'ORM fournit des opérations CRUD, des champs calculés, des contraintes et une gestion des flux de travail.
Définir un modèle
from odoo import models, fields, api
class ProjectTask(models.Model):
_name = 'my_module.task'
_description = 'Project Task'
_order = 'priority desc, create_date desc'
name = fields.Char(string='Task Name', required=True)
description = fields.Html(string='Description')
state = fields.Selection([
('draft', 'Draft'),
('in_progress', 'In Progress'),
('done', 'Done'),
('cancelled', 'Cancelled'),
], default='draft', tracking=True)
assigned_to = fields.Many2one('res.users', string='Assigned To')
deadline = fields.Date(string='Deadline')
priority = fields.Selection([
('0', 'Normal'),
('1', 'Important'),
('2', 'Urgent'),
], default='0')
tag_ids = fields.Many2many('my_module.tag', string='Tags')
progress = fields.Float(compute='_compute_progress', store=True)
Référence des types de champs
| Type de champ | Type Python | Cas d'utilisation | |---|---|---| | Char | str | Texte court (nom, référence) | | Texte | str | Texte long et brut | | HTML | str | Contenu de texte enrichi | | Entier | entier | Nombres entiers | | Flotteur | flotter | Nombres décimaux | | Booléen | booléen | Drapeaux Vrai/Faux | | Dates | date | Date sans heure | | Dateheure | dateheure | Date avec heure | | Sélection | str | Choix déroulants | | Plusieurs2un | entier | Lien vers un enregistrement | | Un2many | liste | Inverse de Many2one | | Beaucoup2many | liste | Lien vers plusieurs enregistrements | | Binaire | octets | Pièces jointes |
Champs et contraintes calculés
@api.depends('subtask_ids.state')
def _compute_progress(self):
for task in self:
total = len(task.subtask_ids)
done = len(task.subtask_ids.filtered(
lambda t: t.state == 'done'
))
task.progress = (done / total * 100) if total else 0
@api.constrains('deadline')
def _check_deadline(self):
for task in self:
if task.deadline and task.deadline < fields.Date.today():
raise ValidationError(
"Deadline cannot be in the past."
)
Mécanismes d'héritage
Odoo propose trois types d'héritage, chacun servant un objectif différent :
1. Héritage de classe (extension)
Étendez un modèle existant en ajoutant des champs ou en remplaçant des méthodes. C'est le modèle le plus courant.
class SaleOrderExtend(models.Model):
_inherit = 'sale.order'
custom_reference = fields.Char(string='Custom Ref')
approved_by = fields.Many2one('res.users')
def action_confirm(self):
# Add custom logic before standard confirmation
for order in self:
if order.amount_total > 10000 and not order.approved_by:
raise UserError("Orders over $10,000 require approval.")
return super().action_confirm()
2. Héritage des prototypes
Créez un nouveau modèle qui copie tous les champs et méthodes d'un modèle existant.
class CustomPartner(models.Model):
_name = 'my_module.partner'
_inherit = 'res.partner' # Copies structure
_description = 'Custom Partner'
3. Héritage de délégation
Créez un nouveau modèle qui délègue à un modèle existant via un lien Many2one. Les champs du modèle parent apparaissent de manière transparente sur le modèle enfant.
class LibraryMember(models.Model):
_name = 'library.member'
_inherits = {'res.partner': 'partner_id'}
partner_id = fields.Many2one('res.partner', required=True,
ondelete='cascade')
membership_date = fields.Date()
member_number = fields.Char()
Le framework OWL (Frontend)
Odoo 18 utilise OWL (Odoo Web Library) comme framework frontend. OWL est un framework basé sur des composants similaire à React ou Vue mais conçu spécifiquement pour les besoins d'Odoo.
Composant OWL de base
/** @odoo-module */
import { Component, useState } from "@odoo/owl";
import { registry } from "@web/core/registry";
class TaskDashboard extends Component {
static template = "my_module.TaskDashboard";
setup() {
this.state = useState({
tasks: [],
filter: 'all',
});
this.loadTasks();
}
async loadTasks() {
this.state.tasks = await this.env.services.orm.searchRead(
"my_module.task",
[["state", "!=", "cancelled"]],
["name", "state", "assigned_to", "deadline"]
);
}
get filteredTasks() {
if (this.state.filter === 'all') return this.state.tasks;
return this.state.tasks.filter(
t => t.state === this.state.filter
);
}
}
Modèle QWeb (XML)
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="my_module.TaskDashboard">
<div class="o_task_dashboard">
<div class="task-filters">
<button t-on-click="() => state.filter = 'all'">All</button>
<button t-on-click="() => state.filter = 'in_progress'">
In Progress
</button>
</div>
<div class="task-list">
<t t-foreach="filteredTasks" t-as="task" t-key="task.id">
<div class="task-card">
<span t-esc="task.name"/>
</div>
</t>
</div>
</div>
</t>
</templates>
Configuration de la sécurité
Chaque modèle a besoin de règles d'accès explicites. Sans eux, aucun utilisateur ne peut accéder aux données du modèle.
Liste de contrôle d'accès (ir.model.access.csv)
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_task_user,task.user,model_my_module_task,base.group_user,1,1,1,0
access_task_manager,task.manager,model_my_module_task,my_module.group_manager,1,1,1,1
Règles d'enregistrement (security.xml)
<record id="task_own_rule" model="ir.rule">
<field name="name">Own Tasks Only</field>
<field name="model_id" ref="model_my_module_task"/>
<field name="domain_force">
[('assigned_to', '=', user.id)]
</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
</record>
Test de votre module
Odoo prend en charge les tests unitaires Python en utilisant le framework unittest avec des classes de test spécifiques à Odoo :
from odoo.tests.common import TransactionCase
class TestTask(TransactionCase):
def setUp(self):
super().setUp()
self.task = self.env['my_module.task'].create({
'name': 'Test Task',
'state': 'draft',
})
def test_task_creation(self):
self.assertEqual(self.task.state, 'draft')
self.assertEqual(self.task.progress, 0)
def test_deadline_constraint(self):
with self.assertRaises(ValidationError):
self.task.write({
'deadline': fields.Date.subtract(
fields.Date.today(), days=1
),
})
Exécutez des tests avec : odoo-bin -d test_db --test-enable -i my_custom_module --stop-after-init
Questions fréquemment posées
Q : Combien de temps faut-il pour créer un module Odoo personnalisé ? Les modules simples (nouveaux champs, vues de base) prennent 1 à 3 jours. Les modules modérés (nouveaux modèles, flux de travail, rapports) prennent 1 à 3 semaines. Les modules complexes (systèmes multimodèles, intégrations externes, composants OWL personnalisés) prennent 4 à 12 semaines. Pour les entreprises qui ont besoin de modules personnalisés mais qui manquent de développeurs Odoo internes, embauchez un développeur Odoo expérimenté de notre équipe.
Q : Dois-je modifier le code principal d'Odoo ou créer un module séparé ? Créez toujours un module séparé. La modification du code principal interrompt la mise à niveau et crée des conflits de fusion lors des mises à jour de version. Utilisez l'héritage pour étendre les modèles et les vues existants à partir de votre module personnalisé.
Q : Que sont les lignes directrices de l'OCA ? L'Odoo Community Association (OCA) publie des normes de codage pour la qualité des modules, notamment les conventions de dénomination, les exigences en matière de documentation, les attentes en matière de couverture des tests et les règles de style de code. Le respect des directives de l'OCA garantit que votre module est maintenable et compatible avec l'écosystème communautaire plus large.
Obtenir de l'aide professionnelle
La création de modules Odoo personnalisés nécessite une expertise dans les conventions de framework Python, JavaScript, PostgreSQL et Odoo. Que vous ayez besoin d'une simple extension de terrain ou d'un système multi-modules complexe, les services de personnalisation Odoo d'ECOSIRE fournissent des modules prêts à la production, construits selon les normes OCA.
Contactez-nous pour discuter des exigences de votre module personnalisé et obtenir une estimation de développement.
Rédigé par
ECOSIRE Research and Development Team
Création de produits numériques de niveau entreprise chez ECOSIRE. Partage d'analyses sur les intégrations Odoo, l'automatisation e-commerce et les solutions d'entreprise propulsées par l'IA.
Articles connexes
Intégration Amazon.de Odoo : vendre sur la plus grande place de marché d'Allemagne avec Odoo ERP
Comment intégrer Amazon.de à Odoo ERP pour le marché allemand. Couvre FBA Allemagne, l'exécution paneuropéenne, la TVA allemande, la conformité VerpackG et le rapprochement des règlements.
Entrer sur le marché allemand du commerce électronique avec Odoo : guide étape par étape pour les vendeurs internationaux
Guide complet pour les vendeurs internationaux entrant sur le marché allemand du commerce électronique. Couvre l'analyse du marché, les exigences légales, l'enregistrement à la TVA, la sélection du marché et la configuration d'Odoo ERP pour la vente aux consommateurs allemands.
Gérer les retours du commerce électronique allemand avec Odoo : stratégies pour les marchés à haut rendement
Comment gérer les taux de retour élevés du commerce électronique en Allemagne à l'aide d'Odoo ERP. Couvre les workflows de traitement des retours, l'analyse des codes de motif, l'automatisation du réapprovisionnement et les politiques spécifiques au marché pour Zalando, Otto, Amazon.de et Kaufland.