[REF] mail: mail_thread: followers update

Followers can now be partners or channels. Partners following a document
will receive needaction, as previously. However people can follow documents
through channels. Members of a channel are able to listen to a stream
of messages using the channel. Those messages do not create needaction
messages. It is therefore possible to follow documents without receiving
too much notifications. For interesting documents subscribing with its
partner will create notification.

message_follower_ids fields is udpated. It is now a many2many to
mail.followers, not to res.partner anymore. A subscription can be either
a partner (partner_id) or a channel (channel_id).

Some access rules have been updated accordingly.
This commit is contained in:
Thibault Delavallée
2015-08-21 12:11:56 +02:00
parent 098fcb7e4e
commit e6f038a821
44 changed files with 344 additions and 281 deletions
+1 -1
View File
@@ -30,5 +30,5 @@ class lead_test(osv.Model):
def message_post(self, cr, uid, thread_id, body='', subject=None, message_type='notification', subtype=None, parent_id=False, attachments=None, context=None, **kwargs):
pass
def message_subscribe(self, cr, uid, ids, partner_ids, subtype_ids=None, context=None):
def message_subscribe(self, cr, uid, ids, partner_ids=None, channel_ids=None, subtype_ids=None, force=True, context=None):
pass
+10 -4
View File
@@ -1395,11 +1395,17 @@ class calendar_event(osv.Model):
del context['default_date']
return super(calendar_event, self).message_post(cr, uid, thread_id, context=context, **kwargs)
def message_subscribe(self, cr, uid, ids, partner_ids, subtype_ids=None, context=None):
return super(calendar_event, self).message_subscribe(cr, uid, get_real_ids(ids), partner_ids, subtype_ids=subtype_ids, context=context)
def message_subscribe(self, cr, uid, ids, partner_ids=None, channel_ids=None, subtype_ids=None, force=True, context=None):
return super(calendar_event, self).message_subscribe(
cr, uid, get_real_ids(ids),
partner_ids=partner_ids,
channel_ids=channel_ids,
subtype_ids=subtype_ids,
force=force,
context=context)
def message_unsubscribe(self, cr, uid, ids, partner_ids, context=None):
return super(calendar_event, self).message_unsubscribe(cr, uid, get_real_ids(ids), partner_ids, context=context)
def message_unsubscribe(self, cr, uid, ids, partner_ids=None, channel_ids=None, context=None):
return super(calendar_event, self).message_unsubscribe(cr, uid, get_real_ids(ids), partner_ids=partner_ids, channel_ids=channel_ids, context=context)
def do_sendmail(self, cr, uid, ids, context=None):
for event in self.browse(cr, uid, ids, context):
+4 -1
View File
@@ -240,8 +240,11 @@ class hr_evaluation(osv.Model):
def write(self, cr, uid, ids, vals, context=None):
if vals.get('employee_id'):
employee_id = self.pool.get('hr.employee').browse(cr, uid, vals.get('employee_id'), context=context)
# TDE FIXME: use add_command
if employee_id.parent_id and employee_id.parent_id.user_id:
vals['message_follower_ids'] = [(4, employee_id.parent_id.user_id.partner_id.id)]
message_follower_ids = vals.get('message_follower_ids') or [] # webclient can send None or False
message_follower_ids += self.env['mail.followers']._add_follower_command(self._name, ids, {employee_id.parent_id.user_id.partner_id.id: None}, {}, force=True)[0]
vals['message_follower_ids'] = message_follower_ids
if 'date' in vals:
new_vals = {'deadline': vals.get('date')}
obj_hr_eval_iterview = self.pool.get('hr.evaluation.interview')
+5 -3
View File
@@ -48,12 +48,14 @@ class MailController(http.Controller):
def read_followers(self, follower_ids):
result = []
is_editable = request.env.user.has_group('base.group_no_one')
for follower in request.env['res.partner'].browse(follower_ids):
for follower in request.env['mail.followers'].browse(follower_ids):
result.append({
'id': follower.id,
'name': follower.name,
'name': follower.partner_id.name or follower.channel_id.name,
'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': request.env.user.partner_id == follower,
'is_uid': request.env.user.partner_id == follower.partner_id,
})
return result
-9
View File
@@ -7,23 +7,14 @@
<field name="name">Best Sales Practices</field>
<field name="description">Discussion about best sales practices and deals.</field>
</record>
<record model="mail.channel" id="channel_1">
<field name="message_follower_ids" eval="[(6, 0, [ref('base.partner_demo')])]"/>
</record>
<record model="mail.channel" id="channel_2">
<field name="name">Board meetings</field>
<field name="description">Board meetings, budgets, strategic plans</field>
</record>
<record model="mail.channel" id="channel_2">
<field name="message_follower_ids" eval="[(6, 0, [])]"/>
</record>
<record model="mail.channel" id="channel_3">
<field name="name">R&amp;D</field>
<field name="description">Research and development discussion group</field>
</record>
<record model="mail.channel" id="channel_3">
<field name="message_follower_ids" eval="[(6, 0, [])]"/>
</record>
<!-- Best sales practices messages -->
<record id="mail_message_channel_1_1" model="mail.message">
+81 -2
View File
@@ -23,11 +23,87 @@ class Followers(models.Model):
res_id = fields.Integer(
'Related Document ID', select=1, help='Id of the followed resource')
partner_id = fields.Many2one(
'res.partner', string='Related Partner', ondelete='cascade', required=True, select=1)
'res.partner', string='Related Partner', ondelete='cascade', select=1)
channel_id = fields.Many2one(
'mail.channel', string='Listener', ondelete='cascade', select=1)
subtype_ids = fields.Many2many(
'mail.message.subtype', string='Subtype',
help="Message subtypes followed, meaning subtypes that will be pushed onto the user's Wall.")
@api.model
def _add_follower_command(self, res_model, res_ids, partner_data, channel_data, force=True):
""" Please upate me
:param force: if True, delete existing followers before creating new one
using the subtypes given in the parameters
"""
force_mode = force or (all(data for data in partner_data.values()) and all(data for data in channel_data.values()))
generic = []
specific = {}
existing = {} # {res_id: follower_ids}
p_exist = {} # {partner_id: res_ids}
c_exist = {} # {channel_id: res_ids}
followers = self.sudo().search([
'&',
'&', ('res_model', '=', res_model), ('res_id', 'in', res_ids),
'|', ('partner_id', 'in', partner_data.keys()), ('channel_id', 'in', channel_data.keys())])
if force_mode:
followers.unlink()
else:
for follower in followers:
existing.setdefault(follower.res_id, list()).append(follower)
if follower.partner_id:
p_exist.setdefault(follower.partner_id.id, list()).append(follower.res_id)
if follower.channel_id:
c_exist.setdefault(follower.channel_id.id, list()).append(follower.res_id)
default_subtypes = self.env['mail.message.subtype'].search([
('default', '=', True),
'|', ('res_model', '=', res_model), ('res_model', '=', False)])
if force_mode:
for pid, data in partner_data.iteritems():
if not data:
partner_data[pid] = default_subtypes.ids
for cid, data in channel_data.iteritems():
if not data:
channel_data[cid] = default_subtypes.ids
# create new followers, batch ok
gen_new_pids = [pid for pid in partner_data.keys() if pid not in p_exist]
gen_new_cids = [cid for cid in channel_data.keys() if cid not in c_exist]
for pid in gen_new_pids:
generic.append([0, 0, {'res_model': res_model, 'partner_id': pid, 'subtype_ids': [(6, 0, partner_data.get(pid) or default_subtypes.ids)]}])
for cid in gen_new_cids:
generic.append([0, 0, {'res_model': res_model, 'channel_id': cid, 'subtype_ids': [(6, 0, channel_data.get(cid) or default_subtypes.ids)]}])
# create new followers, each document at a time because of existing followers to avoid erasing
if not force_mode:
for res_id in res_ids:
command = []
doc_followers = existing.get(res_id, list())
new_pids = set(partner_data.keys()) - set([sub.partner_id.id for sub in doc_followers if sub.partner_id]) - set(gen_new_pids)
new_cids = set(channel_data.keys()) - set([sub.channel_id.id for sub in doc_followers if sub.channel_id]) - set(gen_new_cids)
# subscribe new followers
for new_pid in new_pids:
command.append((0, 0, {
'res_model': res_model,
'partner_id': new_pid,
'subtype_ids': [(6, 0, partner_data.get(new_pid) or default_subtypes.ids)],
}))
for new_cid in new_cids:
command.append((0, 0, {
'res_model': res_model,
'channel_id': new_cid,
'subtype_ids': [(6, 0, channel_data.get(new_cid) or default_subtypes.ids)],
}))
if command:
specific[res_id] = command
return generic, specific
#
# Modifying followers change access rights to individual documents. As the
# cache may contain accessible/inaccessible data, one has to refresh it.
@@ -50,7 +126,10 @@ class Followers(models.Model):
self.invalidate_cache()
return res
_sql_constraints = [('mail_followers_res_partner_res_model_id_uniq', 'unique(res_model,res_id,partner_id)', 'Error, a partner cannot follow twice the same object.')]
_sql_constraints = [
('mail_followers_res_partner_res_model_id_uniq', 'unique(res_model,res_id,partner_id)', 'Error, a partner cannot follow twice the same object.'),
('mail_followers_res_channel_res_model_id_uniq', 'unique(res_model,res_id,channel_id)', 'Error, a channel cannot follow twice the same object.'),
]
class Notification(models.Model):
+66 -108
View File
@@ -66,11 +66,16 @@ class MailThread(models.AbstractModel):
_mail_post_access = 'write' # access required on the document to post on it
_mail_mass_mailing = False # enable mass mailing on this model
message_is_follower = fields.Boolean('Is Follower', compute='_get_followers', search='_search_is_follower')
message_follower_ids = fields.Many2many(
comodel_name='res.partner', string='Followers',
inverse='_set_followers',
compute='_get_followers', search='_search_followers', type='many2many')
message_is_follower = fields.Boolean('Is Follower', compute='_compute_is_follower')
message_follower_ids = fields.One2many(
'mail.followers', 'res_id', string='Followers',
domain=lambda self: [('res_model', '=', self._name)])
message_partner_ids = fields.Many2many(
comodel_name='res.partner', string='Followers (Partners)',
compute='_get_followers', search='_search_follower_partners')
message_channel_ids = fields.Many2many(
comodel_name='mail.channel', string='Followers (Channels)',
compute='_get_followers', search='_search_follower_channels')
message_ids = fields.One2many(
'mail.message', 'res_id', string='Messages',
domain=lambda self: [('model', '=', self._name)], auto_join=True,
@@ -83,72 +88,45 @@ class MailThread(models.AbstractModel):
'Unread Messages', compute='_get_message_unread',
help="Number of unread messages")
@api.multi
@api.one
@api.depends('message_follower_ids')
def _get_followers(self):
res = dict.fromkeys(self.ids, self.env['res.partner'])
followers = self.env['mail.followers'].sudo().search([('res_model', '=', self._name), ('res_id', 'in', self.ids)])
for follower in followers:
res[follower.res_id] |= follower.partner_id
for record in self:
if not record.id:
continue
record.message_follower_ids = res[record.id]
record.message_is_follower = self.env.user.partner_id in record.message_follower_ids
@api.multi
def _set_followers(self):
# read the old set of followers, and determine the new set of followers
old = self.env['mail.followers'].sudo().search([('res_model', '=', self._name), ('res_id', 'in', self.ids)]).mapped('partner_id')
new = self.env['res.partner']
Partner = self.env['res.partner']
old_style_commands = self._fields['message_follower_ids'].convert_to_write(self.message_follower_ids)
for command in old_style_commands:
if command[0] == 0:
new |= Partner.create(command[2])
elif command[0] == 1:
partner = Partner.browse(command[1])
partner.write(command[2])
new |= partner
elif command[0] == 2:
partner = Partner.browse(command[1])
new -= partner
partner.unlink()
elif command[0] == 3:
new -= Partner.browse(command[1])
elif command[0] == 4:
new |= Partner.browse(command[1])
elif command[0] == 5:
new = self.env['res.partner']
elif command[0] == 6:
new = self.env['res.partner'].browse(command[2])
# remove partners that are no longer followers
self.message_unsubscribe((old-new).ids)
# add new followers
self.message_subscribe((new-old).ids)
self.message_partner_ids = self.message_follower_ids.mapped('partner_id')
self.message_channel_ids = self.message_follower_ids.mapped('channel_id')
@api.model
def _search_followers(self, operator, operand):
def _search_follower_partners(self, operator, operand):
"""Search function for message_follower_ids
Do not use with operator 'not in'. Use instead message_is_followers
"""
# TOFIX make it work with not in
assert operator != "not in", "Do not search message_follower_ids with 'not in'"
followers = self.env['mail.followers'].sudo().search([('res_model', '=', self._name), ('partner_id', operator, operand)])
followers = self.env['mail.followers'].sudo().search([
('res_model', '=', self._name),
('partner_id', operator, operand)])
return [('id', 'in', followers.mapped('res_id'))]
@api.model
def _search_is_follower(self, operator, operand):
"""Search function for message_is_follower"""
user_pid = self.env.user.partner_id.id
if (operator == '=' and operand) or (operator == '!=' and not operand): # is a follower
followers = self.env['mail.followers'].sudo().search([('res_model', '=', self._name), ('partner_id', '=', user_pid)])
return [('id', 'in', followers.mapped('res_id'))]
else: # is not a follower or unknown domain
followers = self.env['mail.followers'].sudo().search([('res_model', '=', self._name), ('partner_id', '=', user_pid)])
return [('id', 'not in', followers.mapped('res_id'))]
def _search_follower_channels(self, operator, operand):
"""Search function for message_follower_ids
Do not use with operator 'not in'. Use instead message_is_followers
"""
followers = self.env['mail.followers'].sudo().search([
('res_model', '=', self._name),
('channel_id', operator, operand)])
return [('id', 'in', followers.mapped('res_id'))]
@api.multi
@api.depends('message_follower_ids')
def _compute_is_follower(self):
followers = self.env['mail.followers'].sudo().search([
('res_model', '=', self._name),
('res_id', 'in', self.ids),
('partner_id', '=', self.env.user.partner_id.id),
])
following_ids = followers.mapped('res_id')
for record in self:
record.message_is_follower = record.id in following_ids
@api.multi
def _get_message_unread(self):
@@ -188,7 +166,7 @@ class MailThread(models.AbstractModel):
# subscribe uid unless asked not to
if not self._context.get('mail_create_nosubscribe'):
message_follower_ids = values.get('message_follower_ids') or [] # webclient can send None or False
message_follower_ids.append([4, self.env.user.partner_id.id])
message_follower_ids += self.env['mail.followers']._add_follower_command(self._name, [], {self.env.user.partner_id.id: None}, {}, force=True)[0]
values['message_follower_ids'] = message_follower_ids
thread = super(MailThread, self).create(values)
@@ -765,7 +743,7 @@ class MailThread(models.AbstractModel):
obj = record_set[0]
else:
obj = self.env[alias.alias_parent_model_id.model].browse(alias.alias_parent_thread_id)
if not author_id or author_id not in [fol.id for fol in obj.message_follower_ids]:
if not author_id or author_id not in [fol.id for fol in obj.message_partner_ids]:
_warn('alias %s restricted to internal followers, skipping' % alias.alias_name)
_create_bounce_email()
return False
@@ -1296,7 +1274,7 @@ class MailThread(models.AbstractModel):
partner = self.env['res.partner'].sudo().browse([partner_info['partner_id']])[0]
if email and email in [val[1] for val in result[self.ids[0]]]: # already existing email -> skip
return result
if partner and partner in self.message_follower_ids: # recipient already in the followers -> skip
if partner and partner in self.message_partner_ids: # recipient already in the followers -> skip
return result
if partner and partner.id in [val[0] for val in result[self.ids[0]]]: # already existing partner ID -> skip
return result
@@ -1339,8 +1317,8 @@ class MailThread(models.AbstractModel):
followers = self.env['res.partner']
if res_model and res_id:
record = self.env[res_model].browse(res_id)
if hasattr(record, 'message_follower_ids'):
followers = record.message_follower_ids
if hasattr(record, 'message_partner_ids'):
followers = record.message_partner_ids
Partner = self.env['res.partner'].sudo()
partner_ids = []
@@ -1601,16 +1579,18 @@ class MailThread(models.AbstractModel):
return result
@api.multi
def message_subscribe(self, partner_ids, subtype_ids=None):
def message_subscribe(self, partner_ids=None, channel_ids=None, subtype_ids=None, force=True):
""" Add partners to the records followers. """
# not necessary for computation, but saves an access right check
if not partner_ids:
if not partner_ids and not channel_ids:
return True
if partner_ids is None:
partner_ids = []
if channel_ids is None:
channel_ids = []
Followers = self.env['mail.followers']
Subtype = self.env['mail.message.subtype']
if set(partner_ids) == set([self.env.user.partner_id.id]):
# TDE CHECK THIS
if not channel_ids and partner_ids and set(partner_ids) == set([self.env.user.partner_id.id]):
try:
self.check_access_rights('read')
self.check_access_rule('read')
@@ -1620,38 +1600,14 @@ class MailThread(models.AbstractModel):
self.check_access_rights('write')
self.check_access_rule('write')
existing_pids_dict = dict.fromkeys(self.ids, self.env['res.partner'])
followers = Followers.sudo().search([
'&', '&',
('res_model', '=', self._name),
('res_id', 'in', self.ids),
('partner_id', 'in', partner_ids)])
for follower in followers:
existing_pids_dict[follower.res_id] |= follower.partner_id
# subtype_ids specified: update already subscribed partners
if subtype_ids and followers:
followers.write({'subtype_ids': [(6, 0, subtype_ids)]})
# subtype_ids not specified: do not update already subscribed partner, fetch default subtypes for new partners
if subtype_ids is None:
subtype_ids = Subtype.search([
('default', '=', True),
'|',
('res_model', '=', self._name),
('res_model', '=', False)]).ids
for record in self:
existing_pids = existing_pids_dict[record.id]
new_partners = self.env['res.partner'].browse(partner_ids) - existing_pids
# subscribe new followers
for new_partner in new_partners:
Followers.sudo().create({
'res_model': self._name,
'res_id': record.id,
'partner_id': new_partner.id,
'subtype_ids': [(6, 0, subtype_ids)]})
partner_data = dict((pid, subtype_ids) for pid in partner_ids)
channel_data = dict((cid, subtype_ids) for cid in channel_ids)
gen, part = self.env['mail.followers']._add_follower_command(self._name, self.ids, partner_data, channel_data, force=force)
self.sudo().write({'message_follower_ids': gen})
for record in self.filtered(lambda self: self.id in part):
record.write(part[record.id])
self.invalidate_cache()
return True
@api.multi
@@ -1667,13 +1623,13 @@ class MailThread(models.AbstractModel):
return result
@api.multi
def message_unsubscribe(self, partner_ids):
def message_unsubscribe(self, partner_ids=None, channel_ids=None):
""" Remove partners from the records followers. """
# not necessary for computation, but saves an access right check
if not partner_ids:
if not partner_ids and not channel_ids:
return True
user_pid = self.env.user.partner_id.id
if set(partner_ids) == set([user_pid]):
if not channel_ids and set(partner_ids) == set([user_pid]):
self.check_access_rights('read')
self.check_access_rule('read')
else:
@@ -1682,7 +1638,9 @@ class MailThread(models.AbstractModel):
self.env['mail.followers'].sudo().search([
('res_model', '=', self._name),
('res_id', 'in', self.ids),
('partner_id', 'in', partner_ids)
'|',
('partner_id', 'in', partner_ids or []),
('channel_id', 'in', channel_ids or [])
]).unlink()
@api.model
@@ -1808,7 +1766,7 @@ class MailThread(models.AbstractModel):
for pid, subtypes in new_followers.items():
subtypes = list(subtypes) if subtypes is not None else None
self.message_subscribe([pid], subtypes)
self.message_subscribe([pid], subtype_ids=subtypes)
self._message_auto_subscribe_notify(user_pids)
+1 -1
View File
@@ -121,7 +121,7 @@ class Users(models.Model):
def message_update(self, msg_dict, update_vals=None):
return True
def message_subscribe(self, partner_ids, subtype_ids=None):
def message_subscribe(self, partner_ids=None, channel_ids=None, subtype_ids=None, force=True):
return True
@api.cr_uid_context
+2 -2
View File
@@ -7,8 +7,8 @@ access_mail_mail_portal,mail.mail.portal,model_mail_mail,base.group_portal,1,1,1
access_mail_mail_user,mail.mail.user,model_mail_mail,base.group_user,1,1,1,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,0,0
access_mail_followers_user,mail.followers.user,model_mail_followers,base.group_user,1,1,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_system,mail.followers.system,model_mail_followers,base.group_system,1,1,1,1
access_mail_notification_all,mail.notification.all,model_mail_notification,,1,0,0,0
access_mail_notification_portal,mail.notification.portal,model_mail_notification,base.group_portal,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
7 access_mail_mail_user mail.mail.user model_mail_mail base.group_user 1 1 1 0
8 access_mail_mail_system mail.mail.system model_mail_mail base.group_system 1 1 1 1
9 access_mail_followers_all mail.followers.all model_mail_followers 1 0 0 0
10 access_mail_followers_portal mail.followers.portal model_mail_followers base.group_portal 1 1 0 1 0
11 access_mail_followers_user mail.followers.user model_mail_followers base.group_user 1 1 0 1 0
12 access_mail_followers_system mail.followers.system model_mail_followers base.group_system 1 1 1 1
13 access_mail_notification_all mail.notification.all model_mail_notification 1 0 0 0
14 access_mail_notification_portal mail.notification.portal model_mail_notification base.group_portal 1 1 1 0
+3 -2
View File
@@ -7,17 +7,18 @@
<field name="name">Mail.channel: access only public and joined groups</field>
<field name="model_id" ref="model_mail_channel"/>
<!-- This rule has to be improved for employee only groups -->
<field name="domain_force">['|', '|', ('public', '=', 'public'), ('message_follower_ids', 'in', [user.partner_id.id]), '&amp;', ('public','=','groups'), ('group_public_id','in', [g.id for g in user.groups_id])]</field>
<field name="domain_force">['|', '|', ('public', '=', 'public'), ('message_partner_ids', 'in', [user.partner_id.id]), '&amp;', ('public','=','groups'), ('group_public_id','in', [g.id for g in user.groups_id])]</field>
<field name="perm_create" eval="False"/>
</record>
<record id="mail_followers_read_write_own" model="ir.rule">
<field name="name">mail.followers: read and write its own entries</field>
<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'))]"/>
<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>
<!-- If technical rights then read and write others subscriptions -->
+32 -36
View File
@@ -77,7 +77,7 @@ var Followers = form_common.AbstractField.extend({
self.do_follow();
}
else {
self.do_unfollow();
self.do_unfollow([session.uid]);
}
});
@@ -145,15 +145,10 @@ var Followers = form_common.AbstractField.extend({
},
on_remove_follower: function (event) {
var partner_id = $(event.target).data('id');
var name = $(event.target).parent().find("a").html();
if (confirm(_.str.sprintf(_t("Warning! \n %s won't be notified of any email or discussion on this document. Do you really want to remove him from the followers ?"), name))) {
var context = new data.CompoundContext(this.build_context(), {});
return this.ds_model.call('message_unsubscribe', [[this.view.datarecord.id],
[partner_id],
context])
.then(this.proxy('read_value'));
}
var res_model = $(event.target).parent().find('a').data('res-model');
var res_id = $(event.target).parent().find('a').data('res-id');
if (res_model == 'res.partner') { return this.do_unfollow(undefined, [res_id], undefined); }
else { return this.do_unfollow(undefined, undefined, [res_id]); }
},
on_follower_clicked: function (event) {
@@ -245,19 +240,13 @@ var Followers = form_common.AbstractField.extend({
// truncate number of displayed followers
_(this.followers).each(function (record) {
var partner = {
'id': record['id'],
'name': record['name'],
'is_uid': record['is_uid'],
'is_editable': record['is_editable'],
'avatar_url': mail_utils.get_image(session, 'res.partner', 'image_small', record['id']),
}
$(qweb.render('mail_followers_partner', {'record': partner, 'widget': self}))
.appendTo(node_user_list);
$(qweb.render('mail_followers_partner', {
'record': _.extend(record, {'avatar_url': mail_utils.get_image(session, record['res_model'], 'image_small', record['id'])}),
'widget': self})
).appendTo(node_user_list);
// On mouse-enter it will show the edit_subtype pencil.
if (partner.is_editable) {
if (record.is_editable) {
self.$('.o_timeline_follower_list').on('mouseenter mouseleave', function(e) {
self.$('.o_timeline_edit_subtype')
.toggleClass('oe_hidden', e.type == 'mouseleave');
@@ -358,26 +347,33 @@ var Followers = form_common.AbstractField.extend({
$(record).attr('checked', 'checked');
});
},
do_unfollow: function (user_pid) {
if (confirm(_t("Warning! \nYou won't be notified of any email or discussion on this document. Do you really want to unfollow this document ?"))) {
do_unfollow: function (user_ids, partner_ids, channel_ids) {
if (confirm(_t("Warning! \n If you remove a follower, he won't be notified of any email or discussion on this document. Do you really want to remove this follower ?"))) {
_(this.$('.o_timeline_msg_subtype_check')).each(function (record) {
$(record).attr('checked',false);
});
var action_unsubscribe = 'message_unsubscribe_users';
this.$('.o_timeline_subtype_list > .dropdown-toggle').attr('disabled', true);
var follower_ids = [session.uid];
if (user_pid) {
action_unsubscribe = 'message_unsubscribe';
follower_ids = [user_pid];
}
var context = new data.CompoundContext(this.build_context(), {});
return this.ds_model.call(action_unsubscribe, [[this.view.datarecord.id],
follower_ids, context])
.then(this.proxy('read_value'));
if (partner_ids || channel_ids) {
return this.ds_model.call(
'message_unsubscribe', [
[this.view.datarecord.id],
partner_ids,
channel_ids,
context]
).then(this.proxy('read_value'));
}
else {
return this.ds_model.call(
'message_unsubscribe_users', [
[this.view.datarecord.id],
user_ids,
context]
).then(this.proxy('read_value'));
}
}
return false;
},
@@ -404,7 +400,7 @@ var Followers = form_common.AbstractField.extend({
});
if (!checklist.length) {
if (!this.do_unfollow(user_pid)) {
if (!this.do_unfollow(undefined, [user_pid], undefined)) {
$(event.target).attr("checked", "checked");
}
else {
@@ -42,14 +42,17 @@
<span> <img class="oe_mail_thumbnail oe_mail_frame" t-att-src="record.avatar_url"/> </span>
<span class="oe_partner_name">
<a t-attf-href="#model=res.partner&amp;id=#{record.id}"
t-att-title="record.name" t-att-data-partner="record.id">
<t t-esc="record.name"/> </a>
t-att-title="record.name"
t-att-data-res-model="record.res_model"
t-att-data-res-id="record.res_id"
t-att-data-id="record.id">
<t t-esc="record.name"/></a>
</span>
<i t-if="record.is_editable and (widget.records_length &gt; 1)"
class="oe_edit_subtype fa fa-pencil oe_hidden" title="Edit subscription"
t-att-data-id="record.id"/>
<i t-if="widget.view_is_editable" class="oe_remove_follower fa fa-remove"
title="Remove this follower" t-att-data-id="record.id"/>
title="Remove this follower"/>
</li>
<t t-name="mail_followers_add_more">
@@ -66,7 +69,7 @@
<t t-name="mail_followers_subtype">
<li class="oe_subtype">
<input type="checkbox" t-att-checked="record.followed"
t-att-id="'input_mail_followers_subtype_'+record.id+(dialog ? '_in_dialog': '')"
t-att-id="'input_mail_followers_subtype_'+record.id+(dialog ? '_in_dialog': '')"
t-att-data-id="record.id" t-att-name="record.name" class="o_timeline_msg_subtype_check"/>
<label t-att-for="'input_mail_followers_subtype_'+record.id+(dialog ? '_in_dialog': '')">
+4 -1
View File
@@ -17,9 +17,12 @@ class TestInvite(TestMail):
mail_invite.add_followers()
# Test: Pigs followers should contain Admin, Bert
self.assertEqual(self.group_pigs.message_follower_ids,
self.assertEqual(self.group_pigs.message_partner_ids,
self.user_portal.partner_id | self.partner_1,
'invite wizard: Pigs followers after invite is incorrect, should be Admin + added follower')
self.assertEqual(self.group_pigs.message_follower_ids.mapped('channel_id'),
self.env['mail.channel'],
'invite wizard: Pigs followers after invite is incorrect, should not have channels')
# Test: (pretend to) send email and check subject, body
self.assertEqual(len(self._mails), 2, 'invite wizard: sent email number incorrect, should be only for Bert')
+1 -1
View File
@@ -81,7 +81,7 @@ class TestMailGroup(TestMail):
trigger_read = chell_pigs.name
for message in chell_pigs.message_ids:
trigger_read = message.subject
for partner in chell_pigs.message_follower_ids:
for partner in chell_pigs.message_partner_ids:
if partner.id == self.user_portal.partner_id.id:
# Chell can read her own partner record
continue
+15 -10
View File
@@ -160,38 +160,43 @@ class TestMessagePost(TestMail):
@mute_logger('openerp.addons.mail.models.mail_mail')
def test_post_no_subscribe_author(self):
original_followers = self.group_pigs.message_follower_ids
original = self.group_pigs.message_follower_ids
self.group_pigs.sudo(self.user_employee).with_context({'mail_create_nosubscribe': True}).message_post(
body='Test Body', message_type='comment', subtype='mt_comment')
self.assertEqual(self.group_pigs.message_follower_ids, original_followers)
self.assertEqual(self.group_pigs.message_follower_ids.mapped('partner_id'), original.mapped('partner_id'))
self.assertEqual(self.group_pigs.message_follower_ids.mapped('channel_id'), original.mapped('channel_id'))
@mute_logger('openerp.addons.mail.models.mail_mail')
def test_post_subscribe_author(self):
original_followers = self.group_pigs.message_follower_ids
original = self.group_pigs.message_follower_ids
self.group_pigs.sudo(self.user_employee).message_post(
body='Test Body', message_type='comment', subtype='mt_comment')
self.assertEqual(self.group_pigs.message_follower_ids, original_followers | self.user_employee.partner_id)
self.assertEqual(self.group_pigs.message_follower_ids.mapped('partner_id'), original.mapped('partner_id') | self.user_employee.partner_id)
self.assertEqual(self.group_pigs.message_follower_ids.mapped('channel_id'), original.mapped('channel_id'))
@mute_logger('openerp.addons.mail.models.mail_mail')
def test_post_no_subscribe_recipients(self):
original_followers = self.group_pigs.message_follower_ids
original = self.group_pigs.message_follower_ids
self.group_pigs.sudo(self.user_employee).with_context({'mail_create_nosubscribe': True}).message_post(
body='Test Body', message_type='comment', subtype='mt_comment', partner_ids=[(4, self.partner_1.id), (4, self.partner_2.id)])
self.assertEqual(self.group_pigs.message_follower_ids, original_followers)
self.assertEqual(self.group_pigs.message_follower_ids.mapped('partner_id'), original.mapped('partner_id'))
self.assertEqual(self.group_pigs.message_follower_ids.mapped('channel_id'), original.mapped('channel_id'))
@mute_logger('openerp.addons.mail.models.mail_mail')
def test_post_subscribe_recipients(self):
original_followers = self.group_pigs.message_follower_ids
original = self.group_pigs.message_follower_ids
self.group_pigs.sudo(self.user_employee).with_context({'mail_create_nosubscribe': True, 'mail_post_autofollow': True}).message_post(
body='Test Body', message_type='comment', subtype='mt_comment', partner_ids=[(4, self.partner_1.id), (4, self.partner_2.id)])
self.assertEqual(self.group_pigs.message_follower_ids, original_followers | self.partner_1 | self.partner_2)
self.assertEqual(self.group_pigs.message_follower_ids.mapped('partner_id'), original.mapped('partner_id') | self.partner_1 | self.partner_2)
self.assertEqual(self.group_pigs.message_follower_ids.mapped('channel_id'), original.mapped('channel_id'))
@mute_logger('openerp.addons.mail.models.mail_mail')
def test_post_subscribe_recipients_partial(self):
original_followers = self.group_pigs.message_follower_ids
original = self.group_pigs.message_follower_ids
self.group_pigs.sudo(self.user_employee).with_context({'mail_create_nosubscribe': True, 'mail_post_autofollow': True, 'mail_post_autofollow_partner_ids': [self.partner_2.id]}).message_post(
body='Test Body', message_type='comment', subtype='mt_comment', partner_ids=[(4, self.partner_1.id), (4, self.partner_2.id)])
self.assertEqual(self.group_pigs.message_follower_ids, original_followers | self.partner_2)
self.assertEqual(self.group_pigs.message_follower_ids.mapped('partner_id'), original.mapped('partner_id') | self.partner_2)
self.assertEqual(self.group_pigs.message_follower_ids.mapped('channel_id'), original.mapped('channel_id'))
@mute_logger('openerp.addons.mail.models.mail_mail')
def test_post_notifications(self):
+47 -30
View File
@@ -15,41 +15,55 @@ class TestMailFollowers(TestMail):
self.mt_al_nodef = Subtype.create({'name': 'mt_al_nodef', 'default': False, 'res_model': False})
self.default_group_subtypes = Subtype.search([('default', '=', True), '|', ('res_model', '=', 'mail.channel'), ('res_model', '=', False)])
def test_message_follower_ids_commands(self):
# Create some dummy data on other groups to ensure they are not altered
group_dummy = self.env['mail.channel'].with_context({'mail_create_nolog': True}).create({'name': 'Dummy group'})
self.env['mail.followers'].create({'res_model': 'mail.thread', 'res_id': group_dummy.id, 'partner_id': self.partner_1.id})
def test_m2o_command_new(self):
test_channel = self.env['mail.channel'].create({'name': 'Test'})
groups = self.group_pigs | self.group_public
generic, specific = self.env['mail.followers']._add_follower_command(
'mail.channel', groups.ids,
{self.user_employee.partner_id.id: [self.mt_mg_nodef.id]},
{test_channel.id: [self.mt_al_nodef.id]})
self.assertFalse(specific)
self.assertEqual(len(generic), 2)
# Add 2 followers through the (4, ID) command
self.group_pigs.write({'message_follower_ids': [(4, self.partner_1.id), (4, self.partner_2.id)]})
self.assertEqual(self.group_pigs.message_follower_ids, self.partner_1 | self.partner_2)
self.assertEqual(set([generic[0][2]['res_model'], generic[1][2]['res_model']]),
set(['mail.channel']))
self.assertEqual(set(filter(None, [generic[0][2].get('channel_id'), generic[1][2].get('channel_id')])),
set([test_channel.id]))
self.assertEqual(set(filter(None, [generic[0][2].get('partner_id'), generic[1][2].get('partner_id')])),
set([self.user_employee.partner_id.id]))
self.assertEqual(set(generic[0][2]['subtype_ids'][0][2] + generic[1][2]['subtype_ids'][0][2]),
set([self.mt_mg_nodef.id, self.mt_al_nodef.id]))
# Remove 1 follower through the (3, ID) command
self.group_pigs.write({'message_follower_ids': [(3, self.partner_2.id)]})
self.assertEqual(self.group_pigs.message_follower_ids, self.partner_1)
def test_m2o_command_update_selective(self):
test_channel = self.env['mail.channel'].create({'name': 'Test'})
groups = self.group_pigs | self.group_public
self.env['mail.followers'].create({'partner_id': self.user_employee.partner_id.id, 'res_model': 'mail.channel', 'res_id': self.group_pigs.id})
generic, specific = self.env['mail.followers']._add_follower_command(
'mail.channel', groups.ids,
{self.user_employee.partner_id.id: [self.mt_mg_nodef.id]},
{test_channel.id: False},
force=False)
self.assertEqual(len(generic), 1)
self.assertEqual(len(specific), 1)
# Set followers through the (6, 0, IDs) command
self.group_pigs.write({'message_follower_ids': [(6, 0, [self.partner_2.id, self.env.user.partner_id.id])]})
self.assertEqual(self.group_pigs.message_follower_ids,
self.partner_2 | self.env.user.partner_id)
self.assertEqual(generic[0][2]['res_model'], 'mail.channel')
self.assertEqual(generic[0][2]['channel_id'], test_channel.id)
self.assertEqual(set(generic[0][2]['subtype_ids'][0][2]), set(self.default_group_subtypes.ids))
# Add 1 follower through the (0, 0, values) command
self.group_pigs.write({'message_follower_ids': [(0, 0, {'name': 'Patrick Fiori'})]})
partner_patrick = self.env['res.partner'].search([('name', '=', 'Patrick Fiori')], limit=1)
self.assertEqual(self.group_pigs.message_follower_ids,
self.partner_2 | self.env.user.partner_id | partner_patrick)
self.assertEqual(specific.keys(), [self.group_public.id])
self.assertEqual(specific[self.group_public.id][0][2]['res_model'], 'mail.channel')
self.assertEqual(specific[self.group_public.id][0][2]['partner_id'], self.user_employee.partner_id.id)
self.assertEqual(set(specific[self.group_public.id][0][2]['subtype_ids'][0][2]), set([self.mt_mg_nodef.id]))
# Remove all followers through a (5, 0) command
self.group_pigs.write({'message_follower_ids': [(5, 0)]})
self.assertFalse(self.group_pigs.message_follower_ids)
# Test dummy data has not been altered
followers = self.env['mail.followers'].search([('res_model', '=', 'mail.thread'), ('res_id', '=', group_dummy.id)])
self.assertEqual(followers.mapped('partner_id'), self.partner_1, 'dummy data altered')
def test_message_is_follower(self):
self.assertFalse(self.group_pigs.sudo(self.user_employee).message_is_follower)
self.group_pigs.message_subscribe_users(user_ids=[self.user_employee.id])
self.assertTrue(self.group_pigs.sudo(self.user_employee).message_is_follower)
def test_followers_subtypes_default(self):
self.group_pigs.message_subscribe_users(user_ids=[self.user_employee.id])
self.assertEqual(self.group_pigs.message_follower_ids, self.user_employee.partner_id)
self.assertEqual(self.group_pigs.message_follower_ids.mapped('partner_id'), self.user_employee.partner_id)
self.assertEqual(self.group_pigs.message_follower_ids.mapped('channel_id'), self.env['mail.channel'])
follower = self.env['mail.followers'].search([
('res_model', '=', 'mail.channel'),
('res_id', '=', self.group_pigs.id),
@@ -59,7 +73,8 @@ class TestMailFollowers(TestMail):
def test_followers_subtypes_specified(self):
self.group_pigs.sudo(self.user_employee).message_subscribe_users(subtype_ids=[self.mt_mg_nodef.id])
self.assertEqual(self.group_pigs.message_follower_ids, self.user_employee.partner_id)
self.assertEqual(self.group_pigs.message_follower_ids.mapped('partner_id'), self.user_employee.partner_id)
self.assertEqual(self.group_pigs.message_follower_ids.mapped('channel_id'), self.env['mail.channel'])
follower = self.env['mail.followers'].search([
('res_model', '=', 'mail.channel'),
('res_id', '=', self.group_pigs.id),
@@ -69,7 +84,8 @@ class TestMailFollowers(TestMail):
def test_followers_multiple_subscription(self):
self.group_pigs.sudo(self.user_employee).message_subscribe_users(subtype_ids=[self.mt_mg_nodef.id])
self.assertEqual(self.group_pigs.message_follower_ids, self.user_employee.partner_id)
self.assertEqual(self.group_pigs.message_follower_ids.mapped('partner_id'), self.user_employee.partner_id)
self.assertEqual(self.group_pigs.message_follower_ids.mapped('channel_id'), self.env['mail.channel'])
follower = self.env['mail.followers'].search([
('res_model', '=', 'mail.channel'),
('res_id', '=', self.group_pigs.id),
@@ -78,7 +94,8 @@ class TestMailFollowers(TestMail):
self.assertEqual(follower.subtype_ids, self.mt_mg_nodef)
self.group_pigs.sudo(self.user_employee).message_subscribe_users(subtype_ids=[self.mt_mg_nodef.id, self.mt_al_nodef.id])
self.assertEqual(self.group_pigs.message_follower_ids, self.user_employee.partner_id)
self.assertEqual(self.group_pigs.message_follower_ids.mapped('partner_id'), self.user_employee.partner_id)
self.assertEqual(self.group_pigs.message_follower_ids.mapped('channel_id'), self.env['mail.channel'])
follower = self.env['mail.followers'].search([
('res_model', '=', 'mail.channel'),
('res_id', '=', self.group_pigs.id),
+3 -3
View File
@@ -278,7 +278,7 @@ class TestMailgateway(TestMail):
self.assertIn('test.sylvie.lelitre@agrolait.com', new_groups.message_ids[0].email_from,
'message_process: unrecognized email -> email_from')
self.assertEqual(len(new_groups.message_follower_ids), 0,
self.assertEqual(len(new_groups.message_partner_ids), 0,
'message_process: newly create group should not have any follower')
@mute_logger('openerp.addons.mail.models.mail_thread', 'openerp.models')
@@ -291,7 +291,7 @@ class TestMailgateway(TestMail):
self.assertIn('Valid Lelitre <valid.lelitre@agrolait.com>', new_groups.message_ids[0].email_from,
'message_process: recognized email -> email_from')
self.assertEqual(new_groups.message_follower_ids, self.partner_1,
self.assertEqual(new_groups.message_partner_ids, self.partner_1,
'message_process: recognized email -> added as follower')
self.assertEqual(len(self._mails), 0,
@@ -380,7 +380,7 @@ class TestMailgateway(TestMail):
self.assertIn('valid.lelitre@agrolait.com', self._mails[0].get('email_to')[0],
'message_process: email should be sent to Sylvie')
# Test: author (and not recipient) added as follower
self.assertEqual(self.group_public.message_follower_ids, self.partner_1 | self.partner_2,
self.assertEqual(self.group_public.message_partner_ids, self.partner_1 | self.partner_2,
'message_process: after reply, group should have 2 followers')
@mute_logger('openerp.addons.mail.models.mail_thread', 'openerp.models')
+5 -3
View File
@@ -27,6 +27,7 @@ class Invite(models.TransientModel):
res_model = fields.Char('Related Document Model', required=True, select=1, help='Model of the followed resource')
res_id = fields.Integer('Related Document ID', select=1, help='Id of the followed resource')
partner_ids = fields.Many2many('res.partner', string='Recipients', help="List of partners that will be added as follower of the current document.")
channel_ids = fields.Many2many('mail.channel', string='Channels', help='List of channels that will be added as listeners of the current document.')
message = fields.Html('Message')
send_mail = fields.Boolean('Send Email', default=True, help="If checked, the partners will receive an email warning they have been added in the document's followers.")
@@ -38,8 +39,9 @@ class Invite(models.TransientModel):
document = Model.browse(wizard.res_id)
# filter partner_ids to get the new followers, to avoid sending email to already following partners
new_followers = wizard.partner_ids - document.message_follower_ids
document.message_subscribe(new_followers.ids)
new_partners = wizard.partner_ids - document.message_partner_ids
new_channels = wizard.channel_ids - document.message_channel_ids
document.message_subscribe(new_partners.ids, new_channels.ids)
model_ids = self.env['ir.model'].search([('model', '=', wizard.res_model)])
model_name = model_ids.name_get()[0][1]
@@ -62,5 +64,5 @@ class Invite(models.TransientModel):
'body_html': '%s' % wizard.message,
'auto_delete': True,
'message_id': self.env['mail.message']._get_message_id({'no_auto_thread': True}),
'recipient_ids': [(4, id) for id in new_followers.ids]}).send()
'recipient_ids': [(4, id) for id in new_partners.ids]}).send()
return {'type': 'ir.actions.act_window_close'}
+2
View File
@@ -14,6 +14,8 @@
<field name="partner_ids" widget="many2many_tags_email"
placeholder="Add contacts to notify..."
context="{'force_email':True, 'show_email':True}"/>
<field name="channel_ids" widget="many2many_tags"
placeholder="Add channels to notify..."/>
<field name="send_mail"/>
<field name="message" attrs="{'invisible': [('send_mail','!=',True)]}" options="{'style-inline': true}" class="test_message"/>
</group>
-4
View File
@@ -36,7 +36,6 @@
</field>
<field name="stage_id" ref="demo_note_stage_01"/>
<field name="color">2</field>
<field name="message_follower_ids" eval="[(4, ref('base.group_user'))]"/>
</record>
<record id="note_2" model="note.note">
@@ -61,7 +60,6 @@
]]>
</field>
<field name="stage_id" ref="demo_note_stage_02"/>
<field name="message_follower_ids" eval="[(4, ref('base.group_user'))]"/>
</record>
<record id="note_6" model="note.note">
@@ -93,7 +91,6 @@
</field>
<field name="stage_id" ref="note_stage_03"/>
<field name="color">3</field>
<field name="message_follower_ids" eval="[(4, ref('base.group_user'))]"/>
</record>
<record id="note_9" model="note.note">
@@ -111,7 +108,6 @@
<br/>* Odoo for Retail and Industrial Management
]]>
</field>
<field name="message_follower_ids" eval="[(4, ref('base.group_user'))]"/>
<field name="color">7</field>
<field name="stage_ids" eval="['note_stage_03','demo_note_stage_04']"/>
</record>
+1 -1
View File
@@ -4,7 +4,7 @@
<record id="note_note_rule_global" model="ir.rule">
<field name="name">Only followers can access a sticky notes</field>
<field name="model_id" ref="model_note_note"/>
<field name="domain_force">['|', ('user_id', '=', user.id), ('message_follower_ids', '=', user.partner_id.id)]</field>
<field name="domain_force">['|', ('user_id', '=', user.id), ('message_partner_ids', '=', user.partner_id.id)]</field>
<field name="global" eval="True"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
+1 -1
View File
@@ -47,7 +47,7 @@ class test_portal(TestMail):
'partner_ids': [(4, partner_carine.id)], 'send_mail': True})
mail_invite.add_followers()
# Test: Pigs followers should contain Admin and Bert
self.assertEqual(group_pigs.message_follower_ids, partner_carine)
self.assertEqual(group_pigs.message_partner_ids, partner_carine)
# Test: partner must have been prepared for signup
self.assertTrue(partner_carine.signup_valid, 'partner has not been prepared for signup')
self.assertTrue(base_url in partner_carine.signup_url, 'signup url is incorrect')
+2 -2
View File
@@ -10,10 +10,10 @@ class mail_mail(osv.osv):
order = so_obj.browse(cr, uid, mail.res_id, context=context)
partner = order.partner_id
# Add the customer in the SO as follower
if partner not in order.message_follower_ids:
if partner not in order.message_partner_ids:
so_obj.message_subscribe(cr, uid, [mail.res_id], [partner.id], context=context)
# Add all recipients of the email as followers
for p in mail.partner_ids:
if p not in order.message_follower_ids:
if p not in order.message_partner_ids:
so_obj.message_subscribe(cr, uid, [mail.res_id], [p.id], context=context)
return super(mail_mail, self)._postprocess_sent_message(cr, uid, mail=mail, context=context, mail_sent=mail_sent)
+2 -2
View File
@@ -44,7 +44,7 @@ class sale_order(osv.Model):
assert len(ids) == 1
document = self.browse(cr, uid, ids[0], context=context)
partner = document.partner_id
if partner not in document.message_follower_ids:
if partner not in document.message_partner_ids:
self.message_subscribe(cr, uid, ids, [partner.id], context=context)
return super(sale_order, self).action_button_confirm(cr, uid, ids, context=context)
@@ -103,7 +103,7 @@ class account_invoice(osv.Model):
# fetch the partner's id and subscribe the partner to the invoice
for invoice in self.browse(cr, uid, ids, context=context):
partner = invoice.partner_id
if partner not in invoice.message_follower_ids:
if partner not in invoice.message_partner_ids:
self.message_subscribe(cr, uid, [invoice.id], [partner.id], context=context)
return super(account_invoice, self).invoice_validate(cr, uid, ids, context=context)
@@ -18,7 +18,7 @@ their documents through the portal.</field>
<record id="portal_sale_order_user_rule" model="ir.rule">
<field name="name">Portal Personal Quotations/Sales Orders</field>
<field name="model_id" ref="sale.model_sale_order"/>
<field name="domain_force">[('message_follower_ids','child_of',[user.commercial_partner_id.id])]</field>
<field name="domain_force">[('message_partner_ids','child_of',[user.commercial_partner_id.id])]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
<field eval="1" name="perm_unlink"/>
<field eval="1" name="perm_write"/>
@@ -29,7 +29,7 @@ their documents through the portal.</field>
<record id="portal_sale_order_line_rule" model="ir.rule">
<field name="name">Portal Sales Orders Line</field>
<field name="model_id" ref="sale.model_sale_order_line"/>
<field name="domain_force">[('order_id.message_follower_ids','child_of',[user.commercial_partner_id.id])]</field>
<field name="domain_force">[('order_id.message_partner_ids','child_of',[user.commercial_partner_id.id])]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
@@ -37,21 +37,21 @@ their documents through the portal.</field>
<record id="portal_account_invoice_user_rule" model="ir.rule">
<field name="name">Portal Personal Account Invoices</field>
<field name="model_id" ref="account.model_account_invoice"/>
<field name="domain_force">[('message_follower_ids','child_of',[user.commercial_partner_id.id])]</field>
<field name="domain_force">[('message_partner_ids','child_of',[user.commercial_partner_id.id])]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
<record id="portal_account_invoice_line_rule" model="ir.rule">
<field name="name">Portal Invoice Lines</field>
<field name="model_id" ref="account.model_account_invoice_line"/>
<field name="domain_force">[('invoice_id.message_follower_ids','child_of',[user.commercial_partner_id.id])]</field>
<field name="domain_force">[('invoice_id.message_partner_ids','child_of',[user.commercial_partner_id.id])]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
<record id="portal_personal_contact" model="ir.rule">
<field name="name">Portal Personal Contacts</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="domain_force">[('message_follower_ids','in',[user.commercial_partner_id.id])]</field>
<field name="domain_force">[('message_partner_ids','in',[user.commercial_partner_id.id])]</field>
<field eval="0" name="perm_read"/>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
@@ -6,14 +6,14 @@
<record id="portal_stock_picking_portal_user_rule" model="ir.rule">
<field name="name">Portal Follower Pickings</field>
<field name="model_id" ref="stock.model_stock_picking"/>
<field name="domain_force">['|', ('message_follower_ids','in',[user.partner_id.id]), ('partner_id.id','=',user.partner_id.id)]</field>
<field name="domain_force">['|', ('message_partner_ids','in',[user.partner_id.id]), ('partner_id.id','=',user.partner_id.id)]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
<record id="portal_stock_pack_operation_portal_user_rule" model="ir.rule">
<field name="name">Portal Follower Pack Operations</field>
<field name="model_id" ref="stock.model_stock_pack_operation"/>
<field name="domain_force">['|', ('picking_id.message_follower_ids','in',[user.partner_id.id]), ('picking_id.partner_id.id','=',user.partner_id.id)]</field>
<field name="domain_force">['|', ('picking_id.message_partner_ids','in',[user.partner_id.id]), ('picking_id.partner_id.id','=',user.partner_id.id)]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
</data>
@@ -13,7 +13,7 @@ class account_invoice(osv.Model):
if invoice.type != 'out_invoice':
continue
# subscribe the partner to the invoice
if invoice.partner_id not in invoice.message_follower_ids:
if invoice.partner_id not in invoice.message_partner_ids:
self.message_subscribe(cr, uid, [invoice.id], [invoice.partner_id.id], context=context)
for line in invoice.invoice_line_ids:
if line.product_id.email_template_id:
-1
View File
@@ -113,7 +113,6 @@
<field name="privacy_visibility">followers</field>
<field name="user_id" ref="base.user_root"/>
<field name="alias_model">project.task</field>
<field name="message_follower_ids" eval="[(6, 0, [ref('base.partner_root')])]"/>
</record>
<record id="project_stage_data_1" model="project.task.type">
+3 -7
View File
@@ -59,10 +59,9 @@
<field name="description">Implement new website for Sale and Warehouse Management for a customer.</field>
<field name="alias_model">project.task</field>
<field name="type_ids" eval="[(4, ref('project_stage_0')) ,(4,ref('project_stage_1'))]"/>
<field name="message_follower_ids" eval="[(6, 0, [
ref('base.partner_root'),
ref('base.partner_demo'),
ref('portal.partner_demo_portal')])]"/>
<field name="message_follower_ids" eval="[
(0, 0, {'res_model': 'project.project', 'partner_id': ref('portal.partner_demo_portal')})
]"/>
</record>
@@ -72,9 +71,6 @@
<field name="privacy_visibility">followers</field>
<field name="user_id" ref="base.user_demo"/>
<field name="alias_model">project.task</field>
<field name="message_follower_ids" eval="[(6, 0, [
ref('base.partner_root'),
ref('base.partner_demo')])]"/>
</record>
<record id="project_project_3" model="project.project">
+8 -8
View File
@@ -51,7 +51,7 @@
<field name="model_id" ref="model_project_project"/>
<field name="domain_force">['|',
('privacy_visibility', 'in', ['portal', 'employees']),
('message_follower_ids', 'in', [user.partner_id.id])
('message_partner_ids', 'in', [user.partner_id.id])
]</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
</record>
@@ -75,9 +75,9 @@
'|',
'&amp;',
('project_id.privacy_visibility', '=', 'followers'),
('project_id.message_follower_ids', 'in', [user.partner_id.id]),
('project_id.message_partner_ids', 'in', [user.partner_id.id]),
'|',
('message_follower_ids', 'in', [user.partner_id.id]),
('message_partner_ids', 'in', [user.partner_id.id]),
# to subscribe check access to the record, follower is not enough at creation
('user_id', '=', user.id)
]</field>
@@ -99,10 +99,10 @@
'|',
'&amp;',
('privacy_visibility', '=', 'portal'),
('message_follower_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
('message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
'&amp;',
('privacy_visibility', '=', 'followers'),
('message_follower_ids', 'in', [user.partner_id.id])
('message_partner_ids', 'in', [user.partner_id.id])
]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
@@ -115,14 +115,14 @@
'|',
'&amp;',
('project_id.privacy_visibility', '=', 'portal'),
('project_id.message_follower_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
('project_id.message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
'&amp;',
('project_id.privacy_visibility', '=', 'followers'),
('project_id.message_follower_ids', 'in', [user.partner_id.id]),
('project_id.message_partner_ids', 'in', [user.partner_id.id]),
'&amp;',
# on employee project can receive messages but not access the object
('project_id.privacy_visibility', '!=', 'employees'),
('message_follower_ids', 'in', [user.partner_id.id])
('message_partner_ids', 'in', [user.partner_id.id])
]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
+3 -1
View File
@@ -127,7 +127,9 @@ class TestPortalProject(TestPortalProjectBase):
self.task_3.sudo(self.user_projectmanager).message_subscribe_users(user_ids=[self.user_portal.id, self.user_projectuser.id])
# Do: Alfred reads project -> ok (follower ok followers)
pigs.sudo(self.user_projectuser).read(['state'])
prout = pigs.sudo(self.user_projectuser)
prout.invalidate_cache()
prout.read(['state'])
# Do: Chell reads project -> ok (follower ok follower)
pigs.sudo(self.user_portal).read(['state'])
# Do: Donovan reads project -> ko (public ko follower even if follower)
+1 -1
View File
@@ -88,7 +88,7 @@ class TestProjectFlow(TestProjectBase):
# Test: one task created by mailgateway administrator
self.assertEqual(len(task), 1, 'project: message_process: a new project.task should have been created')
# Test: check partner in message followers
self.assertIn(self.partner_2, task.message_follower_ids, "Partner in message cc is not added as a task followers.")
self.assertIn(self.partner_2, task.message_partner_ids, "Partner in message cc is not added as a task followers.")
# Test: messages
self.assertEqual(len(task.message_ids), 2,
'project: message_process: newly created task should have 2 messages: creation and email')
@@ -16,7 +16,7 @@
('project_id.privacy_visibility', '=', 'employees'),
'&amp;',
('project_id.privacy_visibility', '=', 'followers'),
('message_follower_ids', 'in', [user.partner_id.id]),
('message_partner_ids', 'in', [user.partner_id.id]),
]</field>
<field name="groups" eval="[(4,ref('base.group_user'))]"/>
</record>
@@ -27,10 +27,10 @@
<field name="domain_force">['|',
'&amp;',
('project_id.privacy_visibility', '=', 'portal'),
('message_follower_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
('message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
'&amp;',
('project_id.privacy_visibility', '=', 'followers'),
('message_follower_ids', 'in', [user.partner_id.id]),
('message_partner_ids', 'in', [user.partner_id.id]),
]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
@@ -41,7 +41,7 @@
('project_id.privacy_visibility', 'in', ['portal', 'employees']),
'&amp;',
('project_id.privacy_visibility', '=', 'followers'),
('message_follower_ids', 'in', [user.partner_id.id]),
('message_partner_ids', 'in', [user.partner_id.id]),
]</field>
</record>
@@ -8,10 +8,10 @@
<field name="domain_force">['|',
'&amp;',
('issue_id.project_id.privacy_visibility', '=', 'portal'),
('issue_id.message_follower_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
('issue_id.message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
'&amp;',
('issue_id.project_id.privacy_visibility', '=', 'followers'),
('issue_id.message_follower_ids', 'in', [user.partner_id.id]),
('issue_id.message_partner_ids', 'in', [user.partner_id.id]),
]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
@@ -11,10 +11,10 @@
<field name="domain_force">['|',
'&amp;',
('task_id.project_id.privacy_visibility', '=', 'portal'),
('task_id.message_follower_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
('task_id.message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
'&amp;',
('task_id.project_id.privacy_visibility', '=', 'followers'),
('task_id.message_follower_ids', 'in', [user.partner_id.id]),
('task_id.message_partner_ids', 'in', [user.partner_id.id]),
]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
@@ -21,7 +21,7 @@ class TestWebsiteBlogFlow(TestWebsiteBlogCommon):
'name': 'New Blog',
})
self.assertIn(
self.user_blogmanager.partner_id, test_blog.message_follower_ids,
self.user_blogmanager.partner_id, test_blog.message_partner_ids,
'website_blog: blog create should be in the blog followers')
test_blog.message_subscribe([self.user_employee.partner_id.id, self.user_public.partner_id.id])
@@ -31,10 +31,10 @@ class TestWebsiteBlogFlow(TestWebsiteBlogCommon):
'blog_id': test_blog.id,
})
self.assertNotIn(
self.user_employee.partner_id, test_blog_post.message_follower_ids,
self.user_employee.partner_id, test_blog_post.message_partner_ids,
'website_blog: subscribing to a blog should not subscribe to its posts')
self.assertNotIn(
self.user_public.partner_id, test_blog_post.message_follower_ids,
self.user_public.partner_id, test_blog_post.message_partner_ids,
'website_blog: subscribing to a blog should not subscribe to its posts')
# Publish the blog
@@ -55,5 +55,5 @@ class TestWebsiteBlogFlow(TestWebsiteBlogCommon):
subtype='mt_comment',
)
self.assertIn(
self.user_employee.partner_id, test_blog_post.message_follower_ids,
self.user_employee.partner_id, test_blog_post.message_partner_ids,
'website_blog: people commenting a post should follow it afterwards')
@@ -4,7 +4,7 @@
<record id="portal_claim_rule" model="ir.rule">
<field name="name">Portal Personal Claims</field>
<field ref="crm_claim.model_crm_claim" name="model_id"/>
<field name="domain_force">['|', ('message_follower_ids','in', [user.partner_id.id]), ('partner_id','=', user.partner_id.id)]</field>
<field name="domain_force">['|', ('message_partner_ids','in', [user.partner_id.id]), ('partner_id','=', user.partner_id.id)]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
<field eval="0" name="perm_unlink"/>
<field eval="0" name="perm_write"/>
+1 -1
View File
@@ -114,7 +114,7 @@ class WebsiteForum(http.Controller):
if filters == 'unanswered':
domain += [('child_ids', '=', False)]
elif filters == 'followed':
domain += [('message_follower_ids', '=', request.env.user.partner_id.id)]
domain += [('message_partner_ids', '=', request.env.user.partner_id.id)]
if post_type:
domain += [('post_type', '=', post_type)]
+1
View File
@@ -73,6 +73,7 @@ class WebsiteMail(http.Controller):
@http.route(['/website_mail/follow'], type='json', auth="public", website=True)
def website_message_subscribe(self, id=0, object=None, message_is_follower="on", email=False, **post):
# TDE FIXME: check this method with new followers
cr, uid, context = request.cr, request.uid, request.context
partner_obj = request.registry['res.partner']
+2 -2
View File
@@ -10,7 +10,7 @@
<field name="state">sent</field>
<field name="team_id" ref="sales_team.team_sales_department"/>
<field name="date_order" eval="(DateTime.today() - relativedelta(months=1)).strftime('%Y-%m-%d %H:%M')"/>
<field name="message_follower_ids" eval="[(4, ref('portal.partner_demo_portal'))]"/>
<field name="message_partner_ids" eval="[(4, ref('portal.partner_demo_portal'))]"/>
</record>
<record id="sale_order_line_1" model="sale.order.line">
@@ -52,7 +52,7 @@
<field name="order_policy">manual</field>
<field name="team_id" ref="sales_team.team_sales_department"/>
<field name="date_order" eval="(DateTime.today() - relativedelta(months=1)).strftime('%Y-%m-%d %H:%M')"/>
<field name="message_follower_ids" eval="[(4, ref('portal.partner_demo_portal'))]"/>
<field name="message_partner_ids" eval="[(4, ref('portal.partner_demo_portal'))]"/>
</record>
<record id="sale_order_line_4" model="sale.order.line">
@@ -10,13 +10,14 @@ class WebsiteAccount(website_account):
def account(self):
response = super(WebsiteAccount, self).account()
user = request.env.user
# TDE FIXME: shouldn't that be mnaged by the access rule itself ?
# portal projects where you or someone from your company are a follower
project_issues = request.env['project.issue'].search([
'&',
('project_id.privacy_visibility', '=', 'portal'),
'|',
('message_follower_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
('message_follower_ids', 'child_of', [user.partner_id.id])
('message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
('message_partner_ids', 'child_of', [user.partner_id.id])
])
response.qcontext.update({'issues': project_issues})
return response
@@ -8,10 +8,10 @@
<field name="domain_force">['|',
'&amp;',
('project_id.privacy_visibility', '=', 'portal'),
('message_follower_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
('message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
'&amp;',
('project_id.privacy_visibility', '=', 'followers'),
('message_follower_ids', 'in', [user.partner_id.id]),
('message_partner_ids', 'in', [user.partner_id.id]),
]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
@@ -22,7 +22,7 @@
('project_id.privacy_visibility', 'in', ['portal', 'employees']),
'&amp;',
('project_id.privacy_visibility', '=', 'followers'),
('message_follower_ids', 'in', [user.partner_id.id]),
('message_partner_ids', 'in', [user.partner_id.id]),
]</field>
</record>
</data>
@@ -8,10 +8,10 @@
<field name="domain_force">['|',
'&amp;',
('issue_id.project_id.privacy_visibility', '=', 'portal'),
('issue_id.message_follower_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
('issue_id.message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
'&amp;',
('issue_id.project_id.privacy_visibility', '=', 'followers'),
('issue_id.message_follower_ids', 'in', [user.partner_id.id]),
('issue_id.message_partner_ids', 'in', [user.partner_id.id]),
]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
+1 -1
View File
@@ -589,7 +589,7 @@ class website_sale(http.Controller):
order_info = {
'partner_id': partner_id,
'message_follower_ids': [(4, partner_id), (3, request.website.partner_id.id)],
'message_partner_ids': [(4, partner_id), (3, request.website.partner_id.id)],
'partner_invoice_id': partner_id,
}
order_info.update(order_obj.onchange_partner_id(cr, SUPERUSER_ID, [], partner_id, context=context)['value'])