diff --git a/.oca/oca-port/blacklist/mail_tracking.json b/.oca/oca-port/blacklist/mail_tracking.json
new file mode 100644
index 0000000000..1ab17caca8
--- /dev/null
+++ b/.oca/oca-port/blacklist/mail_tracking.json
@@ -0,0 +1,6 @@
+{
+ "pull_requests": {
+ "792": "(auto) Nothing to port from PR #792",
+ "1244": "(auto) Nothing to port from PR #1244"
+ }
+}
diff --git a/mail_tracking/__manifest__.py b/mail_tracking/__manifest__.py
index 2dbaf72c84..bb7db76411 100644
--- a/mail_tracking/__manifest__.py
+++ b/mail_tracking/__manifest__.py
@@ -23,6 +23,7 @@
"views/mail_tracking_event_view.xml",
"views/mail_message_view.xml",
"views/res_partner_view.xml",
+ "views/res_config_settings.xml",
],
"assets": {
"web.assets_backend": [
diff --git a/mail_tracking/models/__init__.py b/mail_tracking/models/__init__.py
index bf42270a08..a4cd6cbf92 100644
--- a/mail_tracking/models/__init__.py
+++ b/mail_tracking/models/__init__.py
@@ -1,5 +1,7 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+from . import res_company
+from . import res_config_settings
from . import ir_mail_server
from . import mail_bounced_mixin
from . import mail_guest
diff --git a/mail_tracking/models/mail_message.py b/mail_tracking/models/mail_message.py
index 00d8722eee..996d269568 100644
--- a/mail_tracking/models/mail_message.py
+++ b/mail_tracking/models/mail_message.py
@@ -226,6 +226,12 @@ def tracking_status(self):
@api.model
def _drop_aliases(self, mail_list):
aliases = self.env["mail.alias"].get_aliases()
+ if self.env.company.mail_tracking_show_aliases:
+ IrConfigParamObj = self.env["ir.config_parameter"].sudo()
+ aliases = "{}@{}".format(
+ IrConfigParamObj.get_param("mail.catchall.alias"),
+ IrConfigParamObj.get_param("mail.catchall.domain"),
+ )
def _filter_alias(email):
email_wn = getaddresses([email])[0][1]
diff --git a/mail_tracking/models/mail_thread.py b/mail_tracking/models/mail_thread.py
index c17b0eeca2..6198c0a60e 100644
--- a/mail_tracking/models/mail_thread.py
+++ b/mail_tracking/models/mail_thread.py
@@ -94,9 +94,10 @@ def _add_extra_recipients_suggestions(self, suggestions, field_mail, reason):
)
else:
partner = ResPartnerObj.browse(partner_id)
- self._message_add_suggested_recipient(
- suggestions, partner=partner, reason=reason
- )
+ if partner.email not in aliases:
+ self._message_add_suggested_recipient(
+ suggestions, partner=partner, reason=reason
+ )
@api.model
def get_view(self, view_id=None, view_type="form", **options):
diff --git a/mail_tracking/models/mail_tracking_email.py b/mail_tracking/models/mail_tracking_email.py
index a42e48fe7a..cda2f335e3 100644
--- a/mail_tracking/models/mail_tracking_email.py
+++ b/mail_tracking/models/mail_tracking_email.py
@@ -118,8 +118,7 @@ class MailTrackingEmail(models.Model):
@api.depends("mail_message_id")
def _compute_message_id(self):
"""This helper field will allow us to map the message_id from either the linked
- mail.message or a mass.mailing mail.trace.
- """
+ mail.message or a mass.mailing mail.trace."""
self.message_id = False
for tracking in self.filtered("mail_message_id"):
tracking.message_id = tracking.mail_message_id.message_id
@@ -462,3 +461,40 @@ def event_process(self, request, post, metadata, event_type=None):
# - return 'NONE' if this request is not for you
# - return 'ERROR' if any error
return "NONE" # pragma: no cover
+
+ def _get_old_mail_tracking_email_domain(self, max_age_days):
+ target_write_date = fields.Datetime.subtract(
+ fields.Datetime.now(), days=max_age_days
+ )
+ return [("write_date", "<", target_write_date)]
+
+ @api.autovacuum
+ def _gc_mail_tracking_email(self, limit=5000):
+ config_max_age_days = (
+ self.env["ir.config_parameter"]
+ .sudo()
+ .get_param("mail_tracking.mail_tracking_email_max_age_days")
+ )
+ try:
+ max_age_days = int(config_max_age_days)
+ except ValueError:
+ max_age_days = 0
+
+ if not max_age_days > 0:
+ return False
+
+ domain = self._get_old_mail_tracking_email_domain(max_age_days)
+ records_to_delete = self.search(domain, limit=limit).exists()
+ if records_to_delete:
+ _logger.info(
+ "Deleting %s mail.tracking.email records", len(records_to_delete)
+ )
+ records_to_delete.flush_recordset()
+ # Using a direct query to avoid ORM as it causes an issue with
+ # a related field mass_mailing_id in customer DB when deleting
+ # the records. This might be 14.0 specific, so changing to
+ # .unlink() should be tested when forward porting.
+ query = "DELETE FROM mail_tracking_email WHERE id IN %s"
+ args = (tuple(records_to_delete.ids),)
+ self.env.cr.execute(query, args)
+ self.invalidate_model()
diff --git a/mail_tracking/models/res_company.py b/mail_tracking/models/res_company.py
new file mode 100644
index 0000000000..afdcce95bc
--- /dev/null
+++ b/mail_tracking/models/res_company.py
@@ -0,0 +1,10 @@
+from odoo import fields, models
+
+
+class ResCompany(models.Model):
+ _inherit = "res.company"
+
+ mail_tracking_show_aliases = fields.Boolean(
+ string="Show Aliases in Mail Tracking",
+ default=False,
+ )
diff --git a/mail_tracking/models/res_config_settings.py b/mail_tracking/models/res_config_settings.py
new file mode 100644
index 0000000000..3136ae4ab1
--- /dev/null
+++ b/mail_tracking/models/res_config_settings.py
@@ -0,0 +1,16 @@
+from odoo import fields, models
+
+
+class ResConfigSettings(models.TransientModel):
+ _inherit = "res.config.settings"
+
+ mail_tracking_show_aliases = fields.Boolean(
+ related="company_id.mail_tracking_show_aliases",
+ readonly=False,
+ )
+ mail_tracking_email_max_age_days = fields.Integer(
+ "Max age in days of mail tracking email records",
+ config_parameter="mail_tracking.mail_tracking_email_max_age_days",
+ help="If set as positive integer enables the deletion of "
+ "old mail tracking records to reduce the database size.",
+ )
diff --git a/mail_tracking/static/description/index.html b/mail_tracking/static/description/index.html
index e1602b01d1..12253cdf81 100644
--- a/mail_tracking/static/description/index.html
+++ b/mail_tracking/static/description/index.html
@@ -8,10 +8,11 @@
/*
:Author: David Goodger (goodger@python.org)
-:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
+:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
+Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
@@ -274,7 +275,7 @@
margin-left: 2em ;
margin-right: 2em }
-pre.code .ln { color: grey; } /* line numbers */
+pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
@@ -300,7 +301,7 @@
span.pre {
white-space: pre }
-span.problematic {
+span.problematic, pre.problematic {
color: red }
span.section-subtitle {
@@ -495,7 +496,9 @@
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.
diff --git a/mail_tracking/tests/__init__.py b/mail_tracking/tests/__init__.py
index d40d444b68..76c005840b 100644
--- a/mail_tracking/tests/__init__.py
+++ b/mail_tracking/tests/__init__.py
@@ -2,3 +2,4 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import test_mail_tracking
+from . import test_gc_mail_tracking_email
diff --git a/mail_tracking/tests/test_gc_mail_tracking_email.py b/mail_tracking/tests/test_gc_mail_tracking_email.py
new file mode 100644
index 0000000000..74f32cefed
--- /dev/null
+++ b/mail_tracking/tests/test_gc_mail_tracking_email.py
@@ -0,0 +1,88 @@
+# Copyright 2024 Camptocamp SA
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
+
+from odoo import fields
+
+from odoo.addons.base.tests.common import SavepointCaseWithUserDemo
+
+
+class TestMailTrackingEmailCleanUp(SavepointCaseWithUserDemo):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
+ cls.settings = cls.env["res.config.settings"].create(
+ {"mail_tracking_email_max_age_days": 365}
+ )
+ cls.settings.set_values()
+ cls.partner = cls.env.ref("base.res_partner_address_28")
+ cls.message = cls.env["mail.message"].create(
+ {
+ "model": "res.partner",
+ "res_id": cls.partner.id,
+ "body": "TEST",
+ "message_type": "email",
+ "subtype_id": cls.env.ref("mail.mt_comment").id,
+ "author_id": cls.partner.id,
+ "date": "2024-03-26",
+ }
+ )
+ cls.recent_mail_tracking_email = cls.env["mail.tracking.email"].create(
+ {"mail_message_id": cls.message.id}
+ )
+ # Can't set the write_date directly as it gets overwritten by the ORM
+ cls.old_mail_tracking_email = cls.env["mail.tracking.email"].create(
+ {"mail_message_id": cls.message.id}
+ )
+ cls.total_count = 2
+ cls.recent_count = 1
+ cls.domain = [
+ ("mail_message_id", "=", cls.message.id),
+ ]
+
+ def _set_write_date(self):
+ # Set the write_date of the old record to be older than the max_age_days
+ # Update DB directly to avoid ORM overwriting the write_date
+ old_write_date = fields.Datetime.subtract(fields.Datetime.now(), days=400)
+ self.env.cr.execute(
+ "UPDATE mail_tracking_email SET write_date = %s WHERE id = %s",
+ (old_write_date, self.old_mail_tracking_email.id),
+ )
+
+ def test_deletion_of_mail_tracking_email(self):
+ self._set_write_date()
+ self.assertEqual(
+ len(self.env["mail.tracking.email"].search(self.domain)), self.total_count
+ )
+ self.env["mail.tracking.email"]._gc_mail_tracking_email()
+ self.assertEqual(
+ len(self.env["mail.tracking.email"].search(self.domain)), self.recent_count
+ )
+ self.assertTrue(self.recent_mail_tracking_email.exists())
+
+ def test_deletion_follows_configuration_variable(self):
+ self._set_write_date()
+ self.assertEqual(
+ len(self.env["mail.tracking.email"].search(self.domain)), self.total_count
+ )
+ # when disabled, no deletions should happen
+ self.settings.mail_tracking_email_max_age_days = 0
+ self.settings.set_values()
+ self.env["mail.tracking.email"]._gc_mail_tracking_email()
+ self.assertEqual(
+ len(self.env["mail.tracking.email"].search(self.domain)), self.total_count
+ )
+ # when disabled, no deletions should happen
+ self.settings.mail_tracking_email_max_age_days = -1
+ self.settings.set_values()
+ self.env["mail.tracking.email"]._gc_mail_tracking_email()
+ self.assertEqual(
+ len(self.env["mail.tracking.email"].search(self.domain)), self.total_count
+ )
+ # when enabled, deletions should happen
+ self.settings.mail_tracking_email_max_age_days = 365
+ self.settings.set_values()
+ self.env["mail.tracking.email"]._gc_mail_tracking_email()
+ self.assertEqual(
+ len(self.env["mail.tracking.email"].search(self.domain)), self.recent_count
+ )
diff --git a/mail_tracking/tests/test_mail_tracking.py b/mail_tracking/tests/test_mail_tracking.py
index 99d525f777..e07a873f8b 100644
--- a/mail_tracking/tests/test_mail_tracking.py
+++ b/mail_tracking/tests/test_mail_tracking.py
@@ -158,6 +158,36 @@ def test_message_post_partner_no_email(self):
self.assertEqual(tracking_email.error_type, "no_recipient")
self.assertFalse(self.recipient.email_bounced)
+ def test_message_post_show_aliases(self):
+ # Create message with show aliases setup
+ self.env.company.mail_tracking_show_aliases = True
+ # Setup catchall domain
+ IrConfigParamObj = self.env["ir.config_parameter"].sudo()
+ IrConfigParamObj.set_param("mail.catchall.domain", "test.com")
+ # pylint: disable=C8107
+ message = self.env["mail.message"].create(
+ {
+ "subject": "Message test",
+ "author_id": self.sender.id,
+ "email_from": self.sender.email,
+ "message_type": "comment",
+ "model": "res.partner",
+ "res_id": self.recipient.id,
+ "partner_ids": [(4, self.recipient.id)],
+ "email_cc": "Dominique Pinon
, customer-invoices@test.com", # noqa E501
+ "body": "This is another test message
",
+ }
+ )
+ message_dict, *_ = message.message_format()
+ self.assertTrue(
+ any(
+ [
+ tracking["recipient"] == "customer-invoices@test.com"
+ for tracking in message_dict["partner_trackings"]
+ ]
+ )
+ )
+
def _check_partner_trackings_cc(self, message):
message_dict = message.message_format()[0]
self.assertEqual(len(message_dict["partner_trackings"]), 3)
diff --git a/mail_tracking/views/res_config_settings.xml b/mail_tracking/views/res_config_settings.xml
new file mode 100644
index 0000000000..cad361fd90
--- /dev/null
+++ b/mail_tracking/views/res_config_settings.xml
@@ -0,0 +1,35 @@
+
+
+
+ res.config.settings.view.form.inherit.mail.tracking
+ res.config.settings
+
+
+
+
+
+
+
+
Show Aliases in Mail Tracking
+
+
+
+
+
+
+
If set as positive integer enables the deletion of old mail tracking records to reduce the database size.
+
+
+
+
+
+