[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
they're used, as it's hardcoded into the different applications.
* ``mail.message_notification_email``
* ``mail.mail_notification_light``
* ``mail.mail_notification_paynow``
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
to your customers.
@ -54,9 +57,9 @@ to your customers.
Configuration
=============
# Go to Configuration > Technical > Emails > Templates
# Open the desired ``email.template`` record.
# In Advanced Parameters tab, find the Force Layout field.
#. Go to Settings > Technical > Emails > Templates
#. Open the desired ``email.template`` record.
#. 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 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_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
===========
@ -95,6 +108,10 @@ Contributors
* Iván Todorovich <ivan.todorovich@camptocamp.com>
* Abraham Anes <abrahamanes@gmail.com>
* `Quartile <https://www.quartile.co>`_
* Aung Ko Ko Lin
* Yoshi Tashiro
Maintainers
~~~~~~~~~~~

View File

@ -13,5 +13,10 @@
"category": "Marketing",
"depends": ["mail"],
"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_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(
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
# Open the desired ``email.template`` record.
# In Advanced Parameters tab, find the Force Layout field.
#. Go to Settings > Technical > Emails > Templates
#. Open the desired ``email.template`` record.
#. 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 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_paynow``
* ``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>
* 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
they're used, as it's hardcoded into the different applications.
* ``mail.message_notification_email``
* ``mail.mail_notification_light``
* ``mail.mail_notification_paynow``
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
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
theyre used, as its hardcoded into the different applications.</p>
<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_paynow</tt></li>
</ul>
<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
to your customers.</p>
<p><strong>Table of contents</strong></p>
@ -398,9 +401,11 @@ to your customers.</p>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#toc-entry-1">Configuration</a></h1>
<p># Go to Configuration &gt; Technical &gt; Emails &gt; Templates
# Open the desired <tt class="docutils literal">email.template</tt> record.
# In Advanced Parameters tab, find the Force Layout field.</p>
<ol class="arabic simple">
<li>Go to Settings &gt; Technical &gt; Emails &gt; Templates</li>
<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).
You can force a custom email layout of your own.
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_borders</tt></li>
</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 class="section" id="bug-tracker">
<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><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><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>
</div>
<div class="section" id="maintainers">

View File

@ -2,6 +2,7 @@
# @author Iván Todorovich <ivan.todorovich@camptocamp.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import Command
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>",
}
)
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(
{
"name": "Test Template",
@ -71,3 +86,32 @@ class TestMailLayoutForce(TransactionCase):
composer._action_send_mail()
message = self.partner.message_ids[-1]
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>