OD206 unknown-env-model¶
env[...] names a model no indexed module defines.
| Category | Severity | Series | Needs the index |
|---|---|---|---|
| Correctness | Warning | all | yes |
What it reports¶
A literal model name in a subscript of env, such as self.env["x.y"] or request.env["x.y"],
that no module on the addons path defines.
Why it matters¶
The environment looks the model up in the registry when the line runs and raises KeyError for a
model that is not there. Nothing fails when the module installs: the error reaches whoever first
triggers that code path, often a user clicking a button.
The rule stays silent where the code may know better. A name without a dot is skipped, since it is
as likely a key of some other mapping called env. A model that a module missing from depends
defines is not reported either, unlike OD202, because such a lookup is usually guarded
by a check the rule cannot follow. A model from a module that is not on the addons path, such as an
Enterprise module, is reported: add that module's directory to the addons path.
A model the module may build itself gets no finding. That is a name that fits an expression code
outside tests/ builds _name from, where an expression that shows no literal start fits every
name, as does a parent a class in a file that does not parse does not spell out, or the
name a class may take in a module of unknown series, as
Series describes.
Code that runs only once a check at runtime has shown a model, a field or a module there is skipped
too, since a soft dependency is meant to be absent at times. A check asks whether the environment
or the registry has the model, whether a model has the field, by name or through hasattr, or
whether ir.module.module shows the module installed. Such code is one of these:
- What the check opens: the body of
if "x.y" in self.env:or of a test onself.env.get("x.y"), theelseofif "x.y" not in self.env:, and the operands after such a test in anand. - The rest of a block that leaves when the model is absent: after
if "x.y" not in self.env: return, or withself.skipTest(...)in place ofreturn. So too afterself.ensure_installed("m"), a method ofAccountTestInvoicingCommonfrom 17.0 that skips the test unless the module is installed. The module name must be a literal, and the call covers every lookup behind it, whichever module it names. - What a
trythat looks up anenv.ref(...)or anenv[...], catches the failed lookup and skips the test from every handler guards: its body, itselseand the rest of the block after it.
For an env.ref(), OD302 narrows a check that names a module or a model
to the ids of the modules that come with what it names.
A value that holds the result of a check counts as the check: a name until it is assigned again,
and an attribute of self or cls across the class.
Some tests prove nothing, so what follows them is reported. A test of a variable says nothing of a
literal use: if name in self.env:. A private name is more often a method or an internal attribute
than a field: hasattr(self, "_x"). A record of ir.module.module exists whether or not the module
is installed, so only its state counts, and a search of modules counts when it asks for installed
ones. A body that runs when the test fails is not behind it: the body of if "x.y" not in self.env:,
or the rest of a block after if "x.y" in self.env: return.
A check is read where it is written, in the code around the lookup. One that a helper method or
another class makes does not count, so to keep a lookup of a soft dependency quiet, test for the
model, field or module in the function that needs it, or in the class that holds the result.
ensure_installed is the one helper that counts, by its name. Elsewhere, use self.skipTest.
Example¶
class SaleOrder(models.Model):
_inherit = "sale.order"
def _invoiced_lines(self):
return self.env["account.move.lines"].search([("sale_line_ids", "in", self.order_line.ids)])
The model is account.move.line, so this method raises KeyError on its first call.
class SaleOrder(models.Model):
_inherit = "sale.order"
def _invoiced_lines(self):
return self.env["account.move.line"].search([("sale_line_ids", "in", self.order_line.ids)])