Comment créer des modules Odoo personnalisés : guide du développeur sur OWL, ORM et héritage

Guide du développeur pour créer des modules Odoo personnalisés. Couvre la structure des modules, le framework OWL, l'héritage ORM, les vues, les règles de sécurité, les tests et les directives OCA.

E

ECOSIRE Research and Development Team

Équipe ECOSIRE

19 février 20267 min de lecture1.4k Mots

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.

Partager :
E

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.

Discutez sur WhatsApp