+
What you get
+
+
1) Template model
+
data.autocomplete.template
+
Each template stores:
+
+- name: template name
+- model_id: the target Odoo model (ir.model) the template
+applies to
+- company_id: company scoping (global or per company)
+- user_id: optional owner (personal templates)
+- values_json: a JSON dictionary holding serialized default values
+
+
+
+
2) Mixin
+
data.template.mixin
+
Add it to any wizard/model to get:
+
+- template_id field (select a template)
+- actions (for buttons):
+- action_open_create_template_wizard()
+- action_update_current_template()
+- action_apply_template(overwrite=True)
+
+
+
+
+
+
3) Create Template Wizard
+
data.wizard.template.create
+
This popup asks for a name (and personal/global scope) and creates
+the template using the current wizard/model values.
+
+
+
JSON serialization
+
The mixin serializes current values into a JSON-safe dict:
+
+- many2one -> the record id (or false)
+- many2many -> a list of record ids
+- basic fields
+(char/int/float/bool/date/datetime/selection/text/monetary) -> stored
+as-is
+- one2many is ignored
+- computed fields without inverse and readonly fields are ignored (by
+default)
+
+
+
+
Applying templates safely with onchanges
+
Applying a template uses a phased write:
+
+- Drivers: fields returned by _template_driver_fields() are
+written first
+- Rest: everything else (excluding protected fields)
+- Protected: fields returned by _template_protected_fields()
+are written last
+
+
This solves common wizard issues where changing a driver field triggers
+an onchange that clears other fields (e.g. changing plan_id clears
+account_ids).
+
While applying, apply_template=True is present in the context so you
+can skip destructive onchanges in your own code.
+
Table of contents
+
+
+
+
+
1) Inherit the mixin in Python
+
+from odoo import models, api
+
+class MyWizard(models.TransientModel):
+ _inherit = ["my.wizard.model", "data.template.mixin"]
+
+ # Optional: define drivers/protected fields to survive onchanges
+ def _template_driver_fields(self):
+ return {"plan_id"}
+
+ def _template_protected_fields(self):
+ return {"account_ids", "show_months"}
+
+ @api.onchange("plan_id")
+ def _onchange_plan_id(self):
+ # Skip destructive behavior when applying templates
+ if self.env.context.get("apply_template"):
+ return
+ return super()._onchange_plan_id()
+
+
+
+Note: This module only provides the base.
+
+
+
+
3) Filtering templates further (optional)
+
If the same wizard model can represent different “types” of reports
+(e.g. different report_id), you can further restrict the templates
+shown by overriding:
+
+def _template_domain_extra(self):
+ return [("some_field", "=", self.some_field.id)]
+
+
+
+
+
Notes / Tips
+
+- If you want templates shared across companies, allow
+company_id = False templates (already supported by the domain).
+- If you want only personal templates, set user_id on every
+template and adjust the domain.
+- If some fields should never be stored (tokens, volatile flags, etc.),
+override _template_skip_fields().
+
+
+
+
Bug Tracker
+
Bugs are tracked on GitHub Issues.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us to smash it by providing a detailed and welcomed
+feedback.
+
Do not contact contributors directly about support or help with technical issues.
+
+
+
Credits
+
+
+
+
Maintainers
+
This module is maintained by the OCA.
+
+
+
+
OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
Current maintainer:
+

+
This module is part of the OCA/server-tools project on GitHub.
+
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
+