From 15c934a1abfc72c965a6b46618b7b2ae6fad49cc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9my=20Voet=20=28ryv=29?= <ryv@odoo.com>
Date: Wed, 8 Jan 2020 10:09:20 +0000
Subject: [PATCH] [IMP] (website_)mail: improve routes and management of mail
 followers

Purpose of this commit is to improve model of followers, notably management
code and its use in routes. Indeed it is quite an old model and code had
to be cleaned a bit to improve code readability and maintenance.

In this commit we

  * remove unnecessary code examples in gamification about followers: using
    that model as example of code for goals is probably not a good idea as it
    is technical;
  * rewrite routes called by JS are simplified to better match JS
    implementation;
  * introduce computed fields to fetch related partner or channel name,
    email (partner only) and active status;

LINKS

Task 1933771
Task 2078313

closes odoo/odoo#39808

Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
Co-authored-by: Remy Voet <ryv@odoo.com>
Co-authored-by: jgi-odoo <jgi@odoo.com>
Co-authored-by: Xavier-Do <xdo@odoo.com>
---
 addons/gamification/data/goal_base.xml      |  9 ---
 addons/gamification/views/goal.xml          |  2 +-
 addons/mail/controllers/main.py             | 57 ++++++++++-------
 addons/mail/models/mail_followers.py        | 18 ++++++
 addons/mail/models/mail_thread.py           | 25 ++++----
 addons/mail/security/ir.model.access.csv    |  5 +-
 addons/mail/security/mail_security.xml      | 10 ---
 addons/mail/static/src/js/followers.js      | 35 +++++++----
 addons/mail/static/src/xml/followers.xml    | 10 +--
 addons/mail/static/tests/chatter_tests.js   | 69 +++++++++++----------
 addons/test_mail/tests/test_mail_channel.py | 11 ++--
 addons/website_mail/controllers/main.py     | 18 +++---
 12 files changed, 148 insertions(+), 121 deletions(-)

diff --git a/addons/gamification/data/goal_base.xml b/addons/gamification/data/goal_base.xml
index af96a4806cf7..3289073cb132 100644
--- a/addons/gamification/data/goal_base.xml
+++ b/addons/gamification/data/goal_base.xml
@@ -280,15 +280,6 @@ Thank you,
             <field name="action_id" eval="ref('action_new_simplified_res_users')" />
         </record>
 
-        <record model="gamification.goal.definition" id="definition_nbr_following">
-            <field name="name">Mail Group Following</field>
-            <field name="description">Follow mail.channels to receive news</field>
-            <field name="computation_mode">python</field>
-            <field name="compute_code">result = env['mail.followers'].search_count([('res_model', '=', 'mail.channel'), ('partner_id', '=', object.user_id.partner_id.id)])</field>
-            <field name="action_id" eval="ref('mail.mail_channel_action_view')" />
-        </record>
-
-
         <!-- challenges -->
         <record model="gamification.challenge" id="challenge_base_discover">
             <field name="name">Complete your Profile</field>
diff --git a/addons/gamification/views/goal.xml b/addons/gamification/views/goal.xml
index 7301891f7052..1f43e81fc6ec 100644
--- a/addons/gamification/views/goal.xml
+++ b/addons/gamification/views/goal.xml
@@ -249,7 +249,7 @@
                                 attrs="{'invisible':[('computation_mode','!=','sum')], 'required':[('computation_mode','=','sum')]}"/>
                             <field name="field_date_id" class="oe_inline" attrs="{'invisible':[('computation_mode','not in',('sum', 'count'))]}"/>
                             <field name="domain" attrs="{'invisible':[('computation_mode','not in',('sum', 'count'))], 'required':[('computation_mode','in',('sum', 'count'))]}" class="oe_inline"/>
-                            <field name="compute_code" attrs="{'invisible':[('computation_mode','!=','python')], 'required':[('computation_mode','=','python')]}" placeholder="e.g. result = env['mail.followers'].search_count([('res_model', '=', 'mail.channel'), ('partner_id', '=', object.user_id.partner_id.id)])"/>
+                            <field name="compute_code" attrs="{'invisible':[('computation_mode','!=','python')], 'required':[('computation_mode','=','python')]}"/>
                             <field name="condition" widget="radio"/>
                         </group>
                         <group string="Optimisation" name="optimisation" attrs="{'invisible': [('computation_mode', '!=', 'count')]}">
diff --git a/addons/mail/controllers/main.py b/addons/mail/controllers/main.py
index 2dbc9c09288f..b8a2387dbed0 100644
--- a/addons/mail/controllers/main.py
+++ b/addons/mail/controllers/main.py
@@ -141,55 +141,66 @@ class MailController(http.Controller):
         return True
 
     @http.route('/mail/read_followers', type='json', auth='user')
-    def read_followers(self, follower_ids, res_model):
-        followers = []
-        # When editing the followers, the "pencil" icon that leads to the edition of subtypes
-        # should be always be displayed and not only when "debug" mode is activated.
-        is_editable = True
-        partner_id = request.env.user.partner_id
-        follower_id = None
+    def read_followers(self, follower_ids):
+        request.env['mail.followers'].check_access_rights("read")
         follower_recs = request.env['mail.followers'].sudo().browse(follower_ids)
         res_ids = follower_recs.mapped('res_id')
+        res_models = set(follower_recs.mapped('res_model'))
+        if len(res_models) > 1:
+            raise AccessError(_("Can't read followers with different targeted model"))
+        res_model = res_models.pop()
+        request.env[res_model].check_access_rights("read")
         request.env[res_model].browse(res_ids).check_access_rule("read")
+
+        followers = []
+        follower_id = None
         for follower in follower_recs:
-            is_uid = partner_id == follower.partner_id
-            follower_id = follower.id if is_uid else follower_id
+            if follower.partner_id == request.env.user.partner_id:
+                follower_id = follower.id
             followers.append({
                 'id': follower.id,
-                'name': follower.partner_id.name or follower.channel_id.name,
-                'email': follower.partner_id.email if follower.partner_id else None,
-                'res_model': 'res.partner' if follower.partner_id else 'mail.channel',
-                'res_id': follower.partner_id.id or follower.channel_id.id,
-                'is_editable': is_editable,
-                'is_uid': is_uid,
-                'active': follower.partner_id.active or bool(follower.channel_id),
+                'partner_id': follower.partner_id.id,
+                'channel_id': follower.channel_id.id,
+                'name': follower.name,
+                'email': follower.email,
+                'is_active': follower.is_active,
+                # When editing the followers, the "pencil" icon that leads to the edition of subtypes
+                # should be always be displayed and not only when "debug" mode is activated.
+                'is_editable': True
             })
         return {
             'followers': followers,
-            'subtypes': self.read_subscription_data(res_model, follower_id) if follower_id else None
+            'subtypes': self.read_subscription_data(follower_id) if follower_id else None
         }
 
     @http.route('/mail/read_subscription_data', type='json', auth='user')
-    def read_subscription_data(self, res_model, follower_id):
+    def read_subscription_data(self, follower_id):
         """ Computes:
             - message_subtype_data: data about document subtypes: which are
                 available, which are followed if any """
-        followers = request.env['mail.followers'].browse(follower_id)
+        request.env['mail.followers'].check_access_rights("read")
+        follower = request.env['mail.followers'].sudo().browse(follower_id)
+        follower.ensure_one()
+        request.env[follower.res_model].check_access_rights("read")
+        request.env[follower.res_model].browse(follower.res_id).check_access_rule("read")
 
         # find current model subtypes, add them to a dictionary
-        subtypes = request.env['mail.message.subtype'].search(['&', ('hidden', '=', False), '|', ('res_model', '=', res_model), ('res_model', '=', False)])
+        subtypes = request.env['mail.message.subtype'].search([
+            '&', ('hidden', '=', False),
+            '|', ('res_model', '=', follower.res_model), ('res_model', '=', False)])
+        followed_subtypes_ids = set(follower.subtype_ids.ids)
         subtypes_list = [{
             'name': subtype.name,
             'res_model': subtype.res_model,
             'sequence': subtype.sequence,
             'default': subtype.default,
             'internal': subtype.internal,
-            'followed': subtype.id in followers.mapped('subtype_ids').ids,
+            'followed': subtype.id in followed_subtypes_ids,
             'parent_model': subtype.parent_id.res_model,
             'id': subtype.id
         } for subtype in subtypes]
-        subtypes_list = sorted(subtypes_list, key=lambda it: (it['parent_model'] or '', it['res_model'] or '', it['internal'], it['sequence']))
-        return subtypes_list
+        return sorted(subtypes_list,
+                      key=lambda it: (it['parent_model'] or '', it['res_model'] or '', it['internal'], it['sequence']))
 
     @http.route('/mail/view', type='http', auth='public')
     def mail_action_view(self, model=None, res_id=None, access_token=None, **kwargs):
diff --git a/addons/mail/models/mail_followers.py b/addons/mail/models/mail_followers.py
index b9b3f53e8b94..2cd2d48b38d1 100644
--- a/addons/mail/models/mail_followers.py
+++ b/addons/mail/models/mail_followers.py
@@ -35,6 +35,12 @@ class Followers(models.Model):
     subtype_ids = fields.Many2many(
         'mail.message.subtype', string='Subtype',
         help="Message subtypes followed, meaning subtypes that will be pushed onto the user's Wall.")
+    name = fields.Char('Name', compute='_compute_related_fields',
+                       help="Name of the related partner (if exist) or the related channel")
+    email = fields.Char('Email', compute='_compute_related_fields',
+                        help="Email of the related partner (if exist) or False")
+    is_active = fields.Boolean('Is Active', compute='_compute_related_fields',
+                               help="If the related partner is active (if exist) or if related channel exist")
 
     def _invalidate_documents(self, vals_list=None):
         """ Invalidate the cache of the documents followed by ``self``.
@@ -75,6 +81,18 @@ class Followers(models.Model):
     # Private tools methods to fetch followers data
     # --------------------------------------------------
 
+    @api.depends('partner_id', 'channel_id')
+    def _compute_related_fields(self):
+        for follower in self:
+            if follower.partner_id:
+                follower.name = follower.partner_id.name
+                follower.email = follower.partner_id.email
+                follower.is_active = follower.partner_id.active
+            else:
+                follower.name = follower.channel_id.name
+                follower.is_active = bool(follower.channel_id)
+                follower.email = False
+
     def _get_recipient_data(self, records, message_type, subtype_id, pids=None, cids=None):
         """ Private method allowing to fetch recipients data based on a subtype.
         Purpose of this method is to fetch all data necessary to notify recipients
diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py
index 4672cd4d5697..c83d4ae8705d 100644
--- a/addons/mail/models/mail_thread.py
+++ b/addons/mail/models/mail_thread.py
@@ -82,13 +82,15 @@ class MailThread(models.AbstractModel):
     message_is_follower = fields.Boolean(
         'Is Follower', compute='_compute_is_follower', search='_search_is_follower')
     message_follower_ids = fields.One2many(
-        'mail.followers', 'res_id', string='Followers')
+        'mail.followers', 'res_id', string='Followers', groups='base.group_user')
     message_partner_ids = fields.Many2many(
         comodel_name='res.partner', string='Followers (Partners)',
-        compute='_get_followers', search='_search_follower_partners')
+        compute='_get_followers', search='_search_follower_partners',
+        groups='base.group_user')
     message_channel_ids = fields.Many2many(
         comodel_name='mail.channel', string='Followers (Channels)',
-        compute='_get_followers', search='_search_follower_channels')
+        compute='_get_followers', search='_search_follower_channels',
+        groups='base.group_user')
     message_ids = fields.One2many(
         'mail.message', 'res_id', string='Messages',
         domain=lambda self: [('message_type', '!=', 'user_notification')], auto_join=True)
@@ -258,17 +260,16 @@ class MailThread(models.AbstractModel):
         if self._context.get('tracking_disable'):
             return super(MailThread, self).create(vals_list)
 
+        threads = super(MailThread, self).create(vals_list)
         # subscribe uid unless asked not to
         if not self._context.get('mail_create_nosubscribe'):
-            default_followers = self.env['mail.followers']._add_default_followers(
-                self._name, [], self.env.user.partner_id.ids, customer_ids=[],
-                check_existing=False, existing_policy='skip')[0][0]
-            for values in vals_list:
-                message_follower_ids = values.get('message_follower_ids') or []
-                message_follower_ids += [(0, 0, fol_vals) for fol_vals in default_followers]
-                values['message_follower_ids'] = message_follower_ids
-
-        threads = super(MailThread, self).create(vals_list)
+            for thread in threads:
+                self.env['mail.followers']._insert_followers(
+                    thread._name, thread.ids, self.env.user.partner_id.ids,
+                    None, None, None,
+                    customer_ids=[],
+                    check_existing=False
+                )
 
         # auto_subscribe: take values and defaults into account
         create_values_list = {}
diff --git a/addons/mail/security/ir.model.access.csv b/addons/mail/security/ir.model.access.csv
index eb52637ff2e2..2f21338e9f4f 100644
--- a/addons/mail/security/ir.model.access.csv
+++ b/addons/mail/security/ir.model.access.csv
@@ -6,9 +6,8 @@ access_mail_mail_all,mail.mail.all,model_mail_mail,,0,0,0,0
 access_mail_mail_portal,mail.mail.portal,model_mail_mail,base.group_portal,0,0,0,0
 access_mail_mail_user,mail.mail.user,model_mail_mail,base.group_user,0,0,0,0
 access_mail_mail_system,mail.mail.system,model_mail_mail,base.group_system,1,1,1,1
-access_mail_followers_all,mail.followers.all,model_mail_followers,,1,0,0,0
-access_mail_followers_portal,mail.followers.portal,model_mail_followers,base.group_portal,1,1,1,0
-access_mail_followers_user,mail.followers.user,model_mail_followers,base.group_user,1,1,1,0
+access_mail_followers_all,mail.followers.all,model_mail_followers,,0,0,0,0
+access_mail_followers_user,mail.followers.user,model_mail_followers,base.group_user,1,0,0,0
 access_mail_followers_system,mail.followers.system,model_mail_followers,base.group_system,1,1,1,1
 access_mail_notification_portal,mail.notification.portal,model_mail_notification,base.group_portal,1,0,0,0
 access_mail_notification_user,mail.notification.user,model_mail_notification,base.group_user,1,1,1,0
diff --git a/addons/mail/security/mail_security.xml b/addons/mail/security/mail_security.xml
index 3ffe43e35eb4..3a98e1bddb2b 100644
--- a/addons/mail/security/mail_security.xml
+++ b/addons/mail/security/mail_security.xml
@@ -14,16 +14,6 @@
         </record>
     </data>
     <data noupdate="1">
-        <record id="mail_followers_read_write_own" model="ir.rule">
-            <field name="name">mail.followers: write its own entries</field>
-            <field name="model_id" ref="model_mail_followers"/>
-            <field name="groups" eval="[(4, ref('base.group_user')), (4, ref('base.group_portal'))]"/>
-            <field name="domain_force">[('partner_id', '=', user.partner_id.id)]</field>
-            <field name="perm_create" eval="False"/>
-            <field name="perm_unlink" eval="False"/>
-            <field name="perm_read" eval="False"/>
-        </record>
-
         <record id="ir_rule_mail_notifications_group_user" model="ir.rule">
             <field name="name">mail.notifications: group_user: write its own entries</field>
             <field name="model_id" ref="model_mail_notification"/>
diff --git a/addons/mail/static/src/js/followers.js b/addons/mail/static/src/js/followers.js
index cbe20ba240e9..7179b159663d 100644
--- a/addons/mail/static/src/js/followers.js
+++ b/addons/mail/static/src/js/followers.js
@@ -6,6 +6,7 @@ var concurrency = require('web.concurrency');
 var core = require('web.core');
 var Dialog = require('web.Dialog');
 var field_registry = require('web.field_registry');
+var session = require('web.session');
 
 var _t = core._t;
 var QWeb = core.qweb;
@@ -43,7 +44,6 @@ var Followers = AbstractField.extend({
         this.subtypes = [];
         this.data_subtype = {};
         this._isFollower = undefined;
-        var session = this.getSession();
         this.partnerID = session.partner_id;
 
         this.dp = new concurrency.DropPrevious();
@@ -113,14 +113,14 @@ var Followers = AbstractField.extend({
         $(QWeb.render('mail.Followers.add_more', {widget: this})).appendTo($followers_list);
         var $follower_li;
         _.each(this.followers, function (record) {
-            if(!record.active) {
+            if (!record.is_active) {
                 record.title = _.str.sprintf(_t('%s \n(inactive)'), record.name);
             } else {
                 record.title = record.name;
             }
 
             $follower_li = $(QWeb.render('mail.Followers.partner', {
-                'record': _.extend(record, {'avatar_url': '/web/image/' + record.res_model + '/' + record.res_id + '/image_128'}),
+                'record': record,
                 'widget': self})
             );
             $follower_li.appendTo($followers_list);
@@ -199,28 +199,37 @@ var Followers = AbstractField.extend({
         return str;
     },
     _readFollowers: function () {
-        var self = this;
         var missing_ids = _.difference(this.value.res_ids, _.pluck(this.followers, 'id'));
         var def;
         if (missing_ids.length) {
             def = this._rpc({
                     route: '/mail/read_followers',
-                    params: { follower_ids: missing_ids, res_model: this.model, context: {} }  // empty context to be overridden in session.js with 'allowed_company_ids'
+                    params: { follower_ids: missing_ids, context: {} } // empty context to be overridden in session.js with 'allowed_company_ids'
                 });
         }
-        return Promise.resolve(def).then(function (results) {
+        return Promise.resolve(def).then((results) => {
             if (results) {
-                self.followers = _.uniq(results.followers.concat(self.followers), 'id');
+                // Preprocess records
+                _.each(results.followers, (record) => {
+                    var resModel = record.partner_id ? 'res.partner' : 'mail.channel';
+                    var resId = record.partner_id ? record.partner_id : record.channel_id;
+                    record.res_id = resId;
+                    record.res_model = resModel;
+                    record.avatar_url = '/web/image/' + resModel + '/' + resId + '/image_128';
+                });
+                this.followers = _.uniq(results.followers.concat(this.followers), 'id');
                 if (results.subtypes) { //read_followers will return False if current user is not in the list
-                    self.subtypes = results.subtypes;
+                    this.subtypes = results.subtypes;
                 }
             }
             // filter out previously fetched followers that are no longer following
-            self.followers = _.filter(self.followers, function (follower) {
-                return _.contains(self.value.res_ids, follower.id);
+            this.followers = _.filter(this.followers, (follower) => {
+                return _.contains(this.value.res_ids, follower.id);
+            });
+            var userFollower = _.filter(this.followers, (rec) => {
+                return this.partnerID === rec.partner_id;
             });
-            var user_follower = _.filter(self.followers, function (rec) { return rec.is_uid; });
-            self._isFollower = user_follower.length >= 1;
+            this._isFollower = userFollower.length >= 1;
         });
     },
     _reload: function () {
@@ -361,7 +370,7 @@ var Followers = AbstractField.extend({
         var follower_id = $currentTarget.data('follower-id'); // id of model mail_follower
         this._rpc({
                 route: '/mail/read_subscription_data',
-                params: {res_model: this.model, follower_id: follower_id},
+                params: {follower_id: follower_id},
             })
             .then(function (data) {
                 var res_id = $currentTarget.data('oe-id'); // id of model res_partner or mail_channel
diff --git a/addons/mail/static/src/xml/followers.xml b/addons/mail/static/src/xml/followers.xml
index 8a0914737a2c..8789b424af2e 100644
--- a/addons/mail/static/src/xml/followers.xml
+++ b/addons/mail/static/src/xml/followers.xml
@@ -30,12 +30,12 @@
         Partner or channel following the record
     -->
     <t t-name="mail.Followers.partner">
-        <div role="menuitem" t-attf-class="dropdown-item o_partner {{ !record.active ? 'o_inactive': '' }}">
+        <div role="menuitem" t-attf-class="dropdown-item o_partner {{ !record.is_active ? 'o_inactive': '' }}">
             <a class="o_mail_redirect text-truncate"
                 href="#"
                 t-att-title="record.title"
-                t-att-data-oe-model="record.res_model"
-                t-att-data-oe-id="record.res_id">
+                t-att-data-oe-model="record.partner_id ? 'res.partner' : 'mail.channel'"
+                t-att-data-oe-id="record.partner_id ? record.partner_id : record.channel_id">
                 <img t-att-src="record.avatar_url" alt="Avatar" class="o_image_64_cover"/>
                 <t t-esc="record.name"/>
             </a>
@@ -45,8 +45,8 @@
                 aria-label="Edit subscription"
                 role="img"
                 t-att-data-follower-id="record.id"
-                t-att-data-oe-id="record.res_id"
-                t-att-data-oe-model="record.res_model"/>
+                t-att-data-oe-id="record.partner_id ? record.partner_id : record.channel_id"
+                t-att-data-oe-model="record.partner_id ? 'res.partner' : 'mail.channel'"/>
             <button t-if="widget.isEditable"
                 class="btn btn-icon fa fa-remove o_remove_follower"
                 aria-label="Remove this follower"
diff --git a/addons/mail/static/tests/chatter_tests.js b/addons/mail/static/tests/chatter_tests.js
index bc1ff44b746d..f26dd096e62a 100644
--- a/addons/mail/static/tests/chatter_tests.js
+++ b/addons/mail/static/tests/chatter_tests.js
@@ -2490,11 +2490,11 @@ QUnit.test('followers widget: follow/unfollow, edit subtypes', async function (a
                     this.data.partner.records[0].message_follower_ids = [1];
                     followers.push({
                         id: 1,
-                        is_uid: true,
                         name: "Admin",
                         email: "admin@example.com",
-                        res_id: resID,
-                        res_model: 'partner',
+                        partner_id: partnerID,
+                        channel_id: null,
+                        is_active: true
                     });
                 }
                 return Promise.resolve(true);
@@ -2558,18 +2558,18 @@ QUnit.test('followers widget: follow/unfollow confirmation dialog', async functi
     var partnerID = 2;
     var followers = [{
         id: 1,
-        is_uid: true,
         name: "Admin",
         email: "admin@example.com",
-        res_id: resID,
-        res_model: 'res.partner',
+        partner_id: partnerID,
+        channel_id: null,
+        is_active: true   
     }, {
         id: 2,
-        is_uid: true,
         name: "Demo",
         email: "demo@example.com",
-        res_id: 3,
-        res_model: 'res.partner',
+        partner_id: 1,
+        channel_id: null,
+        is_active: true
     }];
 
     var form = await createView({
@@ -2624,12 +2624,14 @@ QUnit.test('followers widget: do not display follower duplications', async funct
 
     this.data.partner.records[0].message_follower_ids = [1];
     var resID = 2;
+    var partnerID = 1;
     var followers = [{
         id: 1,
         name: "Admin",
         email: "admin@example.com",
-        res_id: resID,
-        res_model: 'partner',
+        partner_id: partnerID,
+        channel_id: null,
+        is_active: true   
     }];
     var def;
     var form = await createView({
@@ -2657,17 +2659,18 @@ QUnit.test('followers widget: do not display follower duplications', async funct
             return this._super.apply(this, arguments);
         },
         res_id: resID,
-        session: {partner_id: 1},
+        session: {partner_id: partnerID},
     });
 
 
     followers.push({
         id: 2,
-        is_uid: false,
         name: "A follower",
         email: "follower@example.com",
         res_id: resID,
-        res_model: 'partner',
+        partner_id: 555,
+        channel_id: null,
+        is_active: true
     });
     this.data.partner.records[0].message_follower_ids.push(2);
 
@@ -2695,23 +2698,23 @@ QUnit.test('followers widget: display inactive followers with a different style'
         id: 1,
         name: "Admin",
         email: "admin@example.com",
-        res_id: 101,
-        res_model: 'partner',
-        active: true,
+        partner_id: 101,
+        channel_id: false,
+        is_active: true,
     },{
         id: 2,
         name: "Active_partner",
         email: "active_partner@example.com",
-        res_id: 102,
-        res_model: 'partner',
-        active: true,
+        partner_id: 102,
+        channel_id: false,
+        is_active: true,
     },{
         id: 3,
         name: "Inactive_partner",
         email: "inactive_partner@example.com",
-        res_id: 103,
-        res_model: 'partner',
-        active: false,
+        partner_id: 103,
+        channel_id: false,
+        is_active: false,
     }];
 
     var form = await createView({
@@ -3002,11 +3005,9 @@ QUnit.test('chatter: suggested partner auto-follow on message post', async funct
     var followers = [];
     followers.push({
         id: 1,
-        is_uid: true,
         name: "Admin",
         email: "admin@example.com",
-        res_id: 5,
-        res_model: 'partner',
+        partner_id: 5,
     });
 
     var form = await createView({
@@ -3045,11 +3046,11 @@ QUnit.test('chatter: suggested partner auto-follow on message post', async funct
                 self.data.partner.records[0].message_follower_ids.push(2);
                 followers.push({
                     id: 2,
-                    is_uid: true,
                     name: "Demo User",
                     email: "demo-user@example.com",
-                    res_id: 8,
-                    res_model: 'partner',
+                    partner_id: 8,
+                    channel_id: false,
+                    is_active: true
                 });
 
                 // post a legit message so that it does not crashes
@@ -3187,14 +3188,16 @@ QUnit.test('chatter: mention prefetched partners (followers & employees)', async
                         id: 10,
                         name: 'FollowerUser1',
                         email: 'follower-user1@example.com',
-                        res_model: 'res.partner',
-                        res_id: 1,
+                        partner_id: 1,
+                        channel_id: null,
+                        is_active: true
                     }, {
                         id: 20,
                         name: 'FollowerUser2',
                         email: 'follower-user2@example.com',
-                        res_model: 'res.partner',
-                        res_id: 2,
+                        partner_id: 2,
+                        channel_id: null,
+                        is_active: true,
                     }],
                     subtypes: [],
                 });
diff --git a/addons/test_mail/tests/test_mail_channel.py b/addons/test_mail/tests/test_mail_channel.py
index 8869ac214422..2e3c7c2cb4b8 100644
--- a/addons/test_mail/tests/test_mail_channel.py
+++ b/addons/test_mail/tests/test_mail_channel.py
@@ -99,13 +99,16 @@ class TestChannelAccessRights(TestMailCommon):
         trigger_read = chell_pigs.name
         for message in chell_pigs.message_ids:
             trigger_read = message.subject
-        for partner in chell_pigs.message_partner_ids:
+
+        with self.assertRaises(AccessError):
+            chell_pigs.message_partner_ids
+
+        for partner in self.group_private.message_partner_ids:
             if partner.id == self.user_portal.partner_id.id:
                 # Chell can read her own partner record
                 continue
-            # TODO Change the except_orm to Warning
-            with self.assertRaises(except_orm):
-                trigger_read = partner.name
+            with self.assertRaises(AccessError):
+                trigger_read = partner.with_user(self.user_portal).name
 
 
 class TestChannelFeatures(TestMailCommon):
diff --git a/addons/website_mail/controllers/main.py b/addons/website_mail/controllers/main.py
index 6d00c0641497..822373eb4d6e 100644
--- a/addons/website_mail/controllers/main.py
+++ b/addons/website_mail/controllers/main.py
@@ -11,7 +11,12 @@ class WebsiteMail(http.Controller):
         # TDE FIXME: check this method with new followers
         res_id = int(id)
         is_follower = message_is_follower == 'on'
-        record = request.env[object].browse(res_id)
+        record = request.env[object].browse(res_id).exists()
+        if not record:
+            return False
+
+        record.check_access_rights('read')
+        record.check_access_rule('read')
 
         # search partner_id
         if request.env.user != request.website.user_id:
@@ -24,11 +29,9 @@ class WebsiteMail(http.Controller):
                 partner_ids = request.env['res.partner'].sudo().create({'name': name, 'email': email}).ids
         # add or remove follower
         if is_follower:
-            record.check_access_rule('read')
             record.sudo().message_unsubscribe(partner_ids)
             return False
         else:
-            record.check_access_rule('read')
             # add partner to session
             request.session['partner_id'] = partner_ids[0]
             record.sudo().message_subscribe(partner_ids)
@@ -48,14 +51,13 @@ class WebsiteMail(http.Controller):
             'is_user': user != public_user,
             'email': partner.email if partner else "",
             'is_follower': False,
-            'alias_name': False,
         }
 
-        record = request.env[model].sudo().browse(int(res_id))
-        if record and partner:
-            values['is_follower'] = bool(request.env['mail.followers'].search_count([
+        record_sudo = request.env[model].sudo().browse(int(res_id))
+        if partner and record_sudo.exists():
+            values['is_follower'] = bool(request.env['mail.followers'].sudo().search_count([
                 ('res_model', '=', model),
-                ('res_id', '=', record.id),
+                ('res_id', '=', record_sudo.id),
                 ('partner_id', '=', partner.id)
             ]))
         return values
-- 
GitLab