From ac625ad136cc74e12001867fee59204ec93da37a Mon Sep 17 00:00:00 2001 From: Cyril VINH-TUNG Date: Tue, 26 Jul 2022 11:35:46 -1000 Subject: [PATCH 1/3] [IMP] Add option to keep aliases in mail tracking --- mail_tracking/__manifest__.py | 1 + mail_tracking/models/__init__.py | 2 ++ mail_tracking/models/mail_message.py | 6 ++++ mail_tracking/models/mail_thread.py | 7 +++-- mail_tracking/models/res_company.py | 10 +++++++ mail_tracking/models/res_config_settings.py | 10 +++++++ mail_tracking/tests/test_mail_tracking.py | 31 +++++++++++++++++++++ mail_tracking/views/res_config_settings.xml | 25 +++++++++++++++++ 8 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 mail_tracking/models/res_company.py create mode 100644 mail_tracking/models/res_config_settings.py create mode 100644 mail_tracking/views/res_config_settings.xml 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/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..5a78273b82 --- /dev/null +++ b/mail_tracking/models/res_config_settings.py @@ -0,0 +1,10 @@ +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, + ) diff --git a/mail_tracking/tests/test_mail_tracking.py b/mail_tracking/tests/test_mail_tracking.py index 99d525f777..cb669b4e82 100644 --- a/mail_tracking/tests/test_mail_tracking.py +++ b/mail_tracking/tests/test_mail_tracking.py @@ -158,6 +158,37 @@ 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", + "body": "

This is another test message

", + } + ) + message._moderate_accept() + 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..d7edcafbac --- /dev/null +++ b/mail_tracking/views/res_config_settings.xml @@ -0,0 +1,25 @@ + + + + res.config.settings.view.form.inherit.mail.tracking + res.config.settings + + +
+
+
+ +
+
+
+
+
+
+
+
+
+
From 01d268512c8d6d41c69f5427bc5c85fa47ca96cd Mon Sep 17 00:00:00 2001 From: Henry Backman Date: Wed, 14 Aug 2024 12:53:52 +0200 Subject: [PATCH 2/3] mail_tracking: add garbage collection Add autovacuum to mail_tracking_email that removes old records based on new configuration variable mail_tracking_email_max_age_days. Due to possibly a large number of records to be deleted on first run, set a default limit of 5000 per run. --- mail_tracking/models/mail_tracking_email.py | 37 ++++++++ mail_tracking/models/res_config_settings.py | 6 ++ mail_tracking/tests/__init__.py | 1 + .../tests/test_gc_mail_tracking_email.py | 87 +++++++++++++++++++ mail_tracking/views/res_config_settings.xml | 22 +++++ 5 files changed, 153 insertions(+) create mode 100644 mail_tracking/tests/test_gc_mail_tracking_email.py diff --git a/mail_tracking/models/mail_tracking_email.py b/mail_tracking/models/mail_tracking_email.py index a42e48fe7a..e42c4e2638 100644 --- a/mail_tracking/models/mail_tracking_email.py +++ b/mail_tracking/models/mail_tracking_email.py @@ -462,3 +462,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) + ) + self.flush() + # 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_cache() diff --git a/mail_tracking/models/res_config_settings.py b/mail_tracking/models/res_config_settings.py index 5a78273b82..3136ae4ab1 100644 --- a/mail_tracking/models/res_config_settings.py +++ b/mail_tracking/models/res_config_settings.py @@ -8,3 +8,9 @@ class ResConfigSettings(models.TransientModel): 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/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..5754669491 --- /dev/null +++ b/mail_tracking/tests/test_gc_mail_tracking_email.py @@ -0,0 +1,87 @@ +# Copyright 2024 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import fields +from odoo.tests.common import SavepointCase + + +class TestMailTrackingEmailCleanUp(SavepointCase): + @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/views/res_config_settings.xml b/mail_tracking/views/res_config_settings.xml index d7edcafbac..be5f2fa599 100644 --- a/mail_tracking/views/res_config_settings.xml +++ b/mail_tracking/views/res_config_settings.xml @@ -19,6 +19,28 @@ +
+
+ +
+
+
+
+
+
From 82e66467ea3fab1e74eb78a1c99b2c4bdd368766 Mon Sep 17 00:00:00 2001 From: trisdoan Date: Thu, 3 Oct 2024 10:03:56 +0700 Subject: [PATCH 3/3] [FW] port missing commits from 14.0 to 17.0 --- .oca/oca-port/blacklist/mail_tracking.json | 6 ++ mail_tracking/models/mail_tracking_email.py | 7 +- mail_tracking/static/description/index.html | 11 ++- .../tests/test_gc_mail_tracking_email.py | 5 +- mail_tracking/tests/test_mail_tracking.py | 3 +- mail_tracking/views/res_config_settings.xml | 76 ++++++++----------- 6 files changed, 52 insertions(+), 56 deletions(-) create mode 100644 .oca/oca-port/blacklist/mail_tracking.json 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/models/mail_tracking_email.py b/mail_tracking/models/mail_tracking_email.py index e42c4e2638..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 @@ -490,7 +489,7 @@ def _gc_mail_tracking_email(self, limit=5000): _logger.info( "Deleting %s mail.tracking.email records", len(records_to_delete) ) - self.flush() + 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 @@ -498,4 +497,4 @@ def _gc_mail_tracking_email(self, limit=5000): query = "DELETE FROM mail_tracking_email WHERE id IN %s" args = (tuple(records_to_delete.ids),) self.env.cr.execute(query, args) - self.invalidate_cache() + self.invalidate_model() 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 @@

Contributors

Maintainers

This module is maintained by the OCA.

-Odoo Community Association + +Odoo Community Association +

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/test_gc_mail_tracking_email.py b/mail_tracking/tests/test_gc_mail_tracking_email.py index 5754669491..74f32cefed 100644 --- a/mail_tracking/tests/test_gc_mail_tracking_email.py +++ b/mail_tracking/tests/test_gc_mail_tracking_email.py @@ -2,10 +2,11 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) from odoo import fields -from odoo.tests.common import SavepointCase +from odoo.addons.base.tests.common import SavepointCaseWithUserDemo -class TestMailTrackingEmailCleanUp(SavepointCase): + +class TestMailTrackingEmailCleanUp(SavepointCaseWithUserDemo): @classmethod def setUpClass(cls): super().setUpClass() diff --git a/mail_tracking/tests/test_mail_tracking.py b/mail_tracking/tests/test_mail_tracking.py index cb669b4e82..e07a873f8b 100644 --- a/mail_tracking/tests/test_mail_tracking.py +++ b/mail_tracking/tests/test_mail_tracking.py @@ -174,11 +174,10 @@ def test_message_post_show_aliases(self): "model": "res.partner", "res_id": self.recipient.id, "partner_ids": [(4, self.recipient.id)], - "email_cc": "Dominique Pinon , customer-invoices@test.com", + "email_cc": "Dominique Pinon , customer-invoices@test.com", # noqa E501 "body": "

This is another test message

", } ) - message._moderate_accept() message_dict, *_ = message.message_format() self.assertTrue( any( diff --git a/mail_tracking/views/res_config_settings.xml b/mail_tracking/views/res_config_settings.xml index be5f2fa599..cad361fd90 100644 --- a/mail_tracking/views/res_config_settings.xml +++ b/mail_tracking/views/res_config_settings.xml @@ -1,47 +1,35 @@ - - res.config.settings.view.form.inherit.mail.tracking - res.config.settings - - -
-
-
- -
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
+ + res.config.settings.view.form.inherit.mail.tracking + res.config.settings + + + + + +
+
+
+ + +
+
+
+
+
+