Skip to content

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

models/sale_line_digest.py
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.

models/sale_line_digest.py
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
            )
        """)

Suppressing

    _auto = False  # odooscan: ignore[OD226]

See suppressing a finding.