Skip to content

OD302 xmlid-not-in-depends

The XML id comes from a module missing from depends.

Category Severity Series Needs the index
Correctness Error all yes

What it reports

A reference to an XML id defined by a module that is not among the referencing module's dependencies, direct or transitive. The references checked are the same as for OD301: ref attributes and the other references in data files, and env.ref() in Python. In a data file it also reports an id whose module is neither in depends nor on the addons path. A module that Odoo always installs with base, such as web, counts as in depends. From saas~14.3, where a data file names web.assets_backend, web.assets_common or web.assets_frontend, OD328 reports the element instead.

Why it matters

Odoo loads modules in the order their depends set. A module that names another's id without depending on it works in a database where that module happens to be installed and loaded first, and fails in one where it is not: the data file stops the install with "External ID not found in the system", and env.ref() raises ValueError when the line runs. A demo file differs: see load failures. Tests in a full database pass, and the failure shows up on a customer's lean one.

In Python, only ids of indexed modules are checked, because code often names an Enterprise or optional module's id behind a check that the module is installed. Groups given to has_group() or a field's groups are not reported here, for the same reason. Upgrade scripts under migrations/ and upgrades/ are skipped, since they name ids of past versions.

The rule stays silent on an id that a dependency that is not indexed, such as an Enterprise module, could bring in: an id of a module that is not indexed either, or one that an indexed module outside depends defines. It still reports an id that an indexed module outside depends does not define, unless that module loads a data file the scan cannot read. Add the checkout of the dependency that is not indexed to --addons-path to check the other ids.

An env.ref() in code that runs only once a check at runtime has passed can get no finding, since a soft dependency is meant to be absent at times. OD206 lists the checks and the code they cover, such as a try that catches the failed lookup and skips from every handler; one that carries on after the error guards nothing, so the rule reports what it holds.

A check covers an id only when the id's module comes with what the check names: the module of self.ensure_installed("m"), of _get("m"), of a search by name or of "m" in ..._installed(), and each module that introduces a model it tests for. A guarding try names the modules of the ids it looks up: inside its body every caught lookup counts, and after it only the lookups that run on every path through the body.

Those modules bring their depends. They also bring each module that auto-installs once its triggers are installed, when at least one trigger is among them and every other one is too or comes with the referencing module (in its depends, direct or transitive, or among the modules Odoo always installs with base). An auto-installed module whose triggers all come with the referencing module owes nothing to the check, and an id of it reports as it would without one. For example, self.ensure_installed("sale") covers an id of sales_team, and a check on purchase covers one of purchase_stock in a module whose depends bring in stock_account.

A check that names nothing the scan reads, such as hasattr or the result of a check held in a name or a self attribute, or that names a module or a model the scan does not index, covers every id behind it. OD301 still reports an id behind a check when its module is in depends and does not define it.

Example

models/sale_order.py
class SaleOrder(models.Model):
    _inherit = "sale.order"

    def _rush_picking_type(self):
        return self.env.ref("stock.picking_type_out")

The module depends on sale only, and sale does not depend on stock, so the id exists only when some other module has installed stock.

__manifest__.py
{
    "name": "Rush delivery",
    "version": "18.0.1.0.0",
    "depends": ["sale_stock"],
}

sale_stock depends on both sale and stock. When no such module fits, list the module that defines the id in depends yourself.

Suppressing

return self.env.ref("stock.picking_type_out")  # odooscan: ignore[OD302]

See suppressing a finding.