Skip to Content

Créer un module Odoo personnalisé de A à Z

Créer un module Odoo personnalisé est la compétence fondamentale de tout développeur Odoo. Ce guide vous emmène de la structure de fichiers jusqu'au déploiement en production, avec des exemples de code réels et des bonnes pratiques issues de projets clients réels au Québec.

1. Prérequis et environnement de développement

Avant d'écrire la moindre ligne de code, assurez-vous d'avoir un environnement fonctionnel. Pour Odoo 17, voici ce dont vous avez besoin :

  • Python 3.10+ — Odoo 17 requiert Python 3.10 minimum (python.org)
  • PostgreSQL 14+ — la base de données utilisée par Odoo
  • Odoo 17 Community ou Enterprise — cloné depuis github.com/odoo/odoo
  • Un IDE — VS Code avec l'extension Python, ou PyCharm
  • Un dossier addons personnalisé — séparé du core Odoo pour isoler vos développements
💡
Conseil

Utilisez un environnement virtuel Python (venv) dédié à chaque projet Odoo. Cela évite les conflits de dépendances entre projets et simplifie la maintenance à long terme.

2. Structure d'un module Odoo : les fichiers obligatoires

Un module Odoo est simplement un dossier Python avec une structure bien définie. Voici la structure minimale pour un module fonctionnel, que nous appellerons mon_module :

mon_module/
├── __init__.py # Rend le dossier importable en Python
├── __manifest__.py # Métadonnées du module (obligatoire)
├── models/
│ ├── __init__.py
│ └── mon_modele.py # Vos modèles de données
├── views/
│ └── mon_modele_views.xml
├── security/
│ ├── ir.model.access.csv
│ └── security.xml
└── data/ # Données de démonstration ou de configuration
└── demo.xml

3. Le fichier __manifest__.py expliqué

Le fichier __manifest__.py est le point d'entrée de votre module. Odoo le lit pour enregistrer votre module dans la liste des applications disponibles. Voici un manifest complet et commenté :

__manifest__.pyPython
{
    'name': 'Mon Module Personnalisé',
    'version': '17.0.1.0.0',          # Format : version_odoo.majeure.mineure.patch.révision
    'category': 'Services',
    'summary': 'Description courte du module (max 80 caractères)',
    'author': 'Kuantiik',
    'website': 'https://kuantiik.com',
    'license': 'LGPL-3',
    'depends': [
        'base',               # Dépendance minimale
        'mail',               # Pour les fonctionnalités de messagerie
        'sale',               # Si vous étendez le module Ventes
    ],
    'data': [
        'security/ir.model.access.csv',
        'security/security.xml',
        'views/mon_modele_views.xml',
    ],
    'demo': ['data/demo.xml'],
    'installable': True,
    'application': True,
    'auto_install': False,
}
⚠️
Attention

L'ordre des fichiers dans la liste data est important. Les fichiers de sécurité doivent toujours être chargés avant les vues, sinon Odoo lèvera une erreur d'accès à l'installation.

4. Créer un modèle avec ses champs

Les modèles Odoo héritent de models.Model et définissent les tables de base de données via des champs Python. Odoo gère la création et la migration des tables automatiquement.

models/projet_client.pyPython
from odoo import models, fields, api
from odoo.exceptions import ValidationError


class ProjetClient(models.Model):
    _name = 'mon.module.projet'
    _description = 'Projet Client'
    _inherit = ['mail.thread', 'mail.activity.mixin']
    _order = 'date_debut desc'

    name = fields.Char(string='Nom du projet', required=True, tracking=True)
    client_id = fields.Many2one(comodel_name='res.partner', string='Client', required=True)
    responsable_id = fields.Many2one(
        comodel_name='res.users', string='Responsable',
        default=lambda self: self.env.user
    )
    date_debut = fields.Date(string='Date de début', required=True)
    date_fin = fields.Date(string='Date de fin prévue')
    state = fields.Selection(
        selection=[
            ('brouillon', 'Brouillon'),
            ('en_cours', 'En cours'),
            ('termine', 'Terminé'),
            ('annule', 'Annulé'),
        ],
        default='brouillon', tracking=True
    )
    duree_jours = fields.Integer(string='Durée (jours)', compute='_compute_duree', store=True)

    @api.depends('date_debut', 'date_fin')
    def _compute_duree(self):
        for record in self:
            if record.date_debut and record.date_fin:
                record.duree_jours = (record.date_fin - record.date_debut).days
            else:
                record.duree_jours = 0

    @api.constrains('date_debut', 'date_fin')
    def _check_dates(self):
        for record in self:
            if record.date_fin and record.date_debut > record.date_fin:
                raise ValidationError("La date de fin doit être postérieure à la date de début.")

    def action_demarrer(self):
        self.write({'state': 'en_cours'})

    def action_terminer(self):
        self.write({'state': 'termine'})

5. Définir les vues : liste, formulaire, recherche

Les vues Odoo sont définies en XML. Voici les trois vues essentielles pour rendre votre modèle utilisable dans l'interface :

views/projet_client_views.xmlXML
<?xml version="1.0" encoding="utf-8"?>
<odoo>

  <!-- VUE LISTE -->
  <record id="view_projet_list" model="ir.ui.view">
    <field name="name">mon.module.projet.list</field>
    <field name="model">mon.module.projet</field>
    <field name="arch" type="xml">
      <list string="Projets" decoration-success="state=='termine'">
        <field name="name"/>
        <field name="client_id"/>
        <field name="responsable_id"/>
        <field name="date_debut"/>
        <field name="duree_jours"/>
        <field name="state" widget="badge"/>
      </list>
    </field>
  </record>

  <!-- VUE FORMULAIRE -->
  <record id="view_projet_form" model="ir.ui.view">
    <field name="name">mon.module.projet.form</field>
    <field name="model">mon.module.projet</field>
    <field name="arch" type="xml">
      <form>
        <header>
          <button name="action_demarrer" string="Démarrer" type="object"
                  class="btn-primary" invisible="state != 'brouillon'"/>
          <button name="action_terminer" string="Marquer terminé" type="object"
                  invisible="state != 'en_cours'"/>
          <field name="state" widget="statusbar"
                 statusbar_visible="brouillon,en_cours,termine"/>
        </header>
        <sheet>
          <group><group>
            <field name="name"/>
            <field name="client_id"/>
            <field name="responsable_id"/>
          </group><group>
            <field name="date_debut"/>
            <field name="date_fin"/>
            <field name="duree_jours"/>
          </group></group>
        </sheet>
        <chatter/>
      </form>
    </field>
  </record>

</odoo>

6. Droits d'accès et sécurité

Sans fichier de droits d'accès, votre module s'installera mais personne ne pourra accéder aux données. Le fichier ir.model.access.csv définit qui peut lire, créer, modifier et supprimer les enregistrements.

security/ir.model.access.csvCSV
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_projet_user,projet.user,model_mon_module_projet,base.group_user,1,1,1,0
access_projet_manager,projet.manager,model_mon_module_projet,base.group_system,1,1,1,1
ColonneValeurDescription
model_id:idmodel_mon_module_projetOdoo convertit automatiquement les points en underscores
perm_read1 ou 0Permission de lecture
perm_write1 ou 0Permission de modification
perm_create1 ou 0Permission de création
perm_unlink1 ou 0Permission de suppression
views/menus.xmlXML
<odoo>
  <record id="action_projet_list" model="ir.actions.act_window">
    <field name="name">Projets clients</field>
    <field name="res_model">mon.module.projet</field>
    <field name="view_mode">list,form</field>
  </record>
  <menuitem id="menu_root" name="Mon Module" sequence="10"/>
  <menuitem id="menu_projets" name="Projets" parent="menu_root"
            action="action_projet_list" sequence="10"/>
</odoo>

8. Hériter d'un modèle existant

L'héritage est l'une des fonctionnalités les plus puissantes d'Odoo. Il vous permet d'ajouter des champs ou de modifier le comportement de n'importe quel modèle existant sans toucher au code source d'Odoo.

models/res_partner_extend.pyPython
from odoo import models, fields


class ResPartnerExtend(models.Model):
    _inherit = 'res.partner'   # On hérite du modèle partenaire existant

    numero_neq = fields.Char(
        string='Numéro NEQ (Québec)',
        help="Numéro d'entreprise du Québec (Registraire des entreprises)"
    )
    est_client_gouvernemental = fields.Boolean(string='Client gouvernemental')

La règle d'or du développement Odoo : n'éditez jamais le code source d'Odoo directement. Utilisez toujours l'héritage. C'est ce qui garantit que vos personnalisations survivront aux mises à jour.

9. Installer et tester son module

TerminalBash
# Lancer Odoo avec votre dossier addons personnalisé
python odoo-bin \
  --addons-path=addons,/chemin/vers/vos/modules \
  --dev=all \
  -d nom_de_votre_base \
  -i mon_module

# Mettre à jour le module après modification
python odoo-bin \
  --addons-path=addons,/chemin/vers/vos/modules \
  -d nom_de_votre_base \
  -u mon_module
Mode développeur

Le flag --dev=all active le rechargement automatique des vues XML sans redémarrer le serveur. Activez-le aussi depuis l'interface : Paramètres → Mode développeur.

10. Erreurs fréquentes et comment les éviter

ErreurCause probableSolution
AccessErrorFichier ir.model.access.csv manquant ou mal configuréVérifier que le modèle est listé et le fichier inclus dans le manifest
ValueError: External ID not foundRéférence à un ID externe inexistant dans un fichier XMLVérifier l'orthographe et le module source de l'ID
Module invisible dans la liste'installable': False dans le manifestPasser à True et mettre à jour la liste des apps
Champ calculé non mis à jourDépendances @api.depends incomplètesAjouter tous les champs dont dépend le calcul dans le décorateur
UndefinedColumnColonne SQL non créée après ajout d'un champRelancer avec -u nom_module pour migrer le schéma

Pour aller plus loin, consultez la documentation officielle Odoo 17 — tutoriels serveur et la référence complète de l'ORM Odoo. Pour les questions de sécurité et de règles d'accès avancées, la documentation sécurité Odoo est indispensable.

Vous avez un besoin de développement Odoo sur mesure ?

Modules personnalisés, intégrations API, migration de données : notre équipe de développeurs Odoo certifiés au Québec vous accompagne de la conception au déploiement.

Discuter de votre projet


Migration Odoo : comment passer d'une vieille version sans tout perdre