Se rendre au contenu

Modèles de base Odoo 19 : Model, TransientModel, AbstractModel

Bloc 3 · Framework ORM — Article 1/8 Modèles de base Odoo 19 : Model, TransientModel, AbstractModel Tout module Odoo repose sur des modèles Python.
26 avril 2026 par
Modèles de base Odoo 19 : Model, TransientModel, AbstractModel
B.Mustapha

Bloc 3 · Framework ORM — Article 1/8

Modèles de base Odoo 19 : Model, TransientModel, AbstractModel

Tout module Odoo repose sur des modèles Python. Il existe trois classes de base, chacune avec un usage très précis. On les découvre en construisant les premiers modèles du module odooskills_helpdesk.

~15 minutes de lecture

Ce que tu vas apprendre

Model

models.Model — le persistant, 90% des cas.

Wizard

TransientModel — éphémère, purgé par un cron.

Mixin

AbstractModel — code partagé, sans table.

Helpdesk

4 modèles testés, module fil rouge en marche.

Prérequis
  • Avoir lu les articles T05 (première approche) et T06 (architecture)
  • Un environnement Odoo 19 fonctionnel et un IDE configuré
  • Connaître les bases de Python (classes, héritage)
Le module fil rouge — Cet article est le premier du Bloc 3. À partir d'ici, on construit progressivement le module odooskills_helpdesk. Chaque article ajoute une pièce. Si tu suis toute la série, tu obtiens un module complet à la fin.

Les trois types de modèles, en un coup d'œil

Odoo fournit trois classes de base. Le choix dépend de ce que tu veux stocker et pendant combien de temps.

Les 3 types de modèles Odoo 19 Comparaison : stockage, durée de vie et usage models.Model modèle persistant Stockage Table PostgreSQL créée dédiée Durée de vie Illimitée Usage typique Données métier : clients, tickets, commandes... Exemple T08 helpdesk.ticket helpdesk.ticket.category → 90% des modèles que tu vas écrire models.TransientModel modèle temporaire Stockage Table PostgreSQL purgée par un cron (vacuum) Durée de vie Quelques heures max Usage typique Wizards (dialogues modaux à étapes) Exemple T08 helpdesk.ticket.close. wizard → Pour tout formulaire qui ne persiste pas models.AbstractModel mixin réutilisable Stockage AUCUNE table créée en base Durée de vie N/A (pas de records) Usage typique Mutualiser champs et méthodes entre modèles Exemple T08 odooskills.helpdesk. mixin → mail.thread et mail.activity.mixin Règle simple : données durables → Model  •  formulaire éphémère → TransientModel  •  code partagé → AbstractModel

Règle simple : données durables → Model ; formulaire éphémère → TransientModel ; code partagé → AbstractModel.

1 — models.Model : le modèle persistant

C'est la classe que tu utiliseras dans 90% des cas. Elle crée une table PostgreSQL dédiée et chaque instance (un "record") est stockée durablement en base.

Caractéristiques

  • Une table PostgreSQL est créée automatiquement à l'installation du module
  • Les records persistent — ils survivent au redémarrage du serveur
  • Toutes les fonctionnalités Odoo sont disponibles : vues, recherche, ACL, ORM

Exemple — le ticket helpdesk

On crée notre premier modèle dans models/helpdesk_ticket.py :

from odoo import models, fields


class HelpdeskTicket(models.Model):
    """Ticket de support — modèle persistant."""
    _name = 'helpdesk.ticket'
    _description = 'Ticket Helpdesk'
    _inherit = ['mail.thread', 'mail.activity.mixin', 'odooskills.helpdesk.mixin']

    name = fields.Char(string='Sujet', required=True, tracking=True)
    description = fields.Text(string='Description')
    category_id = fields.Many2one(
        comodel_name='helpdesk.ticket.category',
        string='Catégorie',
        ondelete='restrict',
    )
    partner_id = fields.Many2one('res.partner', string='Client')
    state = fields.Selection(
        selection=[
            ('new', 'Nouveau'),
            ('in_progress', 'En cours'),
            ('done', 'Résolu'),
        ],
        default='new',
        required=True,
        tracking=True,
    )

Et un modèle compagnon pour les catégories dans models/helpdesk_ticket_category.py :

from odoo import models, fields


class HelpdeskTicketCategory(models.Model):
    """Catégorie de ticket — modèle persistant simple."""
    _name = 'helpdesk.ticket.category'
    _description = 'Catégorie de ticket helpdesk'

    name = fields.Char(string='Nom', required=True)
    color = fields.Integer(string='Couleur')
Convention de nommage_name est en minuscules avec des points (helpdesk.ticket), le nom de classe Python en PascalCase (HelpdeskTicket). Odoo convertit _name en table PostgreSQL en remplaçant les points par des underscores : helpdesk_ticket.

2 — models.TransientModel : le modèle temporaire

Un TransientModel a aussi une table PostgreSQL, mais celle-ci est purgée périodiquement par un cron (la base ne grossit pas indéfiniment). C'est la classe à utiliser pour tout ce qui est éphémère : wizards, dialogues, formulaires intermédiaires.

Caractéristiques

  • Table PostgreSQL créée, mais vidée toutes les quelques heures par ir.cron.vacuum
  • Pas adapté aux données métier — tout record peut disparaître
  • Les wizards d'Odoo (popups avec boutons) sont presque tous des TransientModel

Exemple — wizard de clôture de ticket

On veut qu'un utilisateur puisse clôturer un ticket via une popup qui demande une note de résolution. Pas besoin de persister le formulaire : on le crée en TransientModel.

from odoo import models, fields


class HelpdeskTicketCloseWizard(models.TransientModel):
    """Wizard de clôture de ticket."""
    _name = 'helpdesk.ticket.close.wizard'
    _description = 'Clôture de ticket helpdesk (wizard)'

    ticket_id = fields.Many2one(
        comodel_name='helpdesk.ticket',
        string='Ticket',
        required=True,
    )
    resolution_note = fields.Text(string='Note de résolution')

    def action_close(self):
        self.ensure_one()
        self.ticket_id.write({
            'state': 'done',
            'description': (self.ticket_id.description or '') +
                           '\n\nRésolution : ' + (self.resolution_note or ''),
        })
        return {'type': 'ir.actions.act_window_close'}
Piège courant — Ne stocke jamais de données métier dans un TransientModel. Le vacuum les efface sans prévenir. Les transient sont uniquement un support de dialogue avec l'utilisateur.

3 — models.AbstractModel : le mixin réutilisable

Un AbstractModel n'a aucune table en base. Il sert uniquement à partager du code — champs et méthodes — entre plusieurs modèles qui l'héritent.

Caractéristiques

  • Pas de table PostgreSQL (pas de records non plus)
  • Les modèles qui l'héritent reçoivent ses champs et ses méthodes
  • Très utilisé dans Odoo : mail.thread, mail.activity.mixin, portal.mixin... sont tous des AbstractModel

Exemple — mixin helpdesk

On veut ajouter des champs communs (active, priority) à plusieurs modèles helpdesk sans dupliquer le code. Un mixin est parfait :

from odoo import models, fields


class HelpdeskMixin(models.AbstractModel):
    """Mixin réutilisable : pas de table en base, uniquement des
    champs et méthodes injectés dans les modèles qui l'héritent."""
    _name = 'odooskills.helpdesk.mixin'
    _description = 'Mixin helpdesk — champs communs'

    active = fields.Boolean(default=True)
    priority = fields.Selection(
        selection=[
            ('0', 'Normale'),
            ('1', 'Haute'),
            ('2', 'Urgente'),
        ],
        default='0',
    )

Dans helpdesk.ticket, on l'ajoute à _inherit :

class HelpdeskTicket(models.Model):
    _name = 'helpdesk.ticket'
    _inherit = ['mail.thread', 'mail.activity.mixin', 'odooskills.helpdesk.mixin']
    # ticket hérite automatiquement des champs active et priority
Vérification — Après installation du module, la commande SQL \d helpdesk_ticket dans psql montre bien les colonnes active et priority héritées du mixin, alors qu'aucune table odooskills_helpdesk_mixin n'existe.

4 — L'ordre de chargement compte

Quand un modèle hérite d'un autre, celui-ci doit exister au moment du chargement. L'ordre des imports dans models/__init__.py est donc important :

# models/__init__.py

# 1. D'abord le mixin (il n'a pas de dépendance)
from . import helpdesk_mixin

# 2. Puis le modèle de catégorie (référencé par le ticket)
from . import helpdesk_ticket_category

# 3. Puis le ticket (dépend du mixin et de la catégorie)
from . import helpdesk_ticket

# 4. Enfin le wizard (dépend du ticket)
from . import helpdesk_ticket_close_wizard

Si tu importes helpdesk_ticket avant helpdesk_mixin, tu obtiens cette erreur à l'installation :

TypeError: Model 'helpdesk.ticket' inherits from non-existing
model 'odooskills.helpdesk.mixin'.

5 — Manifest et droits d'accès

Le fichier __manifest__.py déclare le module et ses dépendances :

{
    'name': 'OdooSkills Helpdesk',
    'version': '19.0.1.0.0',
    'category': 'Services/Helpdesk',
    'summary': 'Module fil rouge du blog OdooSkills — tickets de support',
    'author': 'OdooSkills',
    'license': 'LGPL-3',
    'depends': ['base', 'mail'],
    'data': [
        'security/ir.model.access.csv',
    ],
    'installable': True,
    'application': True,
}

Tout modèle Model ou TransientModel doit avoir au moins une règle d'accès dans security/ir.model.access.csv. Sans ça, Odoo refuse les opérations et log des warnings à l'installation :

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_helpdesk_ticket_user,helpdesk.ticket.user,model_helpdesk_ticket,base.group_user,1,1,1,1
access_helpdesk_ticket_category_user,helpdesk.ticket.category.user,model_helpdesk_ticket_category,base.group_user,1,1,1,1
access_helpdesk_ticket_close_wizard_user,helpdesk.ticket.close.wizard.user,model_helpdesk_ticket_close_wizard,base.group_user,1,1,1,1
Pas de règle pour l'AbstractModel — Puisqu'il n'a pas de table, il n'a pas besoin de droit d'accès. C'est le modèle qui hérite du mixin qui est soumis aux ACL.

6 — Installer et vérifier le module

On installe le module sur une base de test :

./odoo-bin -c config/odoo.conf -d odooskills_helpdesk_test \
    -i odooskills_helpdesk --stop-after-init

Puis on vérifie que les tables sont bien créées :

psql -d odooskills_helpdesk_test -c "\dt helpdesk*"

Résultat attendu — 3 tables, pas 4 :

                   List of relations
 Schema |             Name             | Type  | Owner
--------+------------------------------+-------+-------
 public | helpdesk_ticket              | table | odoo
 public | helpdesk_ticket_category     | table | odoo
 public | helpdesk_ticket_close_wizard | table | odoo
(3 rows)

Le mixin odooskills.helpdesk.mixin n'a volontairement pas de table — c'est bien la preuve que c'est un AbstractModel. Ses champs sont en revanche présents dans helpdesk_ticket :

psql -d odooskills_helpdesk_test -c "\d helpdesk_ticket" | grep -E 'active|priority'
 priority    | character varying |
 active      | boolean           |
⚠️ Changement en Odoo 19

Les décorateurs @api.multi et @api.one ont été supprimés depuis la v13, mais on les trouve encore dans de vieux tutoriels. Ne les utilise jamais :

# ❌ INTERDIT — erreur à l'installation
@api.multi
def action_close(self):
    ...

# ✅ CORRECT — simplement la méthode, sans décorateur
def action_close(self):
    ...

En Odoo 19, self est toujours un recordset. Si ta méthode ne doit traiter qu'un seul enregistrement à la fois, ajoute self.ensure_one() en première ligne.

Récapitulatif

Classe Table PG ? Durée de vie Usage
models.Model ✅ Oui Illimitée Données métier (90% des cas)
models.TransientModel ✅ Oui, mais purgée Quelques heures Wizards, dialogues éphémères
models.AbstractModel ❌ Non N/A Mixins — partager champs et méthodes

Ce qu'on a ajouté au module odooskills_helpdesk

  • helpdesk.ticket (Model) — 5 champs, héritera de mail.thread
  • helpdesk.ticket.category (Model) — 2 champs pour classer les tickets
  • helpdesk.ticket.close.wizard (TransientModel) — dialogue de clôture
  • odooskills.helpdesk.mixin (AbstractModel) — champs active et priority

Pour aller plus loin

  • Article suivant (T09) — Les attributs de modèles : _order, _rec_name, _sql_constraints, _check_company_auto et leurs usages avancés
  • Code source du module — disponible sur le dépôt GitHub d'OdooSkills, chaque article correspond à un commit
  • Ressources officielles :

Télécharge le Guide Technique Odoo 19

Architecture, pièges v19, checklist premier module — tout dans un PDF gratuit.

Télécharger le guide
Gestion des bases de données Odoo 19
Bloc 2 · Environnement dev — Article 4/4 Gestion des bases de données Odoo 19 Créer, dupliquer, sauvegarder, restaurer — les opérations que tu feras des…