Skip to content

OD313 private-button-method

A button calls a private method, which Odoo refuses.

Category Severity Series Needs the index
Correctness Error all yes

What it reports

A <button type="object" name="x"> in a view arch, with a literal name, where x starts with _, whether or not the model has it, where BaseModel makes x private in the module's series, or where any class in scope of the model at the button's position, or of a model it inherits, decorates x with @api.private.

Series Private methods of BaseModel
10.0 on init
16.0, 17.0, saas~17.4 on concat, ensure_one, filtered, filtered_domain, flush_model, flush_recordset, invalidate_model, invalidate_recordset, mapped, new, sorted, union, with_company, with_context, with_env, with_prefetch, with_user
17.0, saas~17.4 on grouped
18.0 on sudo
saas~18.2 on browse, check_access, exists, fetch, modified, search_fetch, update
saas~18.3 on lock_for_update
19.0 on try_lock_for_update

A module overriding one of these names is still reported, with or without the decorator. A module of unknown series is reported only for init.

Why it matters

Odoo calls a button through RPC, which refuses a private method. From saas~13.1 it checks a name starting with _ when it loads the view, so the module fails to install; before that, the button fails when a user clicks it. A demo file differs: see load failures. @api.private is in 16.0, 17.0 and from saas~17.4 on, not in saas~16.1 to 16.4 or saas~17.1 to 17.3, and is refused the same way, on any override of the method. 16.0 and 17.0 gained it by a February 2025 backport, so an older build accepts a button on ensure_one; the series cannot tell them apart, and the rule reports it. From saas~18.3 the install check reads only the top override, and the click still refuses any. init is refused from 10.0 on, by name until @api.private takes over, and 9.0 refuses only the leading _.

The rule skips the same views and positions as OD312, but not the modules it skips for lack of classes: a leading _ or a private method of BaseModel tells the method is private without looking it up.

Example

views/sale_order_views.xml
<button type="object" name="_action_confirm" string="Confirm"/>

_action_confirm is private. Add a public method for the button to call.

views/sale_order_views.xml
<button type="object" name="action_confirm" string="Confirm"/>

Suppressing

<!-- odooscan: ignore[OD313] -->

See suppressing a finding.