diff --git a/mail_composer_cc_bcc/i18n/nl.po b/mail_composer_cc_bcc/i18n/nl.po index c41b46fbf..de37803d8 100644 --- a/mail_composer_cc_bcc/i18n/nl.po +++ b/mail_composer_cc_bcc/i18n/nl.po @@ -6,13 +6,15 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 18.0\n" "Report-Msgid-Bugs-To: \n" -"Last-Translator: Automatically generated\n" +"PO-Revision-Date: 2026-06-03 16:42+0000\n" +"Last-Translator: Bosd \n" "Language-Team: none\n" "Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" "Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.15.2\n" #. module: mail_composer_cc_bcc #: model:ir.model.fields,field_description:mail_composer_cc_bcc.field_mail_compose_message__partner_bcc_ids @@ -20,97 +22,97 @@ msgstr "" #: model:ir.model.fields,field_description:mail_composer_cc_bcc.field_mail_template__email_bcc #: model:ir.model.fields,field_description:mail_composer_cc_bcc.field_mail_template_preview__email_bcc msgid "Bcc" -msgstr "" +msgstr "Bcc" #. module: mail_composer_cc_bcc #: model:ir.model.fields,field_description:mail_composer_cc_bcc.field_mail_mail__recipient_bcc_ids #: model:ir.model.fields,field_description:mail_composer_cc_bcc.field_mail_message__recipient_bcc_ids msgid "Bcc (Partners)" -msgstr "" +msgstr "Bcc (Relaties)" #. module: mail_composer_cc_bcc #: model:ir.model.fields,help:mail_composer_cc_bcc.field_mail_template_preview__email_bcc msgid "Blind Carbon copy recipients" -msgstr "" +msgstr "Bcc-ontvangers" #. module: mail_composer_cc_bcc #: model:ir.model.fields,help:mail_composer_cc_bcc.field_mail_mail__email_bcc msgid "Blind Cc message recipients" -msgstr "" +msgstr "Bcc-berichtontvangers" #. module: mail_composer_cc_bcc #: model:ir.model.fields,help:mail_composer_cc_bcc.field_mail_template__email_bcc msgid "Blind cc recipients (placeholders may be used here)" -msgstr "" +msgstr "Bcc-ontvangers (placeholders kunnen hier worden gebruikt)" #. module: mail_composer_cc_bcc #: model:ir.model.fields,field_description:mail_composer_cc_bcc.field_mail_compose_message__partner_cc_ids msgid "Cc" -msgstr "" +msgstr "Cc" #. module: mail_composer_cc_bcc #: model:ir.model.fields,field_description:mail_composer_cc_bcc.field_mail_mail__recipient_cc_ids #: model:ir.model.fields,field_description:mail_composer_cc_bcc.field_mail_message__recipient_cc_ids msgid "Cc (Partners)" -msgstr "" +msgstr "Cc (Relaties)" #. module: mail_composer_cc_bcc #: model_terms:ir.ui.view,arch_db:mail_composer_cc_bcc.email_template_form_inherit #: model_terms:ir.ui.view,arch_db:mail_composer_cc_bcc.view_mail_form_inherit msgid "Comma-separated blind carbon copy recipients addresses" -msgstr "" +msgstr "Door komma's gescheiden adressen van Bcc-ontvangers" #. module: mail_composer_cc_bcc #: model:ir.model,name:mail_composer_cc_bcc.model_res_company msgid "Companies" -msgstr "" +msgstr "Bedrijven" #. module: mail_composer_cc_bcc #: model:ir.model.fields,field_description:mail_composer_cc_bcc.field_res_company__default_partner_bcc_ids msgid "Default Bcc" -msgstr "" +msgstr "Standaard Bcc" #. module: mail_composer_cc_bcc #: model:ir.model.fields,field_description:mail_composer_cc_bcc.field_res_company__default_partner_cc_ids msgid "Default Cc" -msgstr "" +msgstr "Standaard Cc" #. module: mail_composer_cc_bcc #: model:ir.model,name:mail_composer_cc_bcc.model_mail_template_preview msgid "Email Template Preview" -msgstr "" +msgstr "Voorbeeld e-mailsjabloon" #. module: mail_composer_cc_bcc #: model:ir.model,name:mail_composer_cc_bcc.model_mail_template msgid "Email Templates" -msgstr "" +msgstr "E-mailsjablonen" #. module: mail_composer_cc_bcc #: model:ir.model,name:mail_composer_cc_bcc.model_mail_thread msgid "Email Thread" -msgstr "" +msgstr "E-maildiscussie" #. module: mail_composer_cc_bcc #: model:ir.model,name:mail_composer_cc_bcc.model_mail_compose_message msgid "Email composition wizard" -msgstr "" +msgstr "Wizard e-mail opstellen" #. module: mail_composer_cc_bcc #: model:ir.model,name:mail_composer_cc_bcc.model_ir_mail_server msgid "Mail Server" -msgstr "" +msgstr "Mailserver" #. module: mail_composer_cc_bcc #: model:ir.model,name:mail_composer_cc_bcc.model_mail_message msgid "Message" -msgstr "" +msgstr "Bericht" #. module: mail_composer_cc_bcc #: model:ir.model,name:mail_composer_cc_bcc.model_mail_mail msgid "Outgoing Mails" -msgstr "" +msgstr "Uitgaande e-mails" #. module: mail_composer_cc_bcc #: model_terms:ir.ui.view,arch_db:mail_composer_cc_bcc.view_mail_form_inherit msgid "Partners" -msgstr "" +msgstr "Relaties" diff --git a/mail_thread_disable_auto_follower/README.rst b/mail_thread_disable_auto_follower/README.rst new file mode 100644 index 000000000..21ad4545e --- /dev/null +++ b/mail_thread_disable_auto_follower/README.rst @@ -0,0 +1,137 @@ +================================= +Disable Mail Thread Auto-follower +================================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:fa79b5e41d473cdccf8b3879058a40600fb5eba961deb26806818df52f494732 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fmail-lightgray.png?logo=github + :target: https://github.com/OCA/mail/tree/18.0/mail_thread_disable_auto_follower + :alt: OCA/mail +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/mail-18-0/mail-18-0-mail_thread_disable_auto_follower + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/mail&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Odoo automatically adds record creators and message senders as followers +of document threads. While this is useful in many scenarios, it can lead +to cluttered chatter mailboxes and unwanted email notifications — +especially on high-volume models like Sales Orders, Purchase Orders, or +Contacts. + +This module lets you **selectively disable** that automatic follower +behavior on a per-model basis. It provides a single, centralized +configuration page (accessible only in Developer / debug mode) where +system administrators can choose exactly which models should skip Odoo's +built-in auto-follower subscription logic. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +Configuration Workflow +---------------------- + +Step 1 — Access the Configuration Menu +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Activate **Developer Mode (debug)** in your Odoo database, then navigate +to: + +**Settings > Technical > Automation > Disable Auto-Follower +Configuration** + +|Menu Navigation| + +Step 2 — Unified Singleton Form View +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The menu action opens a single, clean Form view that represents the +global configuration. Record creation and deletion are intentionally +restricted to maintain system integrity — there is always exactly one +configuration record. + +|Singleton Form View| + +Step 3 — Select Models +~~~~~~~~~~~~~~~~~~~~~~ + +Use the **Many2many tags** dropdown field to select the target models on +which auto-follower subscriptions should be disabled (e.g. +``sale.order``, ``purchase.order``, ``res.partner``). Save your changes. + +|Selecting Models| + +Step 4 — Verification +~~~~~~~~~~~~~~~~~~~~~ + +Create a new record on one of the selected models (for example, create a +Sales Order). You will notice that the record creator is **not** +automatically subscribed as a follower in the document's chatter thread. + +|Auto-subscription Bypass Verification| + +.. |Menu Navigation| image:: https://raw.githubusercontent.com/OCA/mail/18.0/mail_thread_disable_auto_follower/static/description/screenshots/img1.png +.. |Singleton Form View| image:: https://raw.githubusercontent.com/OCA/mail/18.0/mail_thread_disable_auto_follower/static/description/screenshots/img2.png +.. |Selecting Models| image:: https://raw.githubusercontent.com/OCA/mail/18.0/mail_thread_disable_auto_follower/static/description/screenshots/img3.png +.. |Auto-subscription Bypass Verification| image:: https://raw.githubusercontent.com/OCA/mail/18.0/mail_thread_disable_auto_follower/static/description/screenshots/img4.png + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* CIT Services + +Contributors +------------ + +- CIT Services https://cit-services.eu +- Prayag prayag.k@cit-services.eu + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +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. + +This module is part of the `OCA/mail `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/mail_thread_disable_auto_follower/__init__.py b/mail_thread_disable_auto_follower/__init__.py new file mode 100644 index 000000000..e9139d787 --- /dev/null +++ b/mail_thread_disable_auto_follower/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2026 CIT Services +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models +from . import wizards diff --git a/mail_thread_disable_auto_follower/__manifest__.py b/mail_thread_disable_auto_follower/__manifest__.py new file mode 100644 index 000000000..e14179a10 --- /dev/null +++ b/mail_thread_disable_auto_follower/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2026 CIT Services +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Disable Mail Thread Auto-follower", + "summary": "Disable automatic follower subscriptions on selected models.", + "author": "CIT Services, Odoo Community Association (OCA)", + "company": "CIT Services", + "website": "https://github.com/OCA/mail", + "category": "Social Network", + "version": "18.0.1.0.0", + "license": "AGPL-3", + "depends": ["mail"], + "data": [ + "security/ir.model.access.csv", + "data/mail_thread_disable_auto_followers_data.xml", + "views/mail_thread_disable_auto_followers_views.xml", + ], + "installable": True, +} diff --git a/mail_thread_disable_auto_follower/data/mail_thread_disable_auto_followers_data.xml b/mail_thread_disable_auto_follower/data/mail_thread_disable_auto_followers_data.xml new file mode 100644 index 000000000..403175d03 --- /dev/null +++ b/mail_thread_disable_auto_follower/data/mail_thread_disable_auto_followers_data.xml @@ -0,0 +1,9 @@ + + + + Configuration + + diff --git a/mail_thread_disable_auto_follower/models/__init__.py b/mail_thread_disable_auto_follower/models/__init__.py new file mode 100644 index 000000000..8208a553b --- /dev/null +++ b/mail_thread_disable_auto_follower/models/__init__.py @@ -0,0 +1,6 @@ +# Copyright 2026 CIT Services +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import mail_thread +from . import mail_followers +from . import mail_thread_disable_auto_followers_config diff --git a/mail_thread_disable_auto_follower/models/mail_followers.py b/mail_thread_disable_auto_follower/models/mail_followers.py new file mode 100644 index 000000000..51abc3345 --- /dev/null +++ b/mail_thread_disable_auto_follower/models/mail_followers.py @@ -0,0 +1,27 @@ +# Copyright 2026 CIT Services +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models + + +class MailFollowers(models.AbstractModel): + _inherit = "mail.followers" + + def _get_subscription_data( + self, doc_data, pids, include_pshare=False, include_active=False + ): + res = super()._get_subscription_data( + doc_data, pids, include_pshare=include_pshare, include_active=include_active + ) + daf_models = self.env.context.get("mail_thread_disable_auto_followers", []) + if ( + self.env.context.get("mail_create_nosubscribe") + and self.env.context.get("active_model") in daf_models + ): + res_out = [] + for row in res: + row_list = list(row) + row_list[2] = None # Replace partner_id (index 2) with None + res_out.append(tuple(row_list)) + return res_out + return res diff --git a/mail_thread_disable_auto_follower/models/mail_thread.py b/mail_thread_disable_auto_follower/models/mail_thread.py new file mode 100644 index 000000000..102c546ff --- /dev/null +++ b/mail_thread_disable_auto_follower/models/mail_thread.py @@ -0,0 +1,91 @@ +# Copyright 2026 CIT Services +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, models + + +class MailThread(models.AbstractModel): + _inherit = "mail.thread" + + @api.model_create_multi + def create(self, vals_list): + config = ( + self.env["mail.thread.disable.auto.followers.config"] + .sudo() + .search([], limit=1) + ) + daf_models = config.model_ids.mapped("model") if config else [] + if ( + not self.env.context.get("mail_thread_allow_auto_followers") + and self._name in daf_models + ): + self = self.with_context( + mail_create_nosubscribe=True, + mail_thread_disable_auto_followers=daf_models, + active_model=self._name, + ) + return super().create(vals_list) + + def write(self, vals): + config = ( + self.env["mail.thread.disable.auto.followers.config"] + .sudo() + .search([], limit=1) + ) + daf_models = config.model_ids.mapped("model") if config else [] + if ( + not self.env.context.get("mail_thread_allow_auto_followers") + and self._name in daf_models + ): + self = self.with_context(mail_thread_disable_auto_followers=daf_models) + return super().write(vals) + + @api.returns("mail.message", lambda value: value.id) + def message_post(self, **kwargs): + """ + We also need to handle the case where the create/write context + is gone (e.g. mail wizard). + """ + if not self.env.context.get( + "mail_thread_allow_auto_followers" + ) and self.env.context.get("mail_post_autofollow"): + config = ( + self.env["mail.thread.disable.auto.followers.config"] + .sudo() + .search([], limit=1) + ) + daf_models = config.model_ids.mapped("model") if config else [] + if self._name in daf_models: + self = self.with_context( + mail_post_autofollow=False, + mail_create_nosubscribe=True, + ) + return super().message_post(**kwargs) + + def message_subscribe(self, partner_ids=None, subtype_ids=None): + """ + In some cases message_subscribe is called explicitly. + """ + if not self.env.context.get("mail_thread_allow_auto_followers"): + config = ( + self.env["mail.thread.disable.auto.followers.config"] + .sudo() + .search([], limit=1) + ) + daf_models = config.model_ids.mapped("model") if config else [] + if self._name in daf_models: + partner_ids = None + return super().message_subscribe( + partner_ids=partner_ids, subtype_ids=subtype_ids + ) + + def _message_auto_subscribe_followers(self, updated_values, default_subtype_ids): + daf_models = self.env.context.get("mail_thread_disable_auto_followers", []) + if ( + not self.env.context.get("mail_thread_allow_auto_followers") + and self._name in daf_models + ): + return [] + return super()._message_auto_subscribe_followers( + updated_values, default_subtype_ids + ) diff --git a/mail_thread_disable_auto_follower/models/mail_thread_disable_auto_followers_config.py b/mail_thread_disable_auto_follower/models/mail_thread_disable_auto_followers_config.py new file mode 100644 index 000000000..0ed860b2d --- /dev/null +++ b/mail_thread_disable_auto_follower/models/mail_thread_disable_auto_followers_config.py @@ -0,0 +1,16 @@ +# Copyright 2026 CIT Services +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class MailThreadDisableAutoFollowersConfig(models.Model): + _name = "mail.thread.disable.auto.followers.config" + _description = "Disable Auto Followers Configuration" + + name = fields.Char(default="Configuration", required=True, readonly=True) + model_ids = fields.Many2many( + "ir.model", + string="Disabled Models", + domain=[("model", "!=", False)], + ) diff --git a/mail_thread_disable_auto_follower/pyproject.toml b/mail_thread_disable_auto_follower/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/mail_thread_disable_auto_follower/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/mail_thread_disable_auto_follower/readme/CONTRIBUTORS.md b/mail_thread_disable_auto_follower/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..c94d732d8 --- /dev/null +++ b/mail_thread_disable_auto_follower/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- CIT Services +- Prayag diff --git a/mail_thread_disable_auto_follower/readme/DESCRIPTION.md b/mail_thread_disable_auto_follower/readme/DESCRIPTION.md new file mode 100644 index 000000000..cbf8ed75c --- /dev/null +++ b/mail_thread_disable_auto_follower/readme/DESCRIPTION.md @@ -0,0 +1,10 @@ +Odoo automatically adds record creators and message senders as followers of +document threads. While this is useful in many scenarios, it can lead to +cluttered chatter mailboxes and unwanted email notifications — especially on +high-volume models like Sales Orders, Purchase Orders, or Contacts. + +This module lets you **selectively disable** that automatic follower behavior +on a per-model basis. It provides a single, centralized configuration page +(accessible only in Developer / debug mode) where system administrators can +choose exactly which models should skip Odoo's built-in auto-follower +subscription logic. diff --git a/mail_thread_disable_auto_follower/readme/USAGE.md b/mail_thread_disable_auto_follower/readme/USAGE.md new file mode 100644 index 000000000..013251c55 --- /dev/null +++ b/mail_thread_disable_auto_follower/readme/USAGE.md @@ -0,0 +1,33 @@ +## Configuration Workflow + +### Step 1 — Access the Configuration Menu + +Activate **Developer Mode (debug)** in your Odoo database, then navigate to: + +**Settings > Technical > Automation > Disable Auto-Follower Configuration** + +![Menu Navigation](../static/description/screenshots/img1.png) + +### Step 2 — Unified Singleton Form View + +The menu action opens a single, clean Form view that represents the global +configuration. Record creation and deletion are intentionally restricted to +maintain system integrity — there is always exactly one configuration record. + +![Singleton Form View](../static/description/screenshots/img2.png) + +### Step 3 — Select Models + +Use the **Many2many tags** dropdown field to select the target models on which +auto-follower subscriptions should be disabled (e.g. `sale.order`, +`purchase.order`, `res.partner`). Save your changes. + +![Selecting Models](../static/description/screenshots/img3.png) + +### Step 4 — Verification + +Create a new record on one of the selected models (for example, create a Sales +Order). You will notice that the record creator is **not** automatically +subscribed as a follower in the document's chatter thread. + +![Auto-subscription Bypass Verification](../static/description/screenshots/img4.png) diff --git a/mail_thread_disable_auto_follower/security/ir.model.access.csv b/mail_thread_disable_auto_follower/security/ir.model.access.csv new file mode 100644 index 000000000..edfaddc8d --- /dev/null +++ b/mail_thread_disable_auto_follower/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_mail_thread_disable_auto_followers_config_user,access_mail_thread_disable_auto_followers_config_user,model_mail_thread_disable_auto_followers_config,base.group_user,1,0,0,0 +access_mail_thread_disable_auto_followers_config_system,access_mail_thread_disable_auto_followers_config_system,model_mail_thread_disable_auto_followers_config,base.group_system,1,1,1,1 diff --git a/mail_thread_disable_auto_follower/static/description/index.html b/mail_thread_disable_auto_follower/static/description/index.html new file mode 100644 index 000000000..3f6ba3ec6 --- /dev/null +++ b/mail_thread_disable_auto_follower/static/description/index.html @@ -0,0 +1,53 @@ + + + + + + Disable Mail Thread Auto-follower + + + + +
+ +
+
+

Disable Mail Thread Auto-Follower

+

Selectively disable automatic follower subscriptions on specific Odoo + models.

+
+
+ + +
+
+
+
+

Overview

+

+ Odoo automatically adds record creators and message senders as followers of document + threads. While useful in many scenarios, this can lead to cluttered chatter mailboxes + and unwanted email notifications on high-volume models like Sales Orders, Purchase + Orders, or Contacts. +

+

+ This module provides a centralized configuration page (accessible only in Developer + Mode) where administrators can choose which models should skip Odoo's auto-follower + subscription logic. +

+
+
+
+
+ + +
+
+
+

Developed for Odoo 18. Community Association (OCA) standards compliant.

+
+
+
+ + + \ No newline at end of file diff --git a/mail_thread_disable_auto_follower/static/description/screenshots/img1.png b/mail_thread_disable_auto_follower/static/description/screenshots/img1.png new file mode 100644 index 000000000..06767c38c Binary files /dev/null and b/mail_thread_disable_auto_follower/static/description/screenshots/img1.png differ diff --git a/mail_thread_disable_auto_follower/static/description/screenshots/img2.png b/mail_thread_disable_auto_follower/static/description/screenshots/img2.png new file mode 100644 index 000000000..08d9f8f0f Binary files /dev/null and b/mail_thread_disable_auto_follower/static/description/screenshots/img2.png differ diff --git a/mail_thread_disable_auto_follower/static/description/screenshots/img3.png b/mail_thread_disable_auto_follower/static/description/screenshots/img3.png new file mode 100644 index 000000000..51d655a6c Binary files /dev/null and b/mail_thread_disable_auto_follower/static/description/screenshots/img3.png differ diff --git a/mail_thread_disable_auto_follower/static/description/screenshots/img4.png b/mail_thread_disable_auto_follower/static/description/screenshots/img4.png new file mode 100644 index 000000000..fbd49b7cb Binary files /dev/null and b/mail_thread_disable_auto_follower/static/description/screenshots/img4.png differ diff --git a/mail_thread_disable_auto_follower/tests/__init__.py b/mail_thread_disable_auto_follower/tests/__init__.py new file mode 100644 index 000000000..c09cecdc1 --- /dev/null +++ b/mail_thread_disable_auto_follower/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2026 CIT Services +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import test_mail_thread_disable_auto_followers diff --git a/mail_thread_disable_auto_follower/tests/test_mail_thread_disable_auto_followers.py b/mail_thread_disable_auto_follower/tests/test_mail_thread_disable_auto_followers.py new file mode 100644 index 000000000..d29a2aa42 --- /dev/null +++ b/mail_thread_disable_auto_follower/tests/test_mail_thread_disable_auto_followers.py @@ -0,0 +1,268 @@ +# Copyright 2026 CIT Services +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests import TransactionCase + + +class TestMailThreadDisableAutoFollowers(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.test_user = cls.env["res.users"].create( + { + "name": "Test User", + "login": "test_user_disable_followers", + "email": "test_disable_followers@test.com", + "groups_id": [ + ( + 6, + 0, + [ + cls.env.ref("base.group_user").id, + cls.env.ref("base.group_partner_manager").id, + ], + ) + ], + } + ) + cls.env["mail.thread.disable.auto.followers.config"].search([]).unlink() + cls.config = cls.env["mail.thread.disable.auto.followers.config"].search( + [], limit=1 + ) + if not cls.config: + cls.config = cls.env["mail.thread.disable.auto.followers.config"].create({}) + cls.model_partner = cls.env["ir.model"].search([("model", "=", "res.partner")]) + + def test_01_normal_auto_follower(self): + """Verify normal behavior where auto-followers are added if not disabled.""" + self.config.write({"model_ids": [(5, 0, 0)]}) + partner_normal = ( + self.env["res.partner"] + .with_user(self.test_user) + .create( + { + "name": "Partner Normal", + } + ) + ) + followers_normal = partner_normal.message_follower_ids.mapped("partner_id") + self.assertIn(self.test_user.partner_id, followers_normal) + + def test_02_disabled_create(self): + """Verify record creator is not auto-subscribed on disabled models.""" + self.config.write({"model_ids": [(6, 0, [self.model_partner.id])]}) + partner_disabled = ( + self.env["res.partner"] + .with_user(self.test_user) + .create( + { + "name": "Partner Disabled", + } + ) + ) + followers_disabled = partner_disabled.message_follower_ids.mapped("partner_id") + self.assertNotIn(self.test_user.partner_id, followers_disabled) + + def test_03_disabled_composer(self): + """Verify followers are not auto-added via mail.compose.message wizard.""" + self.config.write({"model_ids": [(6, 0, [self.model_partner.id])]}) + partner_disabled = ( + self.env["res.partner"] + .with_user(self.test_user) + .create( + { + "name": "Partner Disabled Composer", + } + ) + ) + + composer = ( + self.env["mail.compose.message"] + .with_user(self.test_user) + .with_context( + active_model="res.partner", + active_ids=partner_disabled.ids, + ) + .create( + { + "subject": "Test Subject", + "body": "Test Body", + } + ) + ) + composer._action_send_mail() + + followers_post = partner_disabled.message_follower_ids.mapped("partner_id") + self.assertNotIn(self.test_user.partner_id, followers_post) + + def test_04_disabled_message_post(self): + """Verify direct message_post calls do not add the poster as a follower.""" + self.config.write({"model_ids": [(6, 0, [self.model_partner.id])]}) + partner_disabled = ( + self.env["res.partner"] + .with_user(self.test_user) + .create( + { + "name": "Partner Disabled Post", + } + ) + ) + + partner_disabled.with_user(self.test_user).message_post( + body="Direct message", + message_type="comment", + subtype_xmlid="mail.mt_comment", + ) + followers_after_post = partner_disabled.message_follower_ids.mapped( + "partner_id" + ) + self.assertNotIn(self.test_user.partner_id, followers_after_post) + + def test_05_disabled_message_subscribe(self): + """Verify explicit subscriber additions are bypassed/blocked on + disabled models. + """ + self.config.write({"model_ids": [(6, 0, [self.model_partner.id])]}) + partner_disabled = ( + self.env["res.partner"] + .with_user(self.test_user) + .create( + { + "name": "Partner Disabled Subscribe", + } + ) + ) + + partner_disabled.with_user(self.test_user).message_subscribe( + partner_ids=[self.test_user.partner_id.id] + ) + followers_after_sub = partner_disabled.message_follower_ids.mapped("partner_id") + self.assertNotIn(self.test_user.partner_id, followers_after_sub) + + def test_06_disabled_write(self): + """Verify editing fields does not trigger automatic user subscription.""" + self.config.write({"model_ids": [(6, 0, [self.model_partner.id])]}) + partner_disabled = ( + self.env["res.partner"] + .with_user(self.test_user) + .create( + { + "name": "Partner Disabled Write", + } + ) + ) + + partner_disabled.with_user(self.test_user).write( + { + "name": "Partner Disabled Updated", + } + ) + followers_after_write = partner_disabled.message_follower_ids.mapped( + "partner_id" + ) + self.assertNotIn(self.test_user.partner_id, followers_after_write) + + def test_07_allow_auto_followers_context(self): + """Verify override context allows followers again.""" + self.config.write({"model_ids": [(6, 0, [self.model_partner.id])]}) + + partner = ( + self.env["res.partner"] + .with_user(self.test_user) + .with_context(mail_thread_allow_auto_followers=True) + .create( + { + "name": "Partner Allow Followers", + } + ) + ) + + followers = partner.message_follower_ids.mapped("partner_id") + self.assertIn(self.test_user.partner_id, followers) + + def test_08_message_subscribe_allowed(self): + """Verify explicit subscribe works when override context is enabled.""" + self.config.write({"model_ids": [(6, 0, [self.model_partner.id])]}) + + partner = self.env["res.partner"].create( + { + "name": "Partner Subscribe Allowed", + } + ) + + partner.with_context(mail_thread_allow_auto_followers=True).message_subscribe( + partner_ids=[self.test_user.partner_id.id] + ) + + followers = partner.message_follower_ids.mapped("partner_id") + self.assertIn(self.test_user.partner_id, followers) + + def test_09_non_disabled_model(self): + """Verify normal behavior on models not in config.""" + self.config.write({"model_ids": [(5, 0, 0)]}) + + partner = ( + self.env["res.partner"] + .with_user(self.test_user) + .create( + { + "name": "Normal Partner", + } + ) + ) + + followers = partner.message_follower_ids.mapped("partner_id") + self.assertIn(self.test_user.partner_id, followers) + + def test_10_message_post_autofollow_context(self): + """Verify message_post with mail_post_autofollow=True context + does not auto-subscribe. + """ + self.config.write({"model_ids": [(6, 0, [self.model_partner.id])]}) + partner_disabled = ( + self.env["res.partner"] + .with_user(self.test_user) + .create( + { + "name": "Partner Disabled Post AutoFollow", + } + ) + ) + partner_disabled.with_user(self.test_user).with_context( + mail_post_autofollow=True + ).message_post( + body="Direct message with autofollow", + message_type="comment", + subtype_xmlid="mail.mt_comment", + ) + followers = partner_disabled.message_follower_ids.mapped("partner_id") + self.assertNotIn(self.test_user.partner_id, followers) + + def test_11_get_subscription_data_with_followers(self): + """Verify _get_subscription_data returns filtered results + when there are followers. + """ + self.config.write({"model_ids": [(5, 0, 0)]}) + partner = ( + self.env["res.partner"] + .with_user(self.test_user) + .create({"name": "Test Partner for Subscription Data"}) + ) + self.assertTrue(partner.message_follower_ids) + + self.config.write({"model_ids": [(6, 0, [self.model_partner.id])]}) + followers_model = self.env["mail.followers"] + + partner_pids = partner.message_follower_ids.mapped("partner_id").ids + res_normal = followers_model._get_subscription_data( + [(partner._name, partner.ids)], partner_pids + ) + self.assertTrue(res_normal) + + res_filtered = followers_model.with_context( + mail_create_nosubscribe=True, + active_model=partner._name, + mail_thread_disable_auto_followers=[partner._name], + )._get_subscription_data([(partner._name, partner.ids)], partner_pids) + for row in res_filtered: + self.assertIsNone(row[2]) diff --git a/mail_thread_disable_auto_follower/views/mail_thread_disable_auto_followers_views.xml b/mail_thread_disable_auto_follower/views/mail_thread_disable_auto_followers_views.xml new file mode 100644 index 000000000..90450f429 --- /dev/null +++ b/mail_thread_disable_auto_follower/views/mail_thread_disable_auto_followers_views.xml @@ -0,0 +1,52 @@ + + + + + mail.thread.disable.auto.followers.config.form + mail.thread.disable.auto.followers.config + +
+ + + + + + + +
+
+
+ + + Disable Auto Follower Configuration + mail.thread.disable.auto.followers.config + form + + + + +
diff --git a/mail_thread_disable_auto_follower/wizards/__init__.py b/mail_thread_disable_auto_follower/wizards/__init__.py new file mode 100644 index 000000000..5cc7b0424 --- /dev/null +++ b/mail_thread_disable_auto_follower/wizards/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2026 CIT Services +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import mail_compose_message diff --git a/mail_thread_disable_auto_follower/wizards/mail_compose_message.py b/mail_thread_disable_auto_follower/wizards/mail_compose_message.py new file mode 100644 index 000000000..00f8258a7 --- /dev/null +++ b/mail_thread_disable_auto_follower/wizards/mail_compose_message.py @@ -0,0 +1,26 @@ +# Copyright 2026 CIT Services +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models + + +class MailComposeMessage(models.TransientModel): + _inherit = "mail.compose.message" + + def _action_send_mail(self, auto_commit=False): + config = ( + self.env["mail.thread.disable.auto.followers.config"] + .sudo() + .search([], limit=1) + ) + daf_models = config.model_ids.mapped("model") if config else [] + if ( + self.model in daf_models + or self.env.context.get("active_model") in daf_models + ): + self = self.with_context( + mail_create_nosubscribe=True, + mail_post_autofollow=False, + mail_thread_disable_auto_followers=daf_models, + ) + return super()._action_send_mail(auto_commit=auto_commit)