Skip to content

Commit

Permalink
[ADD] mail_autosubscribe
Browse files Browse the repository at this point in the history
  • Loading branch information
ivantodorovich committed Aug 10, 2021
1 parent d471fe3 commit 676bc43
Show file tree
Hide file tree
Showing 23 changed files with 466 additions and 0 deletions.
1 change: 1 addition & 0 deletions mail_autosubscribe/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
20 changes: 20 additions & 0 deletions mail_autosubscribe/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright 2021 Camptocamp (http://www.camptocamp.com).
# @author Iván Todorovich <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

{
"name": "Mail Autosubscribe",
"summary": "Automatically subscribe partners to its company's business documents",
"version": "14.0.1.0.0",
"author": "Camptocamp SA, Odoo Community Association (OCA)",
"license": "AGPL-3",
"category": "Marketing",
"depends": ["mail"],
"website": "https://github.com/OCA/social",
"data": [
"security/ir.model.access.csv",
"views/mail_autosubscribe.xml",
"views/mail_template.xml",
"views/res_partner.xml",
],
}
5 changes: 5 additions & 0 deletions mail_autosubscribe/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from . import models
from . import res_partner
from . import mail_thread
from . import mail_autosubscribe
from . import mail_template
42 changes: 42 additions & 0 deletions mail_autosubscribe/models/mail_autosubscribe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Copyright 2021 Camptocamp (http://www.camptocamp.com).
# @author Iván Todorovich <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import api, fields, models


class MailAutosubscribe(models.Model):
_name = "mail.autosubscribe"
_description = "Mail Autosubscribe"

_sql_constraints = [
(
"model_id_unique",
"UNIQUE(model_id)",
"There's already a rule for this model",
)
]

model_id = fields.Many2one(
"ir.model",
required=True,
index=True,
ondelete="cascade",
)
model = fields.Char(
related="model_id.model",
string="Model Name",
store=True,
index=True,
)
name = fields.Char(
compute="_compute_name",
store=True,
readonly=False,
)

@api.depends("model_id")
def _compute_name(self):
for rec in self:
if not rec.name:
rec.name = rec.model_id.name
34 changes: 34 additions & 0 deletions mail_autosubscribe/models/mail_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copyright 2021 Camptocamp (http://www.camptocamp.com).
# @author Iván Todorovich <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import fields, models


class MailTemplate(models.Model):
_inherit = "mail.template"

use_autosubscribe_followers = fields.Boolean(default=True)

def generate_recipients(self, results, res_ids):
res = super().generate_recipients(results, res_ids)
autosubscribe_followers = (
self.use_autosubscribe_followers
and not self.env.context.get("no_autosubscribe_followers")
# In this case, autosubscribers will be added by
# :func:`_message_get_default_recipients`
and not self.use_default_to
and not self.env.context.get("tpl_force_default_to")
)
if autosubscribe_followers:
for res_id in res.keys():
partners = (
self.env["res.partner"].sudo().browse(res[res_id]["partner_ids"])
)
ResModel = self.env[self.model]
followers = ResModel._message_get_autosubscribe_followers(partners)
follower_ids = [
follower.id for follower in followers if follower not in partners
]
res[res_id]["partner_ids"] += follower_ids
return res
27 changes: 27 additions & 0 deletions mail_autosubscribe/models/mail_thread.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copyright 2021 Camptocamp (http://www.camptocamp.com).
# @author Iván Todorovich <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import models


class MailThread(models.AbstractModel):
_inherit = "mail.thread"

def message_subscribe(self, partner_ids=None, channel_ids=None, subtype_ids=None):
# Overload to automatically subscribe autosubscribe followers.
autosubscribe_followers = not self.env.context.get("no_autosubscribe_followers")
if partner_ids and autosubscribe_followers:
partners = self.env["res.partner"].sudo().browse(partner_ids)
followers = self._message_get_autosubscribe_followers(partners)
follower_ids = [
follower.id
for follower in followers
if follower not in partners and follower not in self.message_partner_ids
]
partner_ids += follower_ids
return super().message_subscribe(
partner_ids=partner_ids,
channel_ids=channel_ids,
subtype_ids=subtype_ids,
)
37 changes: 37 additions & 0 deletions mail_autosubscribe/models/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Copyright 2021 Camptocamp (http://www.camptocamp.com).
# @author Iván Todorovich <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import api, models


class BaseModel(models.AbstractModel):
_inherit = "base"

@api.model
def _message_get_autosubscribe_followers_domain(self, partners):
return [
("id", "child_of", partners.commercial_partner_id.ids),
("mail_autosubscribe_ids.model", "=", self._name),
]

@api.model
def _message_get_autosubscribe_followers(self, partners):
domain = self._message_get_autosubscribe_followers_domain(partners)
return self.env["res.partner"].sudo().search(domain)

def _message_get_default_recipients(self):
# Overload to include auto follow document partners in the composer
# Note: This only works if the template is configured with 'Default recipients'
res = super()._message_get_default_recipients()
if self.env.context.get("no_autosubscribe_followers"):
return res
for rec in self:
partner_ids = res[rec.id]["partner_ids"]
partners = self.env["res.partner"].sudo().browse(partner_ids)
followers = rec._message_get_autosubscribe_followers(partners)
follower_ids = [
follower.id for follower in followers if follower not in partners
]
partner_ids += follower_ids
return res
16 changes: 16 additions & 0 deletions mail_autosubscribe/models/res_partner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright 2021 Camptocamp (http://www.camptocamp.com).
# @author Iván Todorovich <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import fields, models


class ResPartner(models.Model):
_inherit = "res.partner"

mail_autosubscribe_ids = fields.Many2many(
"mail.autosubscribe",
string="Autosubscribe Models",
column1="partner_id",
column2="model_id",
)
8 changes: 8 additions & 0 deletions mail_autosubscribe/readme/CONFIGURE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Go to Configuration > Technical > Automation > Autosubscribe Models and configure
the models for which you want the feature to work.

Then, on each partner, you can check the company documents subscriptions in the
field `In copy of`.

This feature can be disabled on specific templates, if required, by disabling the
Autosubscribe followers field.
3 changes: 3 additions & 0 deletions mail_autosubscribe/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
* `Camptocamp <https://www.camptocamp.com>`_

* Iván Todorovich <[email protected]>
5 changes: 5 additions & 0 deletions mail_autosubscribe/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
This module allows you to configure partners that will be automatically in copy
of their company's business documents.

For example, you can configure an accountant to be in copy of all invoices
sent for a given commercial partner, regardless of the invoicing address.
3 changes: 3 additions & 0 deletions mail_autosubscribe/readme/ROADMAP.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
* Consider implementing domain-based autosubscription rules.
This was considered during first development but it wasn't a requirement at the time.
If pursuit, this has to be done carefully to avoid affecting performance.
3 changes: 3 additions & 0 deletions mail_autosubscribe/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_mail_autosubscribe_user,access_mail_autosubscribe_user,model_mail_autosubscribe,base.group_user,1,0,0,0
access_mail_autosubscribe_system,access_mail_autosubscribe_system,model_mail_autosubscribe,base.group_system,1,1,1,1
1 change: 1 addition & 0 deletions mail_autosubscribe/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import test_mail_autosubscribe
1 change: 1 addition & 0 deletions mail_autosubscribe/tests/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import fake_order
13 changes: 13 additions & 0 deletions mail_autosubscribe/tests/models/fake_order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright 2021 Camptocamp (http://www.camptocamp.com).
# @author Iván Todorovich <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import fields, models


class FakeOrder(models.Model):
_name = "fake.order"
_inherit = "mail.thread"
_description = "Fake sale.order like model"

partner_id = fields.Many2one("res.partner", required=True)
132 changes: 132 additions & 0 deletions mail_autosubscribe/tests/test_mail_autosubscribe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# Copyright 2021 Camptocamp (http://www.camptocamp.com).
# @author Iván Todorovich <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo_test_helper import FakeModelLoader

from odoo.tests.common import Form, SavepointCase, tagged


@tagged("post_install", "-at_install")
class TestMailAutosubscribe(SavepointCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
# Setup env
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
# Load fake order model
cls.loader = FakeModelLoader(cls.env, cls.__module__)
cls.loader.backup_registry()
from .models.fake_order import FakeOrder

cls.loader.update_registry((FakeOrder,))
cls.fake_order_model = cls.env["ir.model"].search(
[("model", "=", "fake.order")]
)
# Email Template
cls.mail_template = cls.env["mail.template"].create(
{
"model_id": cls.fake_order_model.id,
"name": "Fake Order: Send by Mail",
"subject": "Fake Order: ${object.partner_id.name}",
"partner_to": "${object.partner_id.id}",
"body_html": "Hello, this is a fake order",
}
)
# Partners
cls.commercial_partner = cls.env.ref("base.res_partner_4")
cls.partner_1 = cls.env.ref("base.res_partner_address_13")
cls.partner_2 = cls.env.ref("base.res_partner_address_14")
cls.partner_3 = cls.env.ref("base.res_partner_address_24")
# Autosubscribe rules
cls.autosubscribe_fake_order = cls.env["mail.autosubscribe"].create(
{"model_id": cls.fake_order_model.id}
)
cls.partner_3.mail_autosubscribe_ids = [(4, cls.autosubscribe_fake_order.id)]
# Empty fake.order
cls.order = cls.env["fake.order"].create({"partner_id": cls.partner_2.id})

@classmethod
def tearDownClass(cls):
cls.loader.restore_registry()
return super().tearDownClass()

def test_message_subscribe(self):
"""Test autosubscribe on a basic workflow"""
self.assertFalse(self.order.message_partner_ids, "No subscribers yet")
self.order.message_subscribe([self.order.partner_id.id])
self.assertEqual(
self.order.message_partner_ids,
self.partner_2 | self.partner_3,
"Partner 3 is automatically subscribed",
)

def test_message_subscribe_disabled(self):
"""Test autosubscribe on a basic workflow (disabled)"""
self.partner_3.mail_autosubscribe_ids = [(5, False)]
self.assertFalse(self.order.message_partner_ids, "No subscribers yet")
self.order.message_subscribe([self.order.partner_id.id])
self.assertEqual(
self.order.message_partner_ids,
self.partner_2,
"Partner 2 is the only subscriber",
)

def test_mail_template(self):
"""Test autosubscribe when partner is set in the mail.template partners_to"""
self.mail_template.send_mail(self.order.id)
message = self.order.message_ids[0]
self.assertEqual(message.partner_ids, self.partner_2 | self.partner_3)

def test_mail_template_disabled(self):
"""Test autosubscribe when the partner is not an autosubscribe follower"""
self.partner_3.mail_autosubscribe_ids = [(5, False)]
self.mail_template.send_mail(self.order.id)
message = self.order.message_ids[0]
self.assertEqual(message.partner_ids, self.partner_2)

def test_mail_template_no_autosubscribe_followers(self):
"""Test autosubscribe doesn't apply if it's disabled on the template"""
self.mail_template.use_autosubscribe_followers = False
self.mail_template.send_mail(self.order.id)
message = self.order.message_ids[0]
self.assertEqual(message.partner_ids, self.partner_2)

def test_mail_template_default_recipients(self):
"""Test autosubscribe when using default recipients"""
self.mail_template.use_default_to = True
self.mail_template.send_mail(self.order.id)
message = self.order.message_ids[0]
self.assertEqual(message.partner_ids, self.partner_2 | self.partner_3)

def test_mail_message_composer(self):
"""Test autosubscribe when using the mail composer"""
self.assertFalse(self.order.message_partner_ids, "No subscribers yet")
composer = Form(
self.env["mail.compose.message"].with_context(
default_model="fake.order",
default_res_id=self.order.id,
default_use_template=True,
default_template_id=self.mail_template.id,
default_composition_mode="comment",
)
)
composer.save().send_mail()
message = self.order.message_ids[0]
self.assertEqual(message.partner_ids, self.partner_2 | self.partner_3)

def test_mail_message_composer_no_autosubscribe_followers(self):
"""Test autosubscribe when using the mail composer and it's disabled"""
self.mail_template.use_autosubscribe_followers = False
composer = Form(
self.env["mail.compose.message"].with_context(
default_model="fake.order",
default_res_id=self.order.id,
default_use_template=True,
default_template_id=self.mail_template.id,
default_composition_mode="comment",
)
)
composer.save().send_mail()
message = self.order.message_ids[0]
self.assertEqual(message.partner_ids, self.partner_2)
Loading

0 comments on commit 676bc43

Please sign in to comment.