[IMP] mail: new internal-only subtype feature.
Starting from now, subtypes can be internal. This means that only employees (group_user members) can see the messages with this subtype. This feature replaces and enhances the 'log a note = no subtype = internal only' feature. Public and portal users cannot see internal messages. This allows to have subtypes and a follow mechanism that works for employees and is not visible for external people. Its first use will be for Crm Activities, allowing to have custom subtypes visible only for salesman and not send to the customer.
This commit is contained in:
@@ -68,5 +68,11 @@
|
||||
<field name="name">Discussions</field>
|
||||
<field name="sequence" eval="0"/>
|
||||
</record>
|
||||
<record id="mt_note" model="mail.message.subtype">
|
||||
<field name="name">Note</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="internal" eval="True"/>
|
||||
<field name="sequence" eval="100"/>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
@@ -541,7 +541,7 @@ class Message(models.Model):
|
||||
count=count, access_rights_uid=access_rights_uid)
|
||||
# Non-employee see only messages with a subtype (aka, no internal logs)
|
||||
if not self.env['res.users'].has_group('base.group_user'):
|
||||
args = ['&', ('subtype_id', '!=', False)] + list(args)
|
||||
args = ['&', '&', ('subtype_id', '!=', False), ('subtype_id.internal', '=', False)] + list(args)
|
||||
# Perform a super with count as False, to have the ids, not a counter
|
||||
ids = super(Message, self)._search(
|
||||
args, offset=offset, limit=limit, order=order,
|
||||
@@ -620,7 +620,11 @@ class Message(models.Model):
|
||||
return
|
||||
# Non employees see only messages with a subtype (aka, not internal logs)
|
||||
if not self.env['res.users'].has_group('base.group_user'):
|
||||
self._cr.execute('SELECT DISTINCT id FROM "%s" WHERE message_type = %%s AND subtype_id IS NULL AND id = ANY (%%s)' % (self._table), ('comment', self.ids,))
|
||||
self._cr.execute('''SELECT DISTINCT message.id, message.subtype_id, subtype.internal
|
||||
FROM "%s" AS message
|
||||
LEFT JOIN "mail_message_subtype" as subtype
|
||||
ON message.subtype_id = subtype.id
|
||||
WHERE message.message_type = %%s AND (message.subtype_id IS NULL OR subtype.internal IS TRUE) AND message.id = ANY (%%s)''' % (self._table), ('comment', self.ids,))
|
||||
if self._cr.fetchall():
|
||||
raise AccessError(
|
||||
_('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') %
|
||||
@@ -783,10 +787,16 @@ class Message(models.Model):
|
||||
self.ensure_one() # tde: not sure, just for testinh, will see
|
||||
partners_to_notify = self.env['res.partner']
|
||||
|
||||
# all followers of the mail.message document have to be added as partners and notified if a subtype is defined (otherwise: log message)
|
||||
# all followers of the mail.message document have to be added as partners and notified
|
||||
# and filter to employees only if the subtype is internal
|
||||
if self.subtype_id and self.model and self.res_id:
|
||||
followers = self.env['mail.followers'].sudo().search([('res_model', '=', self.model), ('res_id', '=', self.res_id)])
|
||||
partners_to_notify |= followers.filtered(lambda fol: self.subtype_id in fol.subtype_ids).mapped('partner_id')
|
||||
followers = self.env['mail.followers'].sudo().search([
|
||||
('res_model', '=', self.model),
|
||||
('res_id', '=', self.res_id)
|
||||
]).filtered(lambda fol: self.subtype_id in fol.subtype_ids)
|
||||
if self.subtype_id.internal:
|
||||
followers.filtered(lambda fol: fol.partner_id.user_ids and fol.partner_id.user_ids[0].has_group('base.group_user'))
|
||||
partners_to_notify |= followers.mapped('partner_id')
|
||||
|
||||
# remove me from notified partners, unless the message is written on my own wall
|
||||
if self.subtype_id and self.author_id and self.model == "res.partner" and self.res_id == self.author_id.id:
|
||||
|
||||
@@ -22,9 +22,14 @@ class MailMessageSubtype(models.Model):
|
||||
'Description', translate=True,
|
||||
help='Description that will be added in the message posted for this '
|
||||
'subtype. If void, the name will be added instead.')
|
||||
internal = fields.Boolean(
|
||||
'Internal Only',
|
||||
help='Messages with internal subtypes will be visible only by employees, aka members of base_user group')
|
||||
parent_id = fields.Many2one(
|
||||
'mail.message.subtype', string='Parent', ondelete='set null',
|
||||
help='Parent subtype, used for automatic subscription.')
|
||||
help='Parent subtype, used for automatic subscription. This field is not'
|
||||
'correctly named. For example on a project, project subtypes have'
|
||||
'parent_id to task-related subtypes.')
|
||||
relation_field = fields.Char(
|
||||
'Relation field',
|
||||
help='Field used to link the related model to the subtype model when '
|
||||
|
||||
@@ -336,8 +336,17 @@ class MailThread(models.AbstractModel):
|
||||
if view_type == 'form':
|
||||
doc = etree.XML(res['arch'])
|
||||
for node in doc.xpath("//field[@name='message_ids']"):
|
||||
# the 'Log a note' button is employee only
|
||||
options = json.loads(node.get('options', '{}'))
|
||||
options['display_log_button'] = self.env.user.sudo().has_group('base.group_user')
|
||||
is_employee = self.env.user.has_group('base.group_user')
|
||||
options['display_log_button'] = is_employee
|
||||
if is_employee:
|
||||
Subtype = self.env['mail.message.subtype'].sudo()
|
||||
# fetch internal subtypes
|
||||
internal_subtypes = Subtype.search_read([
|
||||
('res_model', 'in', [False, self._name]),
|
||||
('internal', '=', True)], ['name', 'description', 'sequence'])
|
||||
options['internal_subtypes'] = internal_subtypes
|
||||
node.set('options', json.dumps(options))
|
||||
res['arch'] = etree.tostring(doc)
|
||||
return res
|
||||
@@ -1552,8 +1561,9 @@ class MailThread(models.AbstractModel):
|
||||
attachments, kwargs.pop('attachment_ids', []), model, self.ids and self.ids[0] or None)
|
||||
|
||||
# 4: mail.message.subtype
|
||||
subtype_id = False
|
||||
if subtype:
|
||||
subtype_id = kwargs.get('subtype_id', False)
|
||||
if not subtype_id:
|
||||
subtype = subtype or 'mt_note'
|
||||
if '.' not in subtype:
|
||||
subtype = 'mail.%s' % subtype
|
||||
subtype_id = self.env['ir.model.data'].xmlid_to_res_id(subtype)
|
||||
@@ -1607,8 +1617,10 @@ class MailThread(models.AbstractModel):
|
||||
|
||||
# Post-process: subscribe author, update message_last_post
|
||||
if model and model != 'mail.thread' and self.ids and subtype_id:
|
||||
# done with SUPERUSER_ID, because on some models users can post only with read access, not necessarily write access
|
||||
self.sudo().write({'message_last_post': fields.datetime.now()})
|
||||
subtype_rec = self.env['mail.message.subtype'].sudo().browse(subtype_id)
|
||||
if not subtype_rec.internal:
|
||||
# done with SUPERUSER_ID, because on some models users can post only with read access, not necessarily write access
|
||||
self.sudo().write({'message_last_post': fields.Datetime.now()})
|
||||
if new_message.author_id and model and self.ids and message_type != 'notification' and not self._context.get('mail_create_nosubscribe'):
|
||||
self.message_subscribe([new_message.author_id.id])
|
||||
return new_message
|
||||
|
||||
@@ -20,7 +20,7 @@ access_mail_alias_all,mail.alias.all,model_mail_alias,,1,0,0,0
|
||||
access_mail_alias_user,mail.alias.user,model_mail_alias,base.group_user,1,1,1,0
|
||||
access_mail_alias_system,mail.alias.system,model_mail_alias,base.group_system,1,1,1,1
|
||||
access_mail_message_subtype_all,mail.message.subtype.all,model_mail_message_subtype,,1,0,0,0
|
||||
access_mail_message_subtype_system,mail.message.subtype.system,model_mail_message_subtype,base.group_system,1,1,1,1
|
||||
access_mail_message_subtype_user,mail.message.subtype.user,model_mail_message_subtype,,1,1,1,1
|
||||
access_mail_tracking_value_all,mail.tracking.value.all,model_mail_tracking_value,,1,0,0,0
|
||||
access_mail_tracking_value_portal,mail.tracking.value.portal,model_mail_tracking_value,base.group_portal,1,1,1,1
|
||||
access_mail_tracking_value_user,mail.tracking.value.user,model_mail_tracking_value,base.group_user,1,1,1,1
|
||||
|
||||
|
@@ -38,6 +38,13 @@
|
||||
<field name="perm_unlink" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record id="mail_message_subtype_rule_public" model="ir.rule">
|
||||
<field name="name">mail.message.subtype: portal/public: read public subtypes</field>
|
||||
<field name="model_id" ref="model_mail_message_subtype"/>
|
||||
<field name="domain_force">[('internal', '=', False)]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_portal')), (4, ref('base.group_public'))]"/>
|
||||
</record>
|
||||
|
||||
<!--
|
||||
This rule can not be uncommented, because we have a more wide method in mail.message. When we implement a many2one_variable field, we will be able to uncomment this.
|
||||
<record id="mail_message_read_partner_or_author" model="ir.rule">
|
||||
|
||||
@@ -50,6 +50,7 @@ var TimelineRecordThread = form_common.AbstractField.extend ({
|
||||
'readonly': this.node.attrs.readonly || false,
|
||||
'compose_placeholder' : this.node.attrs.placeholder || false,
|
||||
'display_log_button' : this.options.display_log_button || true,
|
||||
'internal_subtypes' : this.options.internal_subtypes || [],
|
||||
'show_compose_message': true,
|
||||
'show_link': this.parent.is_action_enabled('edit') || true,
|
||||
}, this.node.params);
|
||||
@@ -1218,10 +1219,16 @@ var ComposeMessage = Attachment.extend ({
|
||||
'content_subtype': 'plaintext',
|
||||
};
|
||||
|
||||
if(log){
|
||||
values.subtype = false;
|
||||
}else{
|
||||
values.subtype = 'mail.mt_comment';
|
||||
if (log) {
|
||||
var subtype_id = parseInt(this.$('select').first().val());
|
||||
if (_.indexOf(_.pluck(self.options.internal_subtypes, 'id'), subtype_id) == -1) {
|
||||
values['subtype'] = 'mail.mt_note'
|
||||
}
|
||||
else {
|
||||
values['subtype_id'] = subtype_id;
|
||||
}
|
||||
} else {
|
||||
values['subtype'] = 'mail.mt_comment';
|
||||
}
|
||||
|
||||
this.parent_thread.ds_thread._model.call('message_post', [this.context.default_res_id], values).done(function (message_id) {
|
||||
|
||||
@@ -37,10 +37,18 @@
|
||||
<div class="o_timeline_msg o_timeline_msg_composer">
|
||||
<div class="o_timeline_msg_center">
|
||||
<div class="o_timeline_msg_content">
|
||||
<button class="oe_full" title="Open the full mail composer">
|
||||
<button t-if="! widget.is_log" class="oe_full" title="Open the full mail composer">
|
||||
<i class='fa fa-pencil-square-o'/>
|
||||
</button>
|
||||
<t t-call="ThreadListRecipients"/>
|
||||
<t t-if="widget.is_log and widget.options.internal_subtypes.length > 1">
|
||||
<label>Activity</label>
|
||||
<select class="">
|
||||
<t t-foreach="widget.options.internal_subtypes" t-as="subtype">
|
||||
<option t-att-value="subtype['id']"><t t-raw="subtype['name']"/></option>
|
||||
</t>
|
||||
</select>
|
||||
</t>
|
||||
<textarea class="field_text"></textarea>
|
||||
</div>
|
||||
<div class="o_timeline_msg_footer">
|
||||
|
||||
@@ -89,7 +89,7 @@ class TestMailFollowers(TestMail):
|
||||
def test_subscription_data(self):
|
||||
self.group_pigs.message_subscribe_users(user_ids=[self.user_employee.id])
|
||||
subtype_data = self.group_pigs.sudo(self.user_employee)._get_subscription_data()[self.group_pigs.id]['message_subtype_data']
|
||||
self.assertEqual(set(subtype_data.keys()), set(['Discussions', 'mt_mg_def', 'mt_al_def', 'mt_mg_nodef', 'mt_al_nodef']), 'mail.group available subtypes incorrect')
|
||||
self.assertEqual(set(subtype_data.keys()), set(['Discussions', 'Note', 'mt_mg_def', 'mt_al_def', 'mt_mg_nodef', 'mt_al_nodef']), 'mail.group available subtypes incorrect')
|
||||
self.assertTrue(subtype_data['Discussions']['followed'])
|
||||
self.assertFalse(subtype_data['mt_mg_nodef']['followed'])
|
||||
self.assertFalse(subtype_data['mt_al_nodef']['followed'])
|
||||
|
||||
@@ -12,6 +12,7 @@ class TestTracking(TestMail):
|
||||
return body.replace(' ', '').replace('\n', '')
|
||||
Subtype = self.env['mail.message.subtype']
|
||||
Data = self.env['ir.model.data']
|
||||
note_subtype = self.env.ref('mail.mt_note')
|
||||
|
||||
group_system = self.env.ref('base.group_system')
|
||||
group_user = self.env.ref('base.group_user')
|
||||
@@ -97,7 +98,7 @@ class TestTracking(TestMail):
|
||||
self.group_pigs.sudo(self.user_employee).write({'name': 'my_name'})
|
||||
self.assertEqual(len(self.group_pigs.message_ids), 1)
|
||||
last_msg = self.group_pigs.message_ids[-1]
|
||||
self.assertFalse(last_msg.subtype_id)
|
||||
self.assertEqual(last_msg.subtype_id, note_subtype)
|
||||
self.assertEqual(len(last_msg.tracking_value_ids), 1)
|
||||
self.assertEqual(last_msg.tracking_value_ids.field, 'name')
|
||||
self.assertEqual(last_msg.tracking_value_ids.field_desc, 'Name')
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
<field name="res_model"/>
|
||||
<field name="description"/>
|
||||
<field name="default"/>
|
||||
<field name="internal"/>
|
||||
<field name="hidden"/>
|
||||
</group>
|
||||
<group string='Auto subscription'>
|
||||
|
||||
@@ -10,7 +10,7 @@ class test_portal(TestMail):
|
||||
port_msg = self.group_portal.message_post(body='Message')
|
||||
|
||||
# Do: Chell comments Pigs, ok because can write on it (public group)
|
||||
self.group_portal.sudo(self.user_portal).message_post(body='I love Pigs')
|
||||
self.group_portal.sudo(self.user_portal).message_post(body='I love Pigs', message_type='comment', subtype='mail.mt_comment')
|
||||
# Do: Chell creates a mail.compose.message record on Pigs, because he uses the wizard
|
||||
compose = self.env['mail.compose.message'].with_context({
|
||||
'default_composition_mode': 'comment',
|
||||
|
||||
Reference in New Issue
Block a user