Skip to content

OD214 many2many-table-clash

Two Many2many fields of a model to one comodel get the same table.

Category Severity Series Needs the index
Correctness Error 13.0 and later yes

What it reports

Two stored Many2many fields of one model to the same comodel that both leave out relation, and name the same columns or none. The later field is reported, when the module adds it: the second in the module, or the module's own when the other comes from a dependency. A module that Odoo always installs with base, such as web, counts as in depends. Modules of earlier series are not checked.

Why it matters

Without relation, Odoo names the table after the two models' tables, and the columns after them too, so both fields would store their links in one table and each would show the other's. Odoo refuses two such fields when it sets up the model, and the error stops the registry from loading: the module cannot be installed, and a database that has it does not start. Give one of them its own relation, and column1 and column2 with it.

A field that is related or computed without store=True has no table and is not checked, nor is a field where a definition unpacks **kwargs, a comodel or column that is not a string literal, or a field that ends as another type. A Many2many to its own model is not reported either: Odoo refuses it without a relation whatever the other fields are. An attribute takes its value from the last definitions that set it, and is unknown when those disagree, as two definitions in different files of one module may: files load in the order of the imports, which is not indexed.

The rule does not run for a module when the module or one of its dependencies depends on a module that is not indexed. A model is skipped when the class that introduces it, with _name or as a class Odoo names, is in a module outside the dependencies, or when a class of it, or of a model it inherits or delegates to, has a plain Python base class or an _inherit element that is not a literal, a parent whose fields the scan cannot see.

Example

models/project_task.py
class ProjectTask(models.Model):
    _inherit = "project.task"

    reviewer_ids = fields.Many2many("res.users")
    approver_ids = fields.Many2many("res.users")

Both fields get the table project_task_res_users_rel.

models/project_task.py
class ProjectTask(models.Model):
    _inherit = "project.task"

    reviewer_ids = fields.Many2many("res.users", "project_task_reviewer_rel", "task_id", "user_id")
    approver_ids = fields.Many2many("res.users", "project_task_approver_rel", "task_id", "user_id")

Suppressing

approver_ids = fields.Many2many("res.users")  # odooscan: ignore[OD214]

See suppressing a finding.