[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:
Thibault Delavallée
2015-07-01 12:52:52 +02:00
parent 0a446f976f
commit 9cd9aaaa17
12 changed files with 77 additions and 20 deletions
+6
View File
@@ -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>
+15 -5
View File
@@ -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:
+6 -1
View File
@@ -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 '
+17 -5
View File
@@ -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
+1 -1
View File
@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
20 access_mail_alias_user mail.alias.user model_mail_alias base.group_user 1 1 1 0
21 access_mail_alias_system mail.alias.system model_mail_alias base.group_system 1 1 1 1
22 access_mail_message_subtype_all mail.message.subtype.all model_mail_message_subtype 1 0 0 0
23 access_mail_message_subtype_system access_mail_message_subtype_user mail.message.subtype.system mail.message.subtype.user model_mail_message_subtype base.group_system 1 1 1 1
24 access_mail_tracking_value_all mail.tracking.value.all model_mail_tracking_value 1 0 0 0
25 access_mail_tracking_value_portal mail.tracking.value.portal model_mail_tracking_value base.group_portal 1 1 1 1
26 access_mail_tracking_value_user mail.tracking.value.user model_mail_tracking_value base.group_user 1 1 1 1
+7
View File
@@ -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">
+11 -4
View File
@@ -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) {
+9 -1
View File
@@ -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">
+1 -1
View File
@@ -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'])
+2 -1
View File
@@ -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'>
+1 -1
View File
@@ -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',