[IMP] mail_layout_force

This commit improves the process of replacing the layout when sending an email without selecting an email template.
pull/1620/head
Aungkokolin1997 2025-03-04 08:50:07 +00:00
parent f5c4912a5c
commit 36cf86810b
13 changed files with 237 additions and 13 deletions

View File

@ -37,11 +37,14 @@ the company logo, and a small footer saying "Powered by Odoo".
There are notably two main layouts used in Odoo, and the user can't control when There are notably two main layouts used in Odoo, and the user can't control when
they're used, as it's hardcoded into the different applications. they're used, as it's hardcoded into the different applications.
* ``mail.message_notification_email``
* ``mail.mail_notification_light`` * ``mail.mail_notification_light``
* ``mail.mail_notification_paynow`` * ``mail.mail_notification_paynow``
This module allows to force a specific layout for a given ``email.template``, This module allows to force a specific layout for a given ``email.template``,
effectively overwritting the one hardcoded by Odoo. effectively overwritting the one hardcoded by Odoo. Additionally, it enables
forcing a custom layout for emails that do not use an existing ``email.template``
record (e.g., when sending an email from the chatter).
This allows you to fully customize the way Odoo emails are rendered and sent This allows you to fully customize the way Odoo emails are rendered and sent
to your customers. to your customers.
@ -54,9 +57,9 @@ to your customers.
Configuration Configuration
============= =============
# Go to Configuration > Technical > Emails > Templates #. Go to Settings > Technical > Emails > Templates
# Open the desired ``email.template`` record. #. Open the desired ``email.template`` record.
# In Advanced Parameters tab, find the Force Layout field. #. In Advanced Parameters tab, find the Force Layout field.
You can leave it empty to use the default email layout (chosen by Odoo). You can leave it empty to use the default email layout (chosen by Odoo).
You can force a custom email layout of your own. You can force a custom email layout of your own.
@ -70,6 +73,16 @@ You can see how the existing layouts are defined for details or inspiration:
* ``mail.mail_notification_paynow`` * ``mail.mail_notification_paynow``
* ``mail.mail_notification_borders`` * ``mail.mail_notification_borders``
To force a new custom layout for emails that do not use an existing ``email.template``
record (e.g., emails sent from the chatter):
#. Go to Settings > Technical > User Interface > Views.
#. Copy the current layout (e.g., mail.message_notification_email) to create a new one, and remove any parts you dont need.
#. Open the layout that you want to swap with a substitute. Then, under the Layout Mapping tab:
* Set ``Substitute Layout`` to the new custom layout you created.
* Set ``Models`` if you want to apply the replacement only to specific models. If left empty,
the email layout will be replaced for all models.
Bug Tracker Bug Tracker
=========== ===========
@ -95,6 +108,10 @@ Contributors
* Iván Todorovich <ivan.todorovich@camptocamp.com> * Iván Todorovich <ivan.todorovich@camptocamp.com>
* Abraham Anes <abrahamanes@gmail.com> * Abraham Anes <abrahamanes@gmail.com>
* `Quartile <https://www.quartile.co>`_
* Aung Ko Ko Lin
* Yoshi Tashiro
Maintainers Maintainers
~~~~~~~~~~~ ~~~~~~~~~~~

View File

@ -13,5 +13,10 @@
"category": "Marketing", "category": "Marketing",
"depends": ["mail"], "depends": ["mail"],
"demo": ["demo/mail_layout.xml"], "demo": ["demo/mail_layout.xml"],
"data": ["data/mail_layout.xml", "views/mail_template.xml"], "data": [
"security/ir.model.access.csv",
"data/mail_layout.xml",
"views/ir_ui_views.xml",
"views/mail_template.xml",
],
} }

View File

@ -1,2 +1,4 @@
from . import email_layout_mapping
from . import ir_ui_view
from . import mail_template from . import mail_template
from . import mail_thread from . import mail_thread

View File

@ -0,0 +1,20 @@
# Copyright 2025 Quartile (https://www.quartile.co)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields, models
class EmailLayoutMapping(models.Model):
_name = "email.layout.mapping"
_description = "Email Layout Mapping"
layout_id = fields.Many2one("ir.ui.view", ondelete="cascade")
substitute_layout_id = fields.Many2one(
"ir.ui.view",
domain=[("type", "=", "qweb")],
required=True,
help="Select a target layout.",
)
model_ids = fields.Many2many(
"ir.model", string="Models", help="Select models that the swapping applies to."
)

View File

@ -0,0 +1,10 @@
# Copyright 2025 Quartile (https://www.quartile.co)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields, models
class IrUiView(models.Model):
_inherit = "ir.ui.view"
layout_mapping_line_ids = fields.One2many("email.layout.mapping", "layout_id")

View File

@ -18,3 +18,58 @@ class MailThread(models.AbstractModel):
return super().message_post_with_template( return super().message_post_with_template(
template_id, email_layout_xmlid=email_layout_xmlid, **kwargs template_id, email_layout_xmlid=email_layout_xmlid, **kwargs
) )
def _notify_thread_by_email(
self,
message,
recipients_data,
msg_vals=False,
mail_auto_delete=True,
model_description=False,
force_email_company=False,
force_email_lang=False,
resend_existing=False,
force_send=True,
send_after_commit=True,
subtitles=None,
**kwargs
):
msg_vals = msg_vals or {}
layout_xmlid = (
msg_vals.get("email_layout_xmlid")
or message.email_layout_xmlid
or "mail.mail_notification_layout"
)
layout = self.env.ref(layout_xmlid, raise_if_not_found=True)
res_model = (
self.env["ir.model"].sudo().search([("model", "=", self._name)], limit=1)
)
mapping = self.env["email.layout.mapping"].search(
[("layout_id", "=", layout.id), ("model_ids", "in", res_model.ids)],
limit=1,
)
if not mapping:
mapping = self.env["email.layout.mapping"].search(
[("layout_id", "=", layout.id), ("model_ids", "=", False)], limit=1
)
if mapping:
substitute_layout = mapping.substitute_layout_id
if not substitute_layout.xml_id:
substitute_layout._export_rows([["id"]])
# Refresh cache to get xml_id assigned by _export_rows
substitute_layout.invalidate_recordset()
msg_vals["email_layout_xmlid"] = mapping.substitute_layout_id.xml_id
return super()._notify_thread_by_email(
message,
recipients_data,
msg_vals=msg_vals,
mail_auto_delete=mail_auto_delete,
model_description=model_description,
force_email_company=force_email_company,
force_email_lang=force_email_lang,
resend_existing=resend_existing,
force_send=force_send,
send_after_commit=send_after_commit,
subtitles=subtitles,
**kwargs
)

View File

@ -1,6 +1,6 @@
# Go to Configuration > Technical > Emails > Templates #. Go to Settings > Technical > Emails > Templates
# Open the desired ``email.template`` record. #. Open the desired ``email.template`` record.
# In Advanced Parameters tab, find the Force Layout field. #. In Advanced Parameters tab, find the Force Layout field.
You can leave it empty to use the default email layout (chosen by Odoo). You can leave it empty to use the default email layout (chosen by Odoo).
You can force a custom email layout of your own. You can force a custom email layout of your own.
@ -13,3 +13,13 @@ You can see how the existing layouts are defined for details or inspiration:
* ``mail.mail_notification_light`` * ``mail.mail_notification_light``
* ``mail.mail_notification_paynow`` * ``mail.mail_notification_paynow``
* ``mail.mail_notification_borders`` * ``mail.mail_notification_borders``
To force a new custom layout for emails that do not use an existing ``email.template``
record (e.g., emails sent from the chatter):
#. Go to Settings > Technical > User Interface > Views.
#. Copy the current layout (e.g., mail.message_notification_email) to create a new one, and remove any parts you dont need.
#. Open the layout that you want to swap with a substitute. Then, under the Layout Mapping tab:
* Set ``Substitute Layout`` to the new custom layout you created.
* Set ``Models`` if you want to apply the replacement only to specific models. If left empty,
the email layout will be replaced for all models.

View File

@ -2,3 +2,7 @@
* Iván Todorovich <ivan.todorovich@camptocamp.com> * Iván Todorovich <ivan.todorovich@camptocamp.com>
* Abraham Anes <abrahamanes@gmail.com> * Abraham Anes <abrahamanes@gmail.com>
* `Quartile <https://www.quartile.co>`_
* Aung Ko Ko Lin
* Yoshi Tashiro

View File

@ -7,11 +7,14 @@ the company logo, and a small footer saying "Powered by Odoo".
There are notably two main layouts used in Odoo, and the user can't control when There are notably two main layouts used in Odoo, and the user can't control when
they're used, as it's hardcoded into the different applications. they're used, as it's hardcoded into the different applications.
* ``mail.message_notification_email``
* ``mail.mail_notification_light`` * ``mail.mail_notification_light``
* ``mail.mail_notification_paynow`` * ``mail.mail_notification_paynow``
This module allows to force a specific layout for a given ``email.template``, This module allows to force a specific layout for a given ``email.template``,
effectively overwritting the one hardcoded by Odoo. effectively overwritting the one hardcoded by Odoo. Additionally, it enables
forcing a custom layout for emails that do not use an existing ``email.template``
record (e.g., when sending an email from the chatter).
This allows you to fully customize the way Odoo emails are rendered and sent This allows you to fully customize the way Odoo emails are rendered and sent
to your customers. to your customers.

View File

@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_email_layout_mapping_all,email.layout.mapping.all,model_email_layout_mapping,,1,0,0,0
access_email_layout_mapping_admin,email.layout.mapping.admin,model_email_layout_mapping,base.group_system,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_email_layout_mapping_all email.layout.mapping.all model_email_layout_mapping 1 0 0 0
3 access_email_layout_mapping_admin email.layout.mapping.admin model_email_layout_mapping base.group_system 1 1 1 1

View File

@ -376,11 +376,14 @@ the company logo, and a small footer saying “Powered by Odoo”.</p>
<p>There are notably two main layouts used in Odoo, and the user cant control when <p>There are notably two main layouts used in Odoo, and the user cant control when
theyre used, as its hardcoded into the different applications.</p> theyre used, as its hardcoded into the different applications.</p>
<ul class="simple"> <ul class="simple">
<li><tt class="docutils literal">mail.message_notification_email</tt></li>
<li><tt class="docutils literal">mail.mail_notification_light</tt></li> <li><tt class="docutils literal">mail.mail_notification_light</tt></li>
<li><tt class="docutils literal">mail.mail_notification_paynow</tt></li> <li><tt class="docutils literal">mail.mail_notification_paynow</tt></li>
</ul> </ul>
<p>This module allows to force a specific layout for a given <tt class="docutils literal">email.template</tt>, <p>This module allows to force a specific layout for a given <tt class="docutils literal">email.template</tt>,
effectively overwritting the one hardcoded by Odoo.</p> effectively overwritting the one hardcoded by Odoo. Additionally, it enables
forcing a custom layout for emails that do not use an existing <tt class="docutils literal">email.template</tt>
record (e.g., when sending an email from the chatter).</p>
<p>This allows you to fully customize the way Odoo emails are rendered and sent <p>This allows you to fully customize the way Odoo emails are rendered and sent
to your customers.</p> to your customers.</p>
<p><strong>Table of contents</strong></p> <p><strong>Table of contents</strong></p>
@ -398,9 +401,11 @@ to your customers.</p>
</div> </div>
<div class="section" id="configuration"> <div class="section" id="configuration">
<h1><a class="toc-backref" href="#toc-entry-1">Configuration</a></h1> <h1><a class="toc-backref" href="#toc-entry-1">Configuration</a></h1>
<p># Go to Configuration &gt; Technical &gt; Emails &gt; Templates <ol class="arabic simple">
# Open the desired <tt class="docutils literal">email.template</tt> record. <li>Go to Settings &gt; Technical &gt; Emails &gt; Templates</li>
# In Advanced Parameters tab, find the Force Layout field.</p> <li>Open the desired <tt class="docutils literal">email.template</tt> record.</li>
<li>In Advanced Parameters tab, find the Force Layout field.</li>
</ol>
<p>You can leave it empty to use the default email layout (chosen by Odoo). <p>You can leave it empty to use the default email layout (chosen by Odoo).
You can force a custom email layout of your own. You can force a custom email layout of your own.
You can use the <em>Mail: No-Layout notification template</em> to prevent Odoo You can use the <em>Mail: No-Layout notification template</em> to prevent Odoo
@ -412,6 +417,22 @@ You can see how the existing layouts are defined for details or inspiration:</p>
<li><tt class="docutils literal">mail.mail_notification_paynow</tt></li> <li><tt class="docutils literal">mail.mail_notification_paynow</tt></li>
<li><tt class="docutils literal">mail.mail_notification_borders</tt></li> <li><tt class="docutils literal">mail.mail_notification_borders</tt></li>
</ul> </ul>
<p>To force a new custom layout for emails that do not use an existing <tt class="docutils literal">email.template</tt>
record (e.g., emails sent from the chatter):</p>
<ol class="arabic simple">
<li>Go to Settings &gt; Technical &gt; User Interface &gt; Views.</li>
<li>Copy the current layout (e.g., mail.message_notification_email) to create a new one, and remove any parts you dont need.</li>
<li><dl class="first docutils">
<dt>Open the layout that you want to swap with a substitute. Then, under the Layout Mapping tab:</dt>
<dd><ul class="first last">
<li>Set <tt class="docutils literal">Substitute Layout</tt> to the new custom layout you created.</li>
<li>Set <tt class="docutils literal">Models</tt> if you want to apply the replacement only to specific models. If left empty,
the email layout will be replaced for all models.</li>
</ul>
</dd>
</dl>
</li>
</ol>
</div> </div>
<div class="section" id="bug-tracker"> <div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-2">Bug Tracker</a></h1> <h1><a class="toc-backref" href="#toc-entry-2">Bug Tracker</a></h1>
@ -441,6 +462,12 @@ If you spotted it first, help us to smash it by providing a detailed and welcome
</li> </li>
<li><p class="first">Abraham Anes &lt;<a class="reference external" href="mailto:abrahamanes&#64;gmail.com">abrahamanes&#64;gmail.com</a>&gt;</p> <li><p class="first">Abraham Anes &lt;<a class="reference external" href="mailto:abrahamanes&#64;gmail.com">abrahamanes&#64;gmail.com</a>&gt;</p>
</li> </li>
<li><p class="first"><a class="reference external" href="https://www.quartile.co">Quartile</a></p>
<ul class="simple">
<li>Aung Ko Ko Lin</li>
<li>Yoshi Tashiro</li>
</ul>
</li>
</ul> </ul>
</div> </div>
<div class="section" id="maintainers"> <div class="section" id="maintainers">

View File

@ -2,6 +2,7 @@
# @author Iván Todorovich <ivan.todorovich@camptocamp.com> # @author Iván Todorovich <ivan.todorovich@camptocamp.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import Command
from odoo.tests import TransactionCase from odoo.tests import TransactionCase
@ -18,6 +19,20 @@ class TestMailLayoutForce(TransactionCase):
"arch": "<t t-name='test'><h1></h1><t t-out='message.body'/></t>", "arch": "<t t-name='test'><h1></h1><t t-out='message.body'/></t>",
} }
) )
cls.mail_notification_layout = cls.env.ref("mail.mail_notification_layout")
cls.layout_substitute = cls.env["ir.ui.view"].create(
{
"name": "Substitute Layout",
"type": "qweb",
"mode": "primary",
"arch": """<?xml version="1.0"?>
<t t-name="custom_mail_notification_layout">
<div t-out="message.body"/>
<div t-if="signature" t-out="signature" style="font-size: 13px;"/>
<h1>Substituted</h1>
</t>""",
}
)
cls.template = cls.env["mail.template"].create( cls.template = cls.env["mail.template"].create(
{ {
"name": "Test Template", "name": "Test Template",
@ -71,3 +86,32 @@ class TestMailLayoutForce(TransactionCase):
composer._action_send_mail() composer._action_send_mail()
message = self.partner.message_ids[-1] message = self.partner.message_ids[-1]
self.assertEqual(message.mail_ids.body_html.strip(), "<h1></h1><p>Test</p>") self.assertEqual(message.mail_ids.body_html.strip(), "<h1></h1><p>Test</p>")
def test_chatter_message_uses_default_layout(self):
self.partner.message_post(
body="Test Message",
email_layout_xmlid=self.mail_notification_layout.xml_id,
message_type="comment",
subtype_xmlid="mail.mt_comment",
mail_auto_delete=False,
force_send=True,
)
message = self.partner.message_ids[-1]
self.assertNotIn("<h1>Substituted</h1>", message.mail_ids.body_html)
self.assertIn("Test Message", message.mail_ids.body_html)
def test_chatter_message_uses_substituted_layout(self):
self.mail_notification_layout.layout_mapping_line_ids = [
Command.create({"substitute_layout_id": self.layout_substitute.id})
]
self.partner.message_post(
body="Test Message",
email_layout_xmlid=self.mail_notification_layout.xml_id,
message_type="comment",
subtype_xmlid="mail.mt_comment",
mail_auto_delete=False,
force_send=True,
)
message = self.partner.message_ids[-1]
self.assertIn("<h1>Substituted</h1>", message.mail_ids.body_html)
self.assertIn("Test Message", message.mail_ids.body_html)

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_ir_ui_view_form_inherit" model="ir.ui.view">
<field name="name">ir.ui.view.form.inherit</field>
<field name="model">ir.ui.view</field>
<field name="inherit_id" ref="base.view_view_form" />
<field name="arch" type="xml">
<xpath expr="//page[@name='inherit_children']" position="after">
<page
name="layout_mapping"
string="Layout Mapping"
attrs="{'invisible': [('type', '!=', 'qweb')]}"
>
<field name="layout_mapping_line_ids" nolabel="1">
<tree editable="bottom">
<field name="substitute_layout_id" />
<field name="model_ids" widget="many2many_tags" />
</tree>
</field>
</page>
</xpath>
</field>
</record>
</odoo>