Introduction
Le champ Many2One constitue l’un des piliers du modèle de données d’Odoo. Chaque fois qu’on rattache une commande à un client, qu’on assigne un produit à une catégorie ou qu’on relie une tâche à un projet, on crée une relation Many2One. C’est le type de champ relationnel le plus utilisé; le maîtriser est indispensable pour qui veut configurer, étendre ou développer sérieusement sur Odoo.
Vu du point de vue métier, ces champs sont ce qui permet à Odoo d’agir comme un système intégré plutôt que comme une suite d’applications isolées. Grâce aux liens Many2One, l’information circule entre documents — factures, commandes, fiches produits — et l’utilisateur peut naviguer naturellement entre eux sans réfléchir à la structure sous-jacente de la base de données.
Ce guide explique ce que stocke un Many2One, son comportement dans l’ORM et l’interface, comment le créer avec Odoo Studio ou en Python, et illustre son usage par des exemples concrets tirés du CRM, des ventes, de la logistique et de la comptabilité.
Qu’est-ce que le champ Many2One dans Odoo
Dans l’ORM d’Odoo, un champ Many2One représente un lien un-à-un vers un enregistrement d’un autre modèle, vu depuis le modèle courant : plusieurs enregistrements ici peuvent pointer vers un seul enregistrement là-bas. Autrement dit, de nombreuses commandes peuvent référencer un même client, ou plusieurs produits peuvent appartenir à la même catégorie.
Au niveau base de données, un Many2One se matérialise par une clé étrangère dans la table du modèle courant. Par exemple, si une commande référence l’ID client 42, la colonne partner_id de la table sale_order contient l’entier 42. L’ORM prend en charge la jointure et la récupération du nom lié sans intervention manuelle.
Côté interface, un Many2One se manifeste par un champ à sélection searchable (liste déroulante avec recherche). L’utilisateur commence à taper un nom, Odoo filtre les correspondances en temps réel, et le choix affiche le nom lisible de l’enregistrement lié plutôt que son identifiant interne — un comportement qui rend ce champ particulièrement ergonomique.
Exemple de définition Python simplifiée :
from odoo import fields, models
class SaleOrder(models.Model):
_inherit = 'sale.order'
x_account_manager_id = fields.Many2one(
comodel_name='res.users',
string='Account Manager',
ondelete='set null',
)
Le paramètre comodel_name indique le modèle cible ; ondelete définit l’effet sur l’enregistrement courant si la référence est supprimée. Les options principales sont cascade (supprime aussi l’enregistrement courant), set null (efface le lien) et restrict (empêche la suppression si des liens existent).
Dans Odoo Studio, le champ apparaît sous l’étiquette Many2One dans le sélecteur de champs. En l’ajoutant via Studio, vous choisissez simplement le modèle cible dans une liste et l’outil crée le champ automatiquement — une manière rapide et sans code d’établir des relations entre formulaires.
Comment fonctionne ce type de champ
En Python, lire un Many2One renvoie un recordset contenant l’enregistrement lié, ou un recordset vide si aucun lien n’existe. Vous pouvez accéder directement aux champs de l’enregistrement lié avec la notation pointée :
order = self.env['sale.order'].browse(1)
customer_name = order.partner_id.name
customer_city = order.partner_id.city
Cette navigation en chaîne est l’un des avantages majeurs de l’ORM : pas de jointures SQL explicites ni de requêtes supplémentaires à écrire — l’ORM s’en charge en coulisse.
Avec l’API XML-RPC, un Many2One est retourné sous la forme d’une liste à deux éléments : l’ID numérique et le nom affiché, par exemple [42, "Acme Corp"]. Si le champ est vide, l’API renvoie False. C’est un détail important à gérer lors du traitement automatisé des réponses.
Attributs clés du champ
Voici les propriétés les plus utiles et configurables sur un Many2One dans Odoo :
- comodel_name : nom technique du modèle cible — seul paramètre obligatoire.
- ondelete : comportement lors de la suppression du record lié —
'cascade','set null', ou'restrict'. Valeur par défaut fréquente :'set null'. - domain : filtre qui limite les enregistrements sélectionnables dans la liste. Par exemple,
domain=[('customer_rank', '>', 0)]restreint la sélection aux clients uniquement. - context : valeurs de contexte transmises à l’ouverture du formulaire lié, utiles pour pré-remplir des champs lors de la création d’un nouvel enregistrement lié.
- required : rend le champ obligatoire — l’enregistrement ne peut être sauvegardé tant que le lien n’est pas renseigné.
- readonly : empêche la modification manuelle du lien, pratique quand il est géré automatiquement par la logique métier.
- delegate : si
True, les champs du modèle lié deviennent accessibles directement sur le modèle courant (utilisé pour l’héritage de modèle, pas pour les relations simples).
Affichage en vue
En vue formulaire, le Many2One s’affiche généralement comme une zone avec recherche intégrée et une flèche pour ouvrir le formulaire lié. Cela facilite la navigation directe vers l’enregistrement lié sans quitter le contexte en cours.
En vue liste, le champ montre le nom de l’enregistrement lié et peut servir aux regroupements : on peut par exemple grouper les commandes par client ou les tâches par projet pour obtenir des rapports synthétiques.
Dans les vues de recherche, le Many2One sert de filtre ou de critère de groupement — par exemple, filtrer le pipeline CRM par commercial revient à filtrer sur un champ Many2One.
Le One2Many inverse
Chaque Many2One possède naturellement une face réciproque : le One2Many. Si les commandes pointent vers un client via Many2One, la fiche client peut exposer une One2Many listant toutes les commandes liées. Bien que la colonne physique existe uniquement côté Many2One, définir les deux côtés facilite la navigation et l’expérience utilisateur.
Cas d’usage métiers
Le Many2One se retrouve dans quasiment tous les processus Odoo. Voici cinq exemples concrets qui montrent son rôle opérationnel.
CRM : rattacher opportunités aux commerciaux
Dans le module CRM, le champ user_id d’une opportunité est un Many2One vers res.users qui identifie le commercial responsable. Cela permet de segmenter le pipeline par personne, calculer les taux de conversion par vendeur et répartir les opportunités en masse sans développement supplémentaire.
Ventes : relier commandes, clients et listes de prix
Une commande contient typiquement des Many2One comme partner_id (client) et pricelist_id (liste de prix). Quand on choisit un client, des onchange peuvent remplir automatiquement la liste de prix, les conditions de paiement ou l’adresse de livraison selon les données du client — un gain d’efficacité visible pour les commerciaux.
Inventaire : produits et catégories
Chaque produit a une Many2One categ_id vers la catégorie produit. La catégorie pilote des paramètres comptables (comptes de charges et produits), la méthode d’évaluation et les stratégies de sortie en entrepôt. Une mauvaise catégorisation produit se répercute sur les états financiers, d’où l’importance d’un champ Many2One bien configuré.
Comptabilité : écritures et journaux
Les écritures comptables pointent vers un journal via journal_id. Le choix du journal influe sur la numérotation, le type d’opération et parfois les comptes par défaut. Sélectionner un mauvais journal sur une facture ou un paiement entraîne des écritures hors section appropriée du grand livre, d’où l’importance du contrôle via le Many2One.
Gestion de projet : tâches et projets
Dans Project, chaque tâche référence un projet via project_id. Ce lien détermine les étapes applicables, les droits d’accès et l’affectation des feuilles de temps. Pour les SSII ou sociétés de services, l’enchaînement timesheet → tâche → projet via Many2One est essentiel pour la facturation et la reconnaissance du chiffre d’affaires.
Créer ou personnaliser un champ Many2One
Trois manières principales d’ajouter un Many2One selon votre contexte technique.
Avec Odoo Studio (sans code)
Odoo Studio est l’outil low-code intégré. Pour ajouter un Many2One sans coder :
- Ouvrir Odoo Studio depuis le menu principal.
- Aller sur le formulaire à modifier.
- Glisser-déposer un champ Many2One depuis le sélecteur de champs sur le formulaire.
- Dans le panneau de propriétés, choisir le modèle cible dans la liste.
- Définir l’étiquette et éventuellement un domain pour restreindre les choix.
- Enregistrer et quitter Studio.
Studio crée le champ préfixé par x_studio_ et l’ajoute à la vue. Le champ fonctionne immédiatement : recherche, ouverture du formulaire lié, création d’un nouvel enregistrement lié. C’est la manière la plus rapide pour les utilisateurs fonctionnels d’étendre un formulaire.
Avec Python dans un module personnalisé
Pour les développeurs déployant du code versionné, on définit les Many2One directement en Python — méthode recommandée pour la production et la maintenance multi-environnements :
from odoo import fields, models
class ProjectTask(models.Model):
_inherit = 'project.task'
x_client_contact_id = fields.Many2one(
comodel_name='res.partner',
string='Client Contact',
domain=[('type', '=', 'contact')],
ondelete='set null',
help='The main contact at the client for this task.',
)
Après définition, on ajoute le champ dans les vues XML et on met à jour le module pour appliquer les changements en base. Cette méthode offre un contrôle total sur le domain, l’ondelete et l’intégration avec compute/constraints.
Il est également recommandé d’ajouter la One2Many réciproque afin d’offrir une navigation dans les deux sens :
class ResPartner(models.Model):
_inherit = 'res.partner'
x_task_ids = fields.One2many(
comodel_name='project.task',
inverse_name='x_client_contact_id',
string='Related Tasks',
)
Via l’API XML-RPC
Pour les scripts de déploiement ou les configurations distantes, on peut créer des champs Many2One via XML-RPC :
field_id = models.execute_kw(
ODOO_DB, uid, ODOO_API_KEY,
'ir.model.fields', 'create',
[{
'name': 'x_client_segment_id',
'field_description': 'Client Segment',
'model_id': model_id,
'ttype': 'many2one',
'relation': 'res.partner.category',
'on_delete': 'set null',
'state': 'manual',
}]
)
La clé relation indique le modèle cible et on_delete le comportement lors de suppression. En API comme en code, pensez toujours à créer la One2Many inverse pour que la navigation fonctionne dans les deux directions.
Bonnes pratiques
1. Toujours créer la One2Many réciproque
Quand vous ajoutez un Many2One, créez aussi la One2Many sur le modèle lié. Cela n’ajoute pas de colonne supplémentaire mais améliore fortement l’ergonomie : depuis la fiche client, l’utilisateur doit pouvoir voir rapidement toutes les commandes, tâches ou enregistrements qui le concernent.
2. Utiliser des domain pour restreindre la sélection
Un Many2One vers res.partner ouvre par défaut l’accès à tous les partenaires (clients, fournisseurs, adresses de livraison, contacts internes). Si le champ ne concerne que les clients, appliquez un domain=[('customer_rank', '>', 0)] pour filtrer et éviter des sélections incorrectes.
3. Choisir le bon ondelete
Le paramètre ondelete a un impact opérationnel majeur. 'cascade' peut entraîner des suppressions en chaîne non désirées. 'set null' est souvent le choix le plus sûr pour les données métier ; 'restrict' protège le record lié si sa suppression doit être empêchée tant que des dépendances existent.
4. Ne pas dupliquer des données qui devraient être relationnelles
Évitez de créer des champs texte pour stocker des valeurs déjà présentes dans un modèle existant (nom de société, catégorie, statut), car cela crée des doublons, complique le filtrage et casse la cohérence lors des changements de nom. Si l’information existe ailleurs, préférez un Many2One.
5. Utiliser le context pour préremplir les nouveaux enregistrements liés
Le context permet de transmettre des valeurs par défaut quand l’utilisateur crée un enregistrement depuis le sélecteur Many2One. Par exemple, en créant un contact depuis le formulaire d’un projet, on peut préremplir automatiquement le projet sur la fiche contact pour gagner du temps et garantir la cohérence.
Pièges fréquents
Oublier la One2Many réciproque
Ne pas définir la One2Many inverse est l’erreur la plus fréquente : le lien devient unidirectionnel et, depuis la fiche liée, il est impossible de voir tous les enregistrements qui pointent dessus. Les utilisateurs réclament alors des recherches ou rapports personnalisés pour compenser, ce qui aurait été évité en créant l’inverse.
Utiliser cascade deletion sans précaution
Placer ondelete='cascade' sur un champ qui relie des données opérationnelles à une donnée maître peut provoquer des pertes massives. Si une catégorie est supprimée et que tous les produits liés sont en cascade, vous perdez tous ces produits. Dans la plupart des cas, set null ou restrict est plus adapté.
Ne pas vérifier la valeur False en Python
Si un Many2One est vide, il renvoie un recordset vide qui est falsy. Accéder directement à order.partner_id.name peut donner une chaîne vide plutôt qu’une erreur, mais en enchaînant plusieurs accès (order.partner_id.country_id.name) vous risquez d’obtenir des résultats silencieusement vides. Vérifiez systématiquement l’existence des liens quand le champ n’est pas requis.
Pointer vers le mauvais modèle
res.partner sert à la fois de clients, fournisseurs, contacts et entreprises. Un Many2One vers ce modèle sans domain filtre donne accès à tous ces types. Si vous vouliez réserver le champ aux clients, définissez un domain approprié pour éviter la confusion dans la liste de sélection.
Surutiliser Many2One quand une Selection suffit
Si la liste de valeurs est restreinte et stable (3–4 statuts fixes), un champ Selection est souvent plus simple et performant qu’un Many2One. Les Many2One impliquent un modèle séparé et des jointures en base ; privilégiez Selection pour de petits jeux de valeurs immuables.
Conclusion
Le Many2One est au cœur de la manière dont Odoo relie les modules entre eux. Sa compréhension dépasse le cadre du développeur : consultants fonctionnels, analystes métier et power users qui personnalisent via Studio gagneront à savoir quand et comment l’utiliser.
Points clés à retenir : créez toujours la One2Many réciproque pour une navigation bidirectionnelle, appliquez des domain pour des listes pertinentes, choisissez consciemment l’ondelete et évitez un Many2One lorsque un Selection couvre le besoin.
Que vous configuriez via Studio, codiez dans un module Python ou gériez des champs par API, bien définir dès le départ vos relations rend l’implémentation plus fiable et plus simple à maintenir.
Chez Dasolo, nous accompagnons les entreprises pour implémenter, personnaliser et optimiser Odoo dans tous les départements. Qu’il s’agisse de concevoir un modèle de données clair, d’ajouter des champs relationnels ou de développer un module complet, notre équipe peut vous assister. Contactez-nous et parlons de votre projet Odoo.