Skip to content

Commit

Permalink
[IMP] mail_composer_cc_bcc: refactor _generate_template_recipients
Browse files Browse the repository at this point in the history
[IMP] mail_composer_cc_bcc: refactor _generate_template_recipients

1. Refactor _generate_template_recipients

2. Fix invalid-email-address for Bcc
   - In standard, Bcc key in dict message was stripped in
https://github.com/odoo/odoo/blob/4394940a09900aa5b42cbc05355880598608d1b6/odoo/addons/base/models/ir_mail_server.py#L676
   - Duplicate the key to retain for later assertion
  • Loading branch information
trisdoan committed Dec 16, 2024
1 parent 396f023 commit a8e2dbd
Show file tree
Hide file tree
Showing 8 changed files with 65 additions and 224 deletions.
1 change: 1 addition & 0 deletions mail_composer_cc_bcc/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@
"views/mail_message_views.xml",
"views/mail_template_views.xml",
"wizards/mail_compose_message_view.xml",
"wizards/mail_template_preview_view.xml",
],
}
7 changes: 7 additions & 0 deletions mail_composer_cc_bcc/models/ir_mail_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ def _prepare_email_message(self, message, smtp_session):
"""
Define smtp_to based on context instead of To+Cc+Bcc
"""
x_odoo_bcc_value = next(
(value for key, value in message._headers if key == "X-Odoo-Bcc"), None
)
# Add Bcc field inside message to pass validation
if x_odoo_bcc_value:
message["Bcc"] = x_odoo_bcc_value

smtp_from, smtp_to_list, message = super()._prepare_email_message(
message, smtp_session
)
Expand Down
190 changes: 1 addition & 189 deletions mail_composer_cc_bcc/models/mail_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).


import itertools

from odoo import fields, models, tools
from odoo import fields, models


class MailTemplate(models.Model):
Expand All @@ -13,189 +11,3 @@ class MailTemplate(models.Model):
email_bcc = fields.Char(
"Bcc", help="Blind cc recipients (placeholders may be used here)"
)

# ------------------------------------------------------------
# MESSAGE/EMAIL VALUES GENERATION
# ------------------------------------------------------------

def _generate_template_recipients( # noqa:C901
self, res_ids, render_fields, find_or_create_partners=False, render_results=None
):
self.ensure_one()
if render_results is None:
render_results = {}
ModelSudo = self.env[self.model].with_prefetch(res_ids).sudo()

# if using default recipients -> ``_message_get_default_recipients`` gives
# values for email_to, email_cc and partner_ids
if self.use_default_to and self.model:
default_recipients = ModelSudo.browse(
res_ids
)._message_get_default_recipients()
for res_id, recipients in default_recipients.items():
render_results.setdefault(res_id, {}).update(recipients)
# render fields dynamically which generates recipients
else:
for field in set(render_fields) & {
"email_cc",
"email_to",
"partner_to",
"email_bcc",
}:
generated_field_values = self._render_field(field, res_ids)
for res_id in res_ids:
render_results.setdefault(res_id, {})[
field
] = generated_field_values[res_id]

# create partners from emails if asked to
if find_or_create_partners:
res_id_to_company = {}
if self.model and "company_id" in ModelSudo._fields:
for read_record in ModelSudo.browse(res_ids).read(["company_id"]):
company_id = (
read_record["company_id"][0]
if read_record["company_id"]
else False
)
res_id_to_company[read_record["id"]] = company_id

all_emails = []
email_to_res_ids = {}
email_to_company = {}
for res_id in res_ids:
record_values = render_results.setdefault(res_id, {})
# DIFFERENT FROM ODOO NATIVE:
if record_values.get("email_cc"):
continue
mails = tools.email_split(record_values.pop("email_to", ""))
all_emails += mails
record_company = res_id_to_company.get(res_id)
for mail in mails:
email_to_res_ids.setdefault(mail, []).append(res_id)
if record_company:
email_to_company[mail] = record_company

if all_emails:
customers_information = ModelSudo.browse(
res_ids
)._get_customer_information()
partners = self.env["res.partner"]._find_or_create_from_emails(
all_emails,
additional_values={
email: {
"company_id": email_to_company.get(email),
**customers_information.get(email, {}),
}
for email in itertools.chain(all_emails, [False])
},
)
for original_email, partner in zip(all_emails, partners): # noqa: B905
if not partner:
continue
for res_id in email_to_res_ids[original_email]:
render_results[res_id].setdefault("partner_ids", []).append(
partner.id
)

# update 'partner_to' rendered value to 'partner_ids'
all_partner_to = {
pid
for record_values in render_results.values()
for pid in self._parse_partner_to(record_values.get("partner_to", ""))
}
existing_pids = set()
if all_partner_to:
existing_pids = set(
self.env["res.partner"].sudo().browse(list(all_partner_to)).exists().ids
)
for res_id, record_values in render_results.items(): # noqa: B007
partner_to = record_values.pop("partner_to", "")
if partner_to:
tpl_partner_ids = (
set(self._parse_partner_to(partner_to)) & existing_pids
)
record_values.setdefault("partner_ids", []).extend(tpl_partner_ids)

# DIFFERENT FROM ODOO NATIVE:
# update 'email_cc' rendered value to 'partner_bcc_ids'
all_cc_emails = []
if record_values.get("email_cc", ""):
mails = tools.email_split(record_values.pop("email_cc", ""))
all_cc_emails += mails
record_company = res_id_to_company.get(res_id)
for mail in mails:
email_to_res_ids.setdefault(mail, []).append(res_id)
if record_company:
email_to_company[mail] = record_company
# DIFFERENT FROM ODOO NATIVE:
if all_cc_emails:
customers_information = ModelSudo.browse(
res_ids
)._get_customer_information()
partners = self.env["res.partner"]._find_or_create_from_emails(
all_cc_emails,
additional_values={
email: {
"company_id": email_to_company.get(email),
**customers_information.get(email, {}),
}
for email in itertools.chain(all_cc_emails, [False])
},
)
for original_email, partner in zip(all_cc_emails, partners): # noqa: B905
if not partner:
continue
for res_id in email_to_res_ids[original_email]:
render_results[res_id].setdefault("partner_cc_ids", []).append(
partner.id
)
# DIFFERENT FROM ODOO NATIVE:
# update 'email_bcc' rendered value to 'partner_bcc_ids'
all_bcc_emails = []
if record_values.get("email_bcc", ""):
mails = tools.email_split(record_values.pop("email_bcc", ""))
all_bcc_emails += mails
record_company = res_id_to_company.get(res_id)
for mail in mails:
email_to_res_ids.setdefault(mail, []).append(res_id)
if record_company:
email_to_company[mail] = record_company
# DIFFERENT FROM ODOO NATIVE:
if all_bcc_emails:
customers_information = ModelSudo.browse(
res_ids
)._get_customer_information()
partners = self.env["res.partner"]._find_or_create_from_emails(
all_bcc_emails,
additional_values={
email: {
"company_id": email_to_company.get(email),
**customers_information.get(email, {}),
}
for email in itertools.chain(all_bcc_emails, [False])
},
)
for original_email, partner in zip(all_bcc_emails, partners): # noqa: B905
if not partner:
continue
for res_id in email_to_res_ids[original_email]:
render_results[res_id].setdefault("partner_bcc_ids", []).append(
partner.id
)
return render_results

def _generate_template(self, res_ids, render_fields, find_or_create_partners=False):
res = super()._generate_template(
res_ids, render_fields, find_or_create_partners=find_or_create_partners
)

for _, (template, template_res_ids) in self._classify_per_lang(res_ids).items():
if "email_bcc" in render_fields:
template._generate_template_recipients(
template_res_ids,
set("email_bcc"),
render_results=res,
find_or_create_partners=find_or_create_partners,
)
return res
12 changes: 0 additions & 12 deletions mail_composer_cc_bcc/tests/test_mail_cc_bcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@
from odoo.tests import Form, tagged
from odoo.tests.common import TransactionCase

from odoo.addons.mail.models.mail_template import MailTemplate as MailTemplate_upstream
from odoo.addons.mail.tests.common import MailCase
from odoo.addons.mail.tests.test_mail_composer import TestMailComposerForm
from odoo.addons.mail.wizard.mail_compose_message import (
MailComposer as MailComposer_upstream,
)

VALID_HASHES = {
"mail.template:_generate_template_recipients": ["73b0e20a018984841e454a57a86ee08d"],
"mail.composer:_compute_partner_ids": ["813ef112e3948fe625b9a89428f2518d"],
}

Expand Down Expand Up @@ -45,16 +43,6 @@ def open_mail_composer_form(self):
form.body = "<p>Hello</p>"
return form

def test_MailTemplate_upstream_file_hash(self):
"""Test that copied upstream function hasn't received fixes"""
func = inspect.getsource(
MailTemplate_upstream._generate_template_recipients
).encode()
func_hash = hashlib.md5(func).hexdigest()
self.assertIn(
func_hash, VALID_HASHES.get("mail.template:_generate_template_recipients")
)

def test_MailComposer_upstream_file_hash(self):
"""Test that copied upstream function hasn't received fixes"""
_compute_partner_ids = inspect.getsource(
Expand Down
1 change: 1 addition & 0 deletions mail_composer_cc_bcc/wizards/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from . import mail_compose_message
from . import mail_template_preview
48 changes: 25 additions & 23 deletions mail_composer_cc_bcc/wizards/mail_compose_message.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Copyright 2023 Camptocamp
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import itertools

from odoo import Command, api, fields, models
from odoo import Command, api, fields, models, tools


class MailComposeMessage(models.TransientModel):
Expand Down Expand Up @@ -44,7 +45,6 @@ def default_get(self, fields_list):
res["partner_bcc_ids"] = [Command.set(partner_bcc.ids)]
return res

# TODO: reduce repeated code
@api.depends(
"composition_mode", "model", "parent_id", "res_domain", "res_ids", "template_id"
)
Expand All @@ -55,27 +55,12 @@ def _compute_partner_cc_bcc_ids(self):
and composer.composition_mode == "comment"
and not composer.composition_batch
):
res_ids = composer._evaluate_res_ids() or [0]
rendered_values = composer._generate_template_for_composer(
res_ids,
{"email_cc", "email_bcc"},
find_or_create_partners=True,
)[res_ids[0]]

if rendered_values.get("partner_cc_ids"):
partner_cc_ids = rendered_values.get("partner_cc_ids")
if not isinstance(partner_cc_ids, list):
partner_cc_ids = [partner_cc_ids]
for partner_cc_id in partner_cc_ids:
composer.partner_cc_ids = [(4, partner_cc_id)]

if rendered_values.get("partner_bcc_ids"):
partner_bcc_ids = rendered_values.get("partner_bcc_ids")
if not isinstance(partner_bcc_ids, list):
partner_bcc_ids = [partner_bcc_ids]
for partner_bcc_id in partner_bcc_ids:
composer.partner_bcc_ids = [(4, partner_bcc_id)]

composer._set_partner_ids_from_mails(
composer.template_id.email_cc, "partner_cc_ids"
)
composer._set_partner_ids_from_mails(
composer.template_id.email_bcc, "partner_bcc_ids"
)
elif composer.parent_id and composer.composition_mode == "comment":
composer.partner_cc_ids = composer.parent_id.partner_cc_ids
composer.partner_bcc_ids = composer.parent_id.partner_bcc_ids
Expand Down Expand Up @@ -112,6 +97,23 @@ def _compute_partner_ids(self):
elif not composer.template_id:
composer.partner_ids = False

def _set_partner_ids_from_mails(self, email_field, partner_field):
if email_field:
mails = tools.email_split(email_field)
partner_ids = self.env["res.partner"]._find_or_create_from_emails(
mails,
additional_values={
email: {
"company_id": self.record_company_id.id,
}
for email in itertools.chain(mails, [False])
},
)
if not isinstance(partner_ids, list):
partner_ids = [partner_ids]

Check warning on line 113 in mail_composer_cc_bcc/wizards/mail_compose_message.py

View check run for this annotation

Codecov / codecov/patch

mail_composer_cc_bcc/wizards/mail_compose_message.py#L113

Added line #L113 was not covered by tests
for partner_id in partner_ids:
setattr(self, partner_field, [(4, partner_id.id)])

# ------------------------------------------------------------
# RENDERING / VALUES GENERATION
# ------------------------------------------------------------
Expand Down
18 changes: 18 additions & 0 deletions mail_composer_cc_bcc/wizards/mail_template_preview.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright 2024 Camptocamp
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import fields, models

from odoo.addons.mail.wizard.mail_template_preview import MailTemplatePreview


class MailTemplatePreview(models.TransientModel):
_inherit = "mail.template.preview"

_MAIL_TEMPLATE_FIELDS = MailTemplatePreview._MAIL_TEMPLATE_FIELDS + ["email_bcc"]

email_bcc = fields.Char(
"Bcc",
compute="_compute_mail_template_fields",
help="Blind Carbon copy recipients",
)
12 changes: 12 additions & 0 deletions mail_composer_cc_bcc/wizards/mail_template_preview_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record model="ir.ui.view" id="mail_template_preview_view_form_inherited">
<field name="model">mail.template.preview</field>
<field name="inherit_id" ref="mail.mail_template_preview_view_form" />
<field name="arch" type="xml">
<xpath expr="//field[@name='email_cc']" position="after">
<field name="email_bcc" invisible="not email_bcc" />
</xpath>
</field>
</record>
</odoo>

0 comments on commit a8e2dbd

Please sign in to comment.