From 36cf86810ba7f41913a27fe4c0aac94d72d61688 Mon Sep 17 00:00:00 2001 From: Aungkokolin1997 Date: Tue, 4 Mar 2025 08:50:07 +0000 Subject: [PATCH] [IMP] mail_layout_force This commit improves the process of replacing the layout when sending an email without selecting an email template. --- mail_layout_force/README.rst | 25 +++++++-- mail_layout_force/__manifest__.py | 7 ++- mail_layout_force/models/__init__.py | 2 + .../models/email_layout_mapping.py | 20 +++++++ mail_layout_force/models/ir_ui_view.py | 10 ++++ mail_layout_force/models/mail_thread.py | 55 +++++++++++++++++++ mail_layout_force/readme/CONFIGURE.rst | 16 +++++- mail_layout_force/readme/CONTRIBUTORS.rst | 4 ++ mail_layout_force/readme/DESCRIPTION.rst | 5 +- .../security/ir.model.access.csv | 3 + .../static/description/index.html | 35 ++++++++++-- .../tests/test_mail_layout_force.py | 44 +++++++++++++++ mail_layout_force/views/ir_ui_views.xml | 24 ++++++++ 13 files changed, 237 insertions(+), 13 deletions(-) create mode 100644 mail_layout_force/models/email_layout_mapping.py create mode 100644 mail_layout_force/models/ir_ui_view.py create mode 100644 mail_layout_force/security/ir.model.access.csv create mode 100644 mail_layout_force/views/ir_ui_views.xml diff --git a/mail_layout_force/README.rst b/mail_layout_force/README.rst index ae6fb6b92..d7aead85f 100644 --- a/mail_layout_force/README.rst +++ b/mail_layout_force/README.rst @@ -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 don’t 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 * Abraham Anes +* `Quartile `_ + + * Aung Ko Ko Lin + * Yoshi Tashiro Maintainers ~~~~~~~~~~~ diff --git a/mail_layout_force/__manifest__.py b/mail_layout_force/__manifest__.py index 6152eaa26..d5b1438c3 100644 --- a/mail_layout_force/__manifest__.py +++ b/mail_layout_force/__manifest__.py @@ -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", + ], } diff --git a/mail_layout_force/models/__init__.py b/mail_layout_force/models/__init__.py index 89e090b24..e37fa21ee 100644 --- a/mail_layout_force/models/__init__.py +++ b/mail_layout_force/models/__init__.py @@ -1,2 +1,4 @@ +from . import email_layout_mapping +from . import ir_ui_view from . import mail_template from . import mail_thread diff --git a/mail_layout_force/models/email_layout_mapping.py b/mail_layout_force/models/email_layout_mapping.py new file mode 100644 index 000000000..b8e162e43 --- /dev/null +++ b/mail_layout_force/models/email_layout_mapping.py @@ -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." + ) diff --git a/mail_layout_force/models/ir_ui_view.py b/mail_layout_force/models/ir_ui_view.py new file mode 100644 index 000000000..ae9b7215a --- /dev/null +++ b/mail_layout_force/models/ir_ui_view.py @@ -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") diff --git a/mail_layout_force/models/mail_thread.py b/mail_layout_force/models/mail_thread.py index 25be8bdda..7428ddd57 100644 --- a/mail_layout_force/models/mail_thread.py +++ b/mail_layout_force/models/mail_thread.py @@ -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 + ) diff --git a/mail_layout_force/readme/CONFIGURE.rst b/mail_layout_force/readme/CONFIGURE.rst index 6e42c41e0..22a2dae54 100644 --- a/mail_layout_force/readme/CONFIGURE.rst +++ b/mail_layout_force/readme/CONFIGURE.rst @@ -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 don’t 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. diff --git a/mail_layout_force/readme/CONTRIBUTORS.rst b/mail_layout_force/readme/CONTRIBUTORS.rst index c0de70411..211db2b9a 100644 --- a/mail_layout_force/readme/CONTRIBUTORS.rst +++ b/mail_layout_force/readme/CONTRIBUTORS.rst @@ -2,3 +2,7 @@ * Iván Todorovich * Abraham Anes +* `Quartile `_ + + * Aung Ko Ko Lin + * Yoshi Tashiro diff --git a/mail_layout_force/readme/DESCRIPTION.rst b/mail_layout_force/readme/DESCRIPTION.rst index e00348fe7..798c3b486 100644 --- a/mail_layout_force/readme/DESCRIPTION.rst +++ b/mail_layout_force/readme/DESCRIPTION.rst @@ -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. diff --git a/mail_layout_force/security/ir.model.access.csv b/mail_layout_force/security/ir.model.access.csv new file mode 100644 index 000000000..0e2f7c1c9 --- /dev/null +++ b/mail_layout_force/security/ir.model.access.csv @@ -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 diff --git a/mail_layout_force/static/description/index.html b/mail_layout_force/static/description/index.html index c490ea2c7..ad70f48a6 100644 --- a/mail_layout_force/static/description/index.html +++ b/mail_layout_force/static/description/index.html @@ -376,11 +376,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.

Table of contents

@@ -398,9 +401,11 @@ 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.

+
    +
  1. Go to Settings > Technical > Emails > Templates
  2. +
  3. Open the desired email.template record.
  4. +
  5. In Advanced Parameters tab, find the Force Layout field.
  6. +

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 Mail: No-Layout notification template to prevent Odoo @@ -412,6 +417,22 @@ 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):

    +
      +
    1. Go to Settings > Technical > User Interface > Views.
    2. +
    3. Copy the current layout (e.g., mail.message_notification_email) to create a new one, and remove any parts you don’t need.
    4. +
    5. +
      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.
      • +
      +
      +
      +
    6. +

    Bug Tracker

    @@ -441,6 +462,12 @@ If you spotted it first, help us to smash it by providing a detailed and welcome
  • Abraham Anes <abrahamanes@gmail.com>

  • +
  • Quartile

    +
      +
    • Aung Ko Ko Lin
    • +
    • Yoshi Tashiro
    • +
    +
  • diff --git a/mail_layout_force/tests/test_mail_layout_force.py b/mail_layout_force/tests/test_mail_layout_force.py index 8ef77222c..9f41370b6 100644 --- a/mail_layout_force/tests/test_mail_layout_force.py +++ b/mail_layout_force/tests/test_mail_layout_force.py @@ -2,6 +2,7 @@ # @author Iván Todorovich # 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": "

    ", } ) + 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": """ + +
    +
    +

    Substituted

    + """, + } + ) 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(), "

    Test

    ") + + 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("

    Substituted

    ", 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("

    Substituted

    ", message.mail_ids.body_html) + self.assertIn("Test Message", message.mail_ids.body_html) diff --git a/mail_layout_force/views/ir_ui_views.xml b/mail_layout_force/views/ir_ui_views.xml new file mode 100644 index 000000000..077c78a07 --- /dev/null +++ b/mail_layout_force/views/ir_ui_views.xml @@ -0,0 +1,24 @@ + + + + ir.ui.view.form.inherit + ir.ui.view + + + + + + + + + + + + + + +