OD320 unknown-server-code-method¶
The code of a server action or a cron calls a method its model does not have.
| Category | Severity | Series | Needs the index |
|---|---|---|---|
| Correctness | Error | all | yes |
What it reports¶
A call model.x(), records.x() or record.x() in the code of an ir.actions.server or
ir.cron record, where neither the classes of its model_id, those of the models it inherits, nor
BaseModel define x, or where only a module missing from depends adds it. Private methods are
fine: this is Python, not a button. OD312 says what defines a method.
env holds a callable Environment on every series, so calling it is fine. pool holds a callable
Registry through saas~18.4, so model.pool('x') reports from 19.0, where it raises a TypeError,
and not in a module of unknown series. Write model.pool['x'].
ir.cron has code only from 11.0: below it Odoo drops the field, so the code never runs, and the
rule stays silent, as OD319 does. The record must run code: state is
code, a cron leaves it unset, or a server action created without it on 9.0 or 10.0, where it
defaults to code. A write to another module's action creates nothing, and a server action without
state in a module of unknown series counts as not running code. From 11.0 through saas~18.2 the
default is object_write. From saas~18.3 state is required with no default, so such a record
fails to load unless a default_state in a context supplies it. The rule reads no default_state
on any series. model_id reads as a ref, an eval="ref('...')", or a search on one model
term. When several records of the load write the same XML id, each field takes the last value the
module's scope gave it, and only the last code is checked. A demo write does not count for a data
value, as a database may hold no demo data.
From saas~18.2 Odoo creates a child action on the model of its parent, whatever the child gives
model_id, so the rule reads the code against the parent's model. A child linked to its parent
after its creation keeps its own model, and so does every child before saas~18.2. A later write of
model_id wins in either case. Give a child the model of its parent: a module update writes the
child's own value back, which the rule ignores. It stays silent when it cannot tell the parent's
model, and in an unknown series when the two models differ.
Why it matters¶
From 10.0 Odoo sets model, record and records from model_id. 9.0 sets self, model, obj
and object, and no record or records. 10.0 sets all five, and obj and object end there.
From 10.0 Odoo compiles the code without looking the method up, and 9.0 does not compile it on load,
so the module installs. The AttributeError comes when the action runs, or from 11.0 the cron,
wrapped in a ValueError by safe_eval: a button that fails for a user, or a cron that fails at
night and logs it.
The rule follows model, record and records on every series, and never obj or object. On
9.0 a record or records the code does not bind raises a NameError, wrapped the same way, when
the action runs, a stateless one included, whatever the method. The rule reports it only under the
conditions above. It stays silent on a name the code binds anywhere, by an assignment, a loop, a
parameter, a comprehension, |= or del, in any order. It also skips env[...], sudo() chains
and other receivers that are not one of the three names, and the body of a try, whose handler may
expect the failure. A <function> that writes the record goes unseen.
It stays silent when it cannot see every class, as OD314 does: a module with a
dependency that is not indexed, a module that builds model names at runtime, a model that is unknown
or outside depends, and a class of the model, or of a model it inherits, with a plain Python base
or an _inherit no indexed module defines, or in a file that does not parse, which
counts as one the scan cannot read. Such a class of a model reached through _inherits or
delegate=True does not count, since those methods stay on that model.
Example¶
<odoo>
<record id="action_confirm_orders" model="ir.actions.server">
<field name="name">Confirm orders</field>
<field name="model_id" ref="sale.model_sale_order"/>
<field name="state">code</field>
<field name="code">records.action_cofnirm()</field>
</record>
</odoo>
sale.order has no action_cofnirm, so the action fails each time it runs.
<odoo>
<record id="action_confirm_orders" model="ir.actions.server">
<field name="name">Confirm orders</field>
<field name="model_id" ref="sale.model_sale_order"/>
<field name="state">code</field>
<field name="code">records.action_confirm()</field>
</record>
</odoo>
The name now matches a method sale.order has on every series. records holds the selected orders;
model is an empty recordset, so model.action_confirm() would confirm nothing.
Both examples need 10.0 or later. On 9.0 records is unbound, so it fails before the method
matters: call obj.action_confirm(), which acts on the active order, and which the rule does not
read.
Suppressing¶
An XML comment inside the field ends the text Odoo reads, so inside the code use a Python comment,
# odooscan: ignore[OD320].