Cet article est actuellement disponible en anglais uniquement. Traduction à venir.
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:
- Template defined but XML file not in manifest
data:. Module installs, template never imported. - Wrong module prefix. Template defined in
my_modulebut referenced asother_module.report_priority_document. Lookup fails. - Module not installed in this database. Source has the template; the database does not.
- Inheritance broke the template. A custom module inherited the template with a bad xpath that disabled it on load.
- Caching. Odoo caches QWeb templates per cursor; after a
-uthe 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
*.xmlunder the module, extracts every<template id="...">and<record id="...">, and asserts the file is indata:. Free correctness. - Always use the
module.idform. 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
unlinkit. 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.
Related Errors
- PDF report renders blank page — what happens when the template is found but renders empty.
- wkhtmltopdf protocol error — adjacent toolchain bug.
- Data not found during init — sibling XML-id lookup failure.
- Module not loadable with traceback — what happens when Python fails before the XML is loaded.
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.
Rédigé par
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
ECOSIRE
Transformez votre entreprise avec Odoo ERP
Implémentation, personnalisation et assistance expertes d'Odoo pour rationaliser vos opérations.
Articles connexes
Comment ajouter un bouton personnalisé à une vue de formulaire Odoo (2026)
Ajoutez des boutons d'action personnalisés aux vues de formulaire Odoo 19 : méthode d'action Python, héritage des vues, visibilité conditionnelle, boîtes de dialogue de confirmation. Testé en production.
Comment ajouter un champ personnalisé dans Odoo sans Studio (2026)
Ajoutez des champs personnalisés via le module personnalisé dans Odoo 19 : héritage de modèle, extension de vue, champs calculés, décisions magasin/non-magasin. Code d'abord, contrôle de version.
Comment ajouter un rapport personnalisé dans Odoo à l'aide d'une mise en page externe
Créez un rapport PDF de marque dans Odoo 19 à l'aide de web.external_layout : modèle QWeb, format papier, liaison d'action. Avec logo imprimé + remplacements de pied de page.