Skip to content

OD311 unknown-qweb-template

A QWeb view or a report names a template no module in scope defines.

Category Severity Series Needs the index
Correctness Error all yes

What it reports

A literal t-call="x", or an <attribute name="t-call"> that sets one, in a <template> or a QWeb ir.ui.view of a data file the manifest loads, when no module in the module's scope defines x. A view defines x by its full XML id or its key. A template that only a module missing from depends defines is reported too, naming that module. A module that Odoo always installs with base, such as web, counts as in depends. A module that Odoo auto-installs with the calling module counts too, as a render after the install finds it: OD210 sets out which ones do. A render inside the install, such as from a hook, comes before that module loads, and a module that a database skipped earlier stays out, so add it to depends when either applies. From saas~17.1 a module with a countries list stays reported, as whether it installs rests on the companies of the database: add it to depends.

Only a file Odoo loads defines a target: one the manifest lists under a key the module's series reads, as OD502 tabulates, or one the module loads itself. A file whose name appears in the module's Python code counts, whatever key lists it, as that code loads it (18.0 addons/hr/models/hr_employee.py:696). So does a chart template under data/template/, on every series though Odoo reads one from saas~16.2. A file listed under a key Odoo loads nothing from, such as other_files, counts unless a legacy key the series ignores lists it too, as it was meant to load from there. 18.0 reads data and not update_xml (odoo/modules/loading.py:39-48), so a file under update_xml and other_files counts as unlisted there, while 9.0 (openerp/modules/loading.py:60-64) and 10.0 (odoo/modules/loading.py:57-61) load it. A template found only in any other file, tests/ and migrations/ included, counts as undefined: list that file under data.

It also reports the report_name of an ir.actions.report, written by a <record> from 11.0 on or by a <report> tag from 11.0 to 16.0, when its report_type renders QWeb: unset, which means qweb-pdf, or any qweb-* text.

The rule matches the whole name and never adds the module of the view, so an undotted target can only find a key, and that match stays silent on every series, while one that finds no key is reported. From saas~18.4 Odoo finds such a key too (20.0 odoo/addons/base/models/ir_ui_view.py:1208). Through saas~18.3 it refuses any undotted name with Invalid template id (18.0 odoo/addons/base/models/ir_ui_view.py:2107-2108), so a match on a key hides a render that fails. From 10.0 a website lookup is exempt.

A <template> without id takes its XML id from t-name, the module added when undotted, so <template t-name="sheet"> in module m answers to m.sheet and not to sheet. Beside an id, t-name names nothing.

The root of a <template> with a non-empty inherit_id calls nothing, primary="True" or not: Odoo renames that root to <data> and applies only its children, so its t-call never runs and is not read. What its specs add is read. An empty inherit_id extends nothing, so that root keeps its t-call.

A t-call on a spec calls nothing either: <t t-call="old.name" position="replace"/> locates that call in the parent view, and OD310 reports it when the parent has none. A spec needs no position, and a position="move" child of a spec locates too. What a spec holds is read, and so is its <attribute name="t-call">. Any other t-call is read, whatever position it carries: from 19.0 on that is a parameter of the call, and earlier QWeb ignores it.

Why it matters

Odoo looks the target up when the t-call runs, and a target it cannot find fails the render. The install does not check the name, so the error reaches the user who opens the report or the page, and a t-call under a t-if only fails when that branch runs. When the template comes from a module outside depends, the page renders where that module is installed and breaks where it is not. A report is the same: it installs, and fails when someone prints it, even with a Python report model, which gives the values and leaves the template required.

The rule leaves alone what only a render can tell: an expression such as {{ x }} or #{x}, a numeric view id, and a path to an .xml file. A kanban arch runs its t-call in the web client, against the templates of its own arch, so a view whose inheritance tree starts at a non-QWeb view is not checked. For a module whose dependencies include one that is not indexed, such as an Enterprise module, the rule stays silent on a target whose prefix names no indexed module, and on one that an indexed module outside depends defines, since that dependency may load the module. It still reports such a target when every module defining it depends on the calling module through indexed modules, which no dependency can load, and a target no indexed module defines when its prefix names an indexed one. Add the checkout of the dependency that is not indexed to --addons-path to check these targets. The rule also stays silent on a target no module defines when an unread data file may define it. Targets in Owl templates under static/src are OD605's.

On a report, the rule reads the last report_name and report_type of a record, and the last value each has across the writes of the module and its dependencies, and only that write of report_name reports. A demo write does not count for a data value, as a database may hold no demo data. A name the field takes from an eval, ref, search, file or type, or a report_type that is any other value, stays silent, and so does a blank, numeric or path name. report_file is never read: no render path uses it. A <report> tag stays silent in a module of unknown series.

An override of ir.actions.report can handle a report by name and return before the template renders, so the report prints with no template while the rule reports its report_name. In 20.0 stock_delivery prints chatter attachments that way (addons/stock_delivery/models/ir_actions_report.py:59-87) and ships empty <template> shells (addons/stock_delivery/report/shipping_reports.xml:5-6). Add such a shell, or suppress the finding.

Example

views/portal_templates.xml
<template id="portal_invoice">
    <t t-call="web.basic_layot">
        <div class="page">Invoice</div>
    </t>
</template>

The template is web.basic_layout, so the page fails as soon as it renders.

views/portal_templates.xml
<template id="portal_invoice">
    <t t-call="web.basic_layout">
        <div class="page">Invoice</div>
    </t>
</template>

A report fails the same way, when it prints. Its template is sale_portal.report_invoice:

report/invoice_report.xml
<record id="action_report_invoice" model="ir.actions.report">
    <field name="name">Invoice</field>
    <field name="model">res.partner</field>
    <field name="report_name">sale_portal.report_invoicing</field>
</record>
report/invoice_report.xml
<record id="action_report_invoice" model="ir.actions.report">
    <field name="name">Invoice</field>
    <field name="model">res.partner</field>
    <field name="report_name">sale_portal.report_invoice</field>
</record>

Suppressing

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

See suppressing a finding.