OD226 sql-view-not-created¶
A model sets _auto = False, and nothing creates its SQL view.
| Category | Severity | Series | Needs the index |
|---|---|---|---|
| Correctness | Error | all | yes |
What it reports¶
The _auto = False of a class that introduces a model, with _name or as a class Odoo names, when
nothing gives the model its table: no class of the model or of a model it inherits, in any indexed
module, defines init() or _auto_init(), sets _table_query or _table_sql, as an attribute or
a property but not as a field, creates in any method a view whose whole name is self._table, bare
or as the one argument of a call such as SQL.identifier(), or has a plain Python base class or an
_inherit element that is not a literal, and no indexed module creates a view of the model's table,
as a hook may.
A view counts as created when an SQL file the manifest loads creates it outside comments and
strings, or a CREATE VIEW query that SQL() or execute() is given spells its name, with a
public. schema or none, in a literal, a sum of literals, an f-string, or the left operand of
another +, of % or of .format(), whatever follows the name. A name the literal stops inside,
as x_report_ in f"CREATE VIEW x_report_{suffix} ..." is, is one Python fills in. So is a name
after a literal that ends at public., as in f"CREATE VIEW public.{table} ...". Odoo runs the
SQL files of data, those of init_xml and update_xml too before saas~15.2, and those of
init_xml again from 19.0 up to the last release before 20.0; for a module whose series is
unknown, every key counts.
When the module itself creates a view whose name Python fills in, other than with self._table
whole in a model class, outside the init() or _auto_init() of a class, none of its models is
reported, as that view may be any. Such a view in a dependency does not count: when a helper of a
dependency creates the module's views by name, from a post_init_hook or elsewhere, the module's
models are reported, so create each view in the model's init() or suppress the finding.
An abstract model, a class whose own body sets _register to False, None or 0, which Odoo
keeps out of the registry, a model whose table another model has or a Many2many of the module or its
dependencies keeps its rows in, a model whose _table is no literal, and a model with a class, of
its own or of a model it inherits, in a file that does not parse in the module or its
dependencies, which counts as one the scan cannot read, are not reported. A module that Odoo always
installs with base, such as web, counts as in depends. The rule skips a module when it or one
of its dependencies depends on a module that is not indexed, since such a module may create the
view.
A model with no field, in any class of it or of a model it inherits, is not reported: it has nothing
to read, so it serves through its methods alone, and those may never touch a relation. A class that
extends base gives every model its fields, and does not count as giving this one any.
Why it matters¶
_auto = False tells Odoo not to create a table for the model's fields: the model reads from an
SQL view that init() creates when the module is installed or updated, or from the query
_table_query gives, _table_sql from 20.0. Without any of them, the relation the model reads from
does not exist, so every search or read of the model fails, and so do the menus, reports and
relational fields that reach it.
Create the view in init(), or drop _auto = False to let Odoo create a table from the fields.
Example¶
class SaleLineDigest(models.Model):
_name = "x.sale.line.digest"
_description = "Sale line digest"
_auto = False
order_id = fields.Many2one("sale.order", readonly=True)
amount = fields.Float(readonly=True)
In a module that depends on sale, this is reported: no table and no view x_sale_line_digest
exists, so the first search of the model fails.
class SaleLineDigest(models.Model):
_name = "x.sale.line.digest"
_description = "Sale line digest"
_auto = False
order_id = fields.Many2one("sale.order", readonly=True)
amount = fields.Float(readonly=True)
def init(self):
tools.drop_view_if_exists(self.env.cr, self._table)
self.env.cr.execute("""
CREATE OR REPLACE VIEW x_sale_line_digest AS (
SELECT l.id, l.order_id, l.price_subtotal AS amount
FROM sale_order_line l
)
""")