跳至主要内容

运行时未找到 Odoo QWeb 模板 — 修复指南 2026

2019 年 17 月 18 日,Odoo QWeb 模板未找到错误?今天通过全面深入的生产来诊断模板 ID、模块安装状态和继承。

E
ECOSIRE Research and Development Team
|2026年5月4日7 分钟阅读1.2k 字数|

本文目前仅提供英文版本。翻译即将推出。

You print a report or render a website page and Odoo throws:

You print a report or render a website page and Odoo throws:

ValueError: External ID not found in the system: my_module.report_priority_document

Or the inverse:

QWebException: Template my_module.report_priority_document not found.

The template exists in source. The module is "installed". And yet the lookup fails. This is a class of QWeb resolution bugs across Odoo 17.0/18.0/19.0 with five distinct root causes.

Quick Fix

Verify the template's XML id is registered in ir_model_data:

SELECT id, module, name, model, res_id
FROM   ir_model_data
WHERE  module = 'my_module' AND name = 'report_priority_document';

If empty, the template was never imported. Run:

sudo -u odoo /opt/odoo/odoo-bin -c odoo.conf -d <db> -u my_module --stop-after-init

Then check again. If still empty, the template's XML file is not in __manifest__.py's data:.

Why This Happens

QWeb templates are stored as ir.ui.view rows with type qweb, plus an ir.model.data row mapping the XML id to the view. Both must exist. The five common breaks:

  1. Template defined but XML file not in manifest data:. Module installs, template never imported.
  2. Wrong module prefix. Template defined in my_module but referenced as other_module.report_priority_document. Lookup fails.
  3. Module not installed in this database. Source has the template; the database does not.
  4. Inheritance broke the template. A custom module inherited the template with a bad xpath that disabled it on load.
  5. Caching. Odoo caches QWeb templates per cursor; after a -u the cache should refresh, but stale workers serve stale templates.

Step-by-Step Diagnosis

1. Check the source.

grep -rn "report_priority_document" /opt/odoo/custom/my_module/

Find the <template id="report_priority_document" line. Note the file path.

2. Check that file is in the manifest's data::

# __manifest__.py
'data': [
    'security/ir.model.access.csv',
    'reports/report_priority.xml',   # is this here?
    ...
],

If missing, add it.

3. Check ir_model_data:

SELECT * FROM ir_model_data
WHERE  module = 'my_module' AND name = 'report_priority_document';

Empty = template never imported. One row = template imported correctly.

4. Check ir_ui_view:

SELECT id, name, type, active, arch_db
FROM   ir_ui_view
WHERE  id = (
    SELECT res_id FROM ir_model_data
    WHERE  module = 'my_module' AND name = 'report_priority_document'
);

If active = false, the template was disabled (validation error). Check the log for the disabling reason.

5. Check module install state:

SELECT name, state FROM ir_module_module WHERE name = 'my_module';

state = 'installed' is required. Anything else means the module did not finish installing.

Permanent Fix

For missing manifest entries, add the file:

'data': [
    'security/ir.model.access.csv',
    'reports/report_priority.xml',   # add me
    'reports/report_action.xml',     # and any related action
],

Then -u my_module to import.

For wrong module prefix, fix the reference. Templates defined in my_module are referenced as my_module.<name>:

<!-- The action -->
<record id="action_report_priority" model="ir.actions.report">
    <field name="name">Priority Order</field>
    <field name="model">sale.order</field>
    <field name="report_type">qweb-pdf</field>
    <field name="report_name">my_module.report_priority_document</field>   <!-- prefix matters -->
    <field name="binding_model_id" ref="sale.model_sale_order"/>
</record>

<!-- The template -->
<template id="report_priority_document">
    <t t-call="web.html_container">
        <t t-foreach="docs" t-as="o">
            <t t-call="web.external_layout">
                <div class="page">
                    <h2>Priority Order: <span t-field="o.name"/></h2>
                </div>
            </t>
        </t>
    </t>
</template>

For module-not-installed, install:

sudo -u odoo /opt/odoo/odoo-bin -c odoo.conf -d <db> -i my_module --stop-after-init

For broken inheritance, find the inheritance and fix or disable:

SELECT id, name, inherit_id, active
FROM   ir_ui_view
WHERE  inherit_id = (
    SELECT res_id FROM ir_model_data
    WHERE  module = 'my_module' AND name = 'report_priority_document'
);

The inherits with active = false are your problem. Read their arch, fix the xpath, set active = true.

For cache issues, restart workers:

sudo systemctl restart odoo

QWeb cache is per-process. A restart clears it.

How to Prevent It

  • CI smoke test that loads every report. A 10-line CI job that opens each report URL after install verifies the template is found. Catches missing-manifest-entry bugs immediately.
  • Lint manifest data: against XML files. A script that walks every *.xml under the module, extracts every <template id="..."> and <record id="...">, and asserts the file is in data:. Free correctness.
  • Always use the module.id form. Never reference templates by bare id (report_priority_document); always include the module prefix (my_module.report_priority_document). Removes ambiguity.
  • Don't disable templates manually. If you need to remove an inheritance, archive the inherit view, do not unlink it. Recovery is easier.
  • Test reports as non-admin. Some templates render fine for admins but fail for portal users due to ACL on referenced fields. Run report generation as the smallest-privilege intended user during dev.

Frequently Asked Questions

What is the difference between <template> and <record> for QWeb views?

<template id="x"> is shorthand for <record id="x" model="ir.ui.view"><field name="type">qweb</field><field name="arch">...</field></record>. They produce the same database row. Use <template> for readability.

Can I reference a template from another module?

Yes, with the full id: another_module.template_id. Your module must depend on another_module so load order is correct.

Why does my template work in the dev database but not production?

Almost always either: the production module is at an older version (the new template was added later and never imported), or production has a custom inherit that disables it. Check ir_model_data and ir_ui_view.active on production.

How do I ship a one-off fix to a broken template?

The cleanest pattern is a new module that depends on the broken module and inherits the template with the fix. This survives upgrades of the broken module without your fix being overwritten. Direct edits to the broken module's source are lost on the next upgrade.

Why does my template work in Odoo shell but fail in the actual report?

Most often a context difference. The shell defaults to admin with a permissive context. Reports run with the user's context, including ACL and language. Test the lookup with with_user(user_id) and with_context(lang='fr_FR') in the shell to mimic real conditions.

Can I have two reports share the same QWeb template?

Yes. Two ir.actions.report records can both have report_name = 'my_module.report_priority_document'. The action's other fields (model, name, paperformat) determine how the report is invoked; the template is shared. Useful for shipping the same content with different paper sizes or languages.

My template was working then I updated only the action — what could break?

The action's report_name field determines which template is rendered. A typo there points at a non-existent template id. The action save succeeds (no XML id check) but the next render fails. Always run a smoke render after editing report actions.

Can a QWeb template inherit from a template in another module?

Yes. Set inherit_id to a ref="other_module.template_id":

<template id="report_priority_invoice" inherit_id="account.report_invoice_document">
    <xpath expr="//div[hasclass('page')]" position="inside">
        <p class="priority-banner" t-if="o.priority == '3'">PRIORITY ORDER</p>
    </xpath>
</template>

Your module must depend on account so load order is correct.

How do I enable a disabled QWeb template safely?

Read its arch first, fix the broken xpath or field reference, then set active=True:

view = env['ir.ui.view'].browse(view_id)
view.write({'arch': fixed_arch, 'active': True})

If you set active=True without fixing the arch, Odoo re-disables it on the next reload. The fix has to be persistent in source XML, then re-imported via -u <module>.


Need help with a tricky Odoo error? ECOSIRE's Odoo experts have shipped 215+ modules — get expert help.

分享:
E

作者

ECOSIRE Research and Development Team

技术写作

ECOSIRE 技术写作团队的内容涵盖 Odoo ERP、Shopify 电子商务、AI 智能体、Power BI 分析、GoHighLevel 自动化以及企业软件最佳实践。我们的指南帮助企业做出明智的技术决策。

ECOSIRE

使用 Odoo ERP 转变您的业务

专家 Odoo 实施、定制和支持可简化您的运营。