[IMP] mail_message: added the right to reply to a message wes received even if we...
authorThibault Delavallée <tde@openerp.com>
Wed, 12 Dec 2012 16:06:34 +0000 (17:06 +0100)
committerThibault Delavallée <tde@openerp.com>
Wed, 12 Dec 2012 16:06:34 +0000 (17:06 +0100)
bzr revid: tde@openerp.com-20121212160634-umbiq2erwijm2d20

addons/mail/mail_message.py
addons/mail/tests/test_mail_message.py

index 6bed14f..2751e33 100644 (file)
@@ -608,55 +608,73 @@ class mail_message(osv.Model):
                 - uid have read access to the related document if model, res_id
                 - otherwise: raise
             - create: if
-                - no model, no res_id, I create a private message
+                - no model, no res_id, I create a private message OR
                 - pid in message_follower_ids if model, res_id OR
+                - mail_notification (parent_id.id, pid) exists, uid has been notified of the parent, OR
                 - uid have write access on the related document if model, res_id, OR
                 - otherwise: raise
             - write: if
                 - author_id == pid, uid is the author, OR
                 - uid has write access on the related document if model, res_id
-                - Otherwise: raise
+                - otherwise: raise
             - unlink: if
                 - uid has write access on the related document if model, res_id
-                - Otherwise: raise
+                - otherwise: raise
         """
+        def _generate_model_record_ids(msg_val, msg_ids=[]):
+            """ :param model_record_ids: {'model': {'res_id': (msg_id, msg_id)}, ... }
+                :param message_values: {'msg_id': {'model': .., 'res_id': .., 'author_id': ..}}
+            """
+            model_record_ids = {}
+            for id in msg_ids:
+                if msg_val[id]['model'] and msg_val[id]['res_id']:
+                    model_record_ids.setdefault(msg_val[id]['model'], dict()).setdefault(msg_val[id]['res_id'], set()).add(msg_val[id]['res_id'])
+            return model_record_ids
+
         if uid == SUPERUSER_ID:
             return
         if isinstance(ids, (int, long)):
             ids = [ids]
+        not_obj = self.pool.get('mail.notification')
+        fol_obj = self.pool.get('mail.followers')
         partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0]
 
         # Read mail_message.ids to have their values
         message_values = dict.fromkeys(ids)
-        model_record_ids = {}
-        cr.execute('SELECT DISTINCT id, model, res_id, author_id FROM "%s" WHERE id = ANY (%%s)' % self._table, (ids,))
-        for id, rmod, rid, author_id in cr.fetchall():
-            message_values[id] = {'res_model': rmod, 'res_id': rid, 'author_id': author_id}
-            if rmod:
-                model_record_ids.setdefault(rmod, dict()).setdefault(rid, set()).add(id)
-
-        # Author condition, for read and create (private message) -> could become an ir.rule, but not till we do not have a many2one variable field
+        cr.execute('SELECT DISTINCT id, model, res_id, author_id, parent_id FROM "%s" WHERE id = ANY (%%s)' % self._table, (ids,))
+        for id, rmod, rid, author_id, parent_id in cr.fetchall():
+            message_values[id] = {'model': rmod, 'res_id': rid, 'author_id': author_id, 'parent_id': parent_id}
+
+        # Author condition (READ, WRITE, CREATE (private)) -> could become an ir.rule ?
+        author_ids = []
         if operation == 'read' or operation == 'write':
             author_ids = [mid for mid, message in message_values.iteritems()
                 if message.get('author_id') and message.get('author_id') == partner_id]
         elif operation == 'create':
             author_ids = [mid for mid, message in message_values.iteritems()
                 if not message.get('model') and not message.get('res_id')]
-        else:
-            author_ids = []
+
+        # Parent condition, for create (check for received notifications for the created message parent)
+        notified_ids = []
+        if operation == 'create':
+            parent_ids = [message.get('parent_id') for mid, message in message_values.iteritems()
+                if message.get('parent_id')]
+            not_ids = not_obj.search(cr, SUPERUSER_ID, [('message_id.id', 'in', parent_ids), ('partner_id', '=', partner_id)], context=context)
+            not_parent_ids = [notif.message_id.id for notif in not_obj.browse(cr, SUPERUSER_ID, not_ids, context=context)]
+            notified_ids += [mid for mid, message in message_values.iteritems()
+                if message.get('parent_id') in not_parent_ids]
 
         # Notification condition, for read (check for received notifications and create (in message_follower_ids)) -> could become an ir.rule, but not till we do not have a many2one variable field
+        other_ids = set(ids).difference(set(author_ids), set(notified_ids))
+        model_record_ids = _generate_model_record_ids(message_values, other_ids)
         if operation == 'read':
-            not_obj = self.pool.get('mail.notification')
             not_ids = not_obj.search(cr, SUPERUSER_ID, [
                 ('partner_id', '=', partner_id),
                 ('message_id', 'in', ids),
             ], context=context)
             notified_ids = [notification.message_id.id for notification in not_obj.browse(cr, SUPERUSER_ID, not_ids, context=context)]
         elif operation == 'create':
-            notified_ids = []
             for doc_model, doc_dict in model_record_ids.items():
-                fol_obj = self.pool.get('mail.followers')
                 fol_ids = fol_obj.search(cr, SUPERUSER_ID, [
                     ('res_model', '=', doc_model),
                     ('res_id', 'in', list(doc_dict.keys())),
@@ -664,22 +682,15 @@ class mail_message(osv.Model):
                     ], context=context)
                 fol_mids = [follower.res_id for follower in fol_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context)]
                 notified_ids += [mid for mid, message in message_values.iteritems()
-                    if message.get('res_model') == doc_model and message.get('res_id') in fol_mids]
-        else:
-            notified_ids = []
-
-        # Calculate remaining ids, and related model/res_ids
-        model_record_ids = {}
-        other_ids = set(ids).difference(set(author_ids), set(notified_ids))
-        for id in other_ids:
-            if message_values[id]['res_model']:
-                model_record_ids.setdefault(message_values[id]['res_model'], set()).add(message_values[id]['res_id'])
+                    if message.get('model') == doc_model and message.get('res_id') in fol_mids]
 
         # CRUD: Access rights related to the document
+        other_ids = other_ids.difference(set(notified_ids))
+        model_record_ids = _generate_model_record_ids(message_values, other_ids)
         document_related_ids = []
-        for model, mids in model_record_ids.items():
+        for model, doc_dict in model_record_ids.items():
             model_obj = self.pool.get(model)
-            mids = model_obj.exists(cr, uid, mids)
+            mids = model_obj.exists(cr, uid, doc_dict.keys())
             if operation in ['create', 'write', 'unlink']:
                 model_obj.check_access_rights(cr, uid, 'write')
                 model_obj.check_access_rule(cr, uid, mids, 'write', context=context)
@@ -687,10 +698,10 @@ class mail_message(osv.Model):
                 model_obj.check_access_rights(cr, uid, operation)
                 model_obj.check_access_rule(cr, uid, mids, operation, context=context)
             document_related_ids += [mid for mid, message in message_values.iteritems()
-                if message.get('res_model') == model and message.get('res_id') in mids]
+                if message.get('model') == model and message.get('res_id') in mids]
 
         # Calculate remaining ids: if not void, raise an error
-        other_ids = other_ids - set(document_related_ids)
+        other_ids = other_ids.difference(set(document_related_ids))
         if not other_ids:
             return
         raise orm.except_orm(_('Access Denied'),
index 3030d6d..39bb990 100644 (file)
@@ -123,7 +123,8 @@ class test_mail_access_rights(TestMailBase):
         user_bert_id, user_raoul_id = self.user_bert_id, self.user_raoul_id
 
         # Prepare groups: Pigs (employee), Jobs (public)
-        self.mail_group.message_post(cr, uid, self.group_pigs_id, body='Message')
+        pigs_msg_id = self.mail_group.message_post(cr, uid, self.group_pigs_id, body='Message')
+        priv_msg_id = self.mail_group.message_post(cr, uid, self.group_priv_id, body='Message')
 
         # prepare an attachment
         attachment_id = self.ir_attachment.create(cr, uid, {'datas': 'My attachment'.encode('base64'), 'name': 'doc.txt', 'datas_fname': 'doc.txt'})
@@ -184,11 +185,20 @@ class test_mail_access_rights(TestMailBase):
         # Do: Bert create a private message -> ko, no creation rights
         self.assertRaises(except_orm, self.mail_message.create,
             cr, user_bert_id, {'body': 'Test'})
+
         # Do: Raoul creates a message on Jobs -> ok, write access to the related document
         self.mail_message.create(cr, user_raoul_id, {'model': 'mail.group', 'res_id': self.group_jobs_id, 'body': 'Test'})
         # Do: Raoul creates a message on Priv -> ko, no write access to the related document
         self.assertRaises(except_orm, self.mail_message.create,
             cr, user_raoul_id, {'model': 'mail.group', 'res_id': self.group_priv_id, 'body': 'Test'})
+        # Do: Raoul creates a private message -> ok
+        self.mail_message.create(cr, user_raoul_id, {'body': 'Test'})
+        # Do: Raoul creates a reply to a message on Priv -> ko
+        self.assertRaises(except_orm, self.mail_message.create,
+            cr, user_raoul_id, {'model': 'mail.group', 'res_id': self.group_priv_id, 'body': 'Test', 'parent_id': priv_msg_id})
+        # Do: Raoul creates a reply to a message on Priv-> ok if has received parent
+        self.mail_notification.create(cr, uid, {'message_id': priv_msg_id, 'partner_id': self.partner_raoul_id})
+        self.mail_message.create(cr, user_raoul_id, {'model': 'mail.group', 'res_id': self.group_priv_id, 'body': 'Test', 'parent_id': priv_msg_id})
 
     def test_20_message_set_star(self):
         """ Tests for starring messages and its related access rights """
@@ -326,6 +336,11 @@ class test_mail_access_rights(TestMailBase):
                           self.mail_group.message_post,
                           cr, user_bert_id, self.group_jobs_id, body='I love Pigs')
 
+        # Do: Bert writes on its own profile, ko because no message create access
+        with self.assertRaises(except_orm):
+            self.res_users.message_post(cr, user_bert_id, user_bert_id, body='I love Bert')
+            self.res_partner.message_post(cr, user_bert_id, partner_bert_id, body='I love Bert')
+
         # ----------------------------------------
         # CASE2: Raoul, employee
         # ----------------------------------------
@@ -350,3 +365,11 @@ class test_mail_access_rights(TestMailBase):
             {'subject': 'Subject', 'body': 'Body text'},
             {'default_composition_mode': 'reply', 'default_parent_id': pigs_msg_id})
         mail_compose.send_mail(cr, user_raoul_id, [compose_id])
+
+        # Do: Raoul writes on its own profile, ok because follower of its partner
+        self.res_users.message_post(cr, user_raoul_id, user_raoul_id, body='I love Raoul')
+        self.res_partner.message_post(cr, user_raoul_id, partner_raoul_id, body='I love Raoul')
+        compose_id = mail_compose.create(cr, user_raoul_id,
+            {'subject': 'Subject', 'body': 'Body text', 'partner_ids': []},
+            {'default_composition_mode': 'comment', 'default_model': 'res.users', 'default_res_id': self.user_raoul_id})
+        mail_compose.send_mail(cr, user_raoul_id, [compose_id])