From 9fb978ea8196aee4783641a7b3ea8c7f0da7b580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 5 Sep 2012 17:53:19 +0200 Subject: [PATCH] [IMP] mail: added and cleaned the tests. Fixed various small bugs in composer. Fixed mail_message.message_read() and flattening algorithm. Cleaned tests for message_read to have a more precise test. bzr revid: tde@openerp.com-20120905155319-wd168m5snb25ari9 --- addons/mail/mail_message.py | 46 ++-- addons/mail/tests/test_mail.py | 249 +++++++++++++-------- addons/mail/wizard/mail_compose_message.py | 65 +++--- 3 files changed, 207 insertions(+), 153 deletions(-) diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py index a9266ff07fc..98f419b3f08 100644 --- a/addons/mail/mail_message.py +++ b/addons/mail/mail_message.py @@ -20,10 +20,12 @@ ############################################################################## import logging -from email.header import decode_header -from osv import osv, fields import tools +from email.header import decode_header +from operator import itemgetter +from osv import osv, fields + _logger = logging.getLogger(__name__) """ Some tools for parsing / creating email fields """ @@ -45,7 +47,7 @@ class mail_message(osv.Model): _message_record_name_length = 18 def _shorten_name(self, name): - if len(name) <= (self._message_record_name_length+3): + if len(name) <= (self._message_record_name_length + 3): return name return name[:self._message_record_name_length] + '...' @@ -73,7 +75,7 @@ class mail_message(osv.Model): return res def _search_unread(self, cr, uid, obj, name, domain, context=None): - """ Search for messages unread by the current user. Condition is + """ Search for messages unread by the current user. Condition is inversed because we search unread message on a read column. """ if domain[0][2]: read_cond = '(read = false or read is null)' @@ -135,7 +137,7 @@ class mail_message(osv.Model): _defaults = { 'type': 'email', 'date': lambda *a: fields.datetime.now(), - 'author_id': lambda self,cr,uid,ctx: self._get_default_author(cr, uid, ctx), + 'author_id': lambda self, cr, uid, ctx={}: self._get_default_author(cr, uid, ctx), 'body': '', } @@ -169,12 +171,13 @@ class mail_message(osv.Model): """ Given a tree with several roots of following structure : [ {'id': 1, 'child_ids': [ {'id': 11, 'child_ids': [...] },], - }, {...} ] - Flatten it to have a maximum number of level, with 0 being - completely flat. + Flatten it to have a maximum number of levels, 0 being flat and + sort messages in a level according to a key of the messages. Perform the flattening at leafs if above the maximum depth, then get back in the tree. + :param context: ``sort_key``: key for sorting (id by default) + :param context: ``sort_reverse``: reverser order for sorting (True by default) """ def _flatten(msg_dict): """ from {'id': x, 'child_ids': [{child1}, {child2}]} @@ -183,19 +186,22 @@ class mail_message(osv.Model): child_ids = msg_dict.pop('child_ids', []) msg_dict['child_ids'] = [] return [msg_dict] + child_ids + # return sorted([msg_dict] + child_ids, key=itemgetter('id'), reverse=True) + context = context or {} # Depth-first flattening for message in messages: if message.get('type') == 'expandable': continue - message['child_ids'] = self.message_read_tree_flatten(cr, uid, message['child_ids'], current_level+1, level, context=context) + message['child_ids'] = self.message_read_tree_flatten(cr, uid, message['child_ids'], current_level + 1, level, context=context) # Flatten if above maximum depth if current_level < level: - return messages - new_list = [] - for message in messages: - for flat_message in _flatten(message): - new_list.append(flat_message) - return new_list + return_list = messages + else: + return_list = [] + for message in messages: + for flat_message in _flatten(message): + return_list.append(flat_message) + return sorted(return_list, key=itemgetter(context.get('sort_key', 'id')), reverse=context.get('sort_reverse', True)) def message_read(self, cr, uid, ids=False, domain=[], thread_level=0, limit=None, context=None): """ If IDs are provided, fetch these records. Otherwise use the domain @@ -213,7 +219,7 @@ class mail_message(osv.Model): result = [] tree = {} # key: ID, value: record for msg in messages: - if len(result)<(limit-1): + if len(result) < (limit - 1): record = self._message_dict_get(cr, uid, msg, context=context) if thread_level and msg.parent_id: while msg.parent_id: @@ -233,7 +239,7 @@ class mail_message(osv.Model): else: result.append({ 'type': 'expandable', - 'domain': [('id','<=', msg.id)]+domain, + 'domain': [('id', '<=', msg.id)] + domain, 'context': context, 'thread_level': thread_level # should be improve accodting to level of records }) @@ -279,7 +285,7 @@ class mail_message(osv.Model): if not (rmod and rid): continue document_ids.append(id) - model_record_ids.setdefault(rmod,set()).add(rid) + model_record_ids.setdefault(rmod, set()).add(rid) for model, mids in model_record_ids.items(): model_obj = self.pool.get(model) mids = model_obj.exists(cr, uid, mids) @@ -292,7 +298,7 @@ class mail_message(osv.Model): def create(self, cr, uid, values, context=None): if not values.get('message_id') and values.get('res_id') and values.get('model'): - values['message_id'] = tools.generate_tracking_message_id('%(model)s-%(res_id)s'% values) + values['message_id'] = tools.generate_tracking_message_id('%(model)s-%(res_id)s' % values) newid = super(mail_message, self).create(cr, uid, values, context) self.notify(cr, uid, newid, context=context) return newid @@ -333,4 +339,4 @@ class mail_message(osv.Model): if default is None: default = {} default.update(message_id=False, headers=False) - return super(mail_message,self).copy(cr, uid, id, default=default, context=context) + return super(mail_message, self).copy(cr, uid, id, default=default, context=context) diff --git a/addons/mail/tests/test_mail.py b/addons/mail/tests/test_mail.py index b24ba94b891..15416c51a28 100644 --- a/addons/mail/tests/test_mail.py +++ b/addons/mail/tests/test_mail.py @@ -19,8 +19,6 @@ # ############################################################################## -import tools - from openerp.tests import common MAIL_TEMPLATE = """Return-Path: @@ -93,10 +91,9 @@ class test_mail(common.TransactionCase): self.registry('ir.mail_server').send_email = self._mock_smtp_gateway # groups@.. will cause the creation of new mail groups - self.mail_group_model_id = self.ir_model.search(self.cr, self.uid, [('model','=', 'mail.group')])[0] + self.mail_group_model_id = self.ir_model.search(self.cr, self.uid, [('model', '=', 'mail.group')])[0] self.mail_alias.create(self.cr, self.uid, {'alias_name': 'groups', 'alias_model_id': self.mail_group_model_id}) - # create a 'pigs' group that will be used through the various tests self.group_pigs_id = self.mail_group.create(self.cr, self.uid, {'name': 'Pigs', 'description': 'Fans of Pigs, unite !'}) @@ -104,10 +101,10 @@ class test_mail(common.TransactionCase): def test_00_message_process(self): cr, uid = self.cr, self.uid # Incoming mail creates a new mail_group "frogs" - self.assertEqual(self.mail_group.search(cr, uid, [('name','=','frogs')]), []) + self.assertEqual(self.mail_group.search(cr, uid, [('name', '=', 'frogs')]), []) mail_frogs = MAIL_TEMPLATE.format(to='groups@example.com, other@gmail.com', subject='frogs', extra='') self.mail_thread.message_process(cr, uid, None, mail_frogs) - frog_groups = self.mail_group.search(cr, uid, [('name','=','frogs')]) + frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'frogs')]) self.assertTrue(len(frog_groups) == 1) # Previously-created group can be emailed now - it should have an implicit alias group+frogs@... @@ -121,11 +118,11 @@ class test_mail(common.TransactionCase): # Even with a wrong destination, a reply should end up in the correct thread mail_reply = MAIL_TEMPLATE.format(to='erroneous@example.com>', subject='Re: news', - extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n'%frog_group.id) + extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id) self.mail_thread.message_process(cr, uid, None, mail_reply) frog_group.refresh() self.assertTrue(len(frog_group.message_ids) == 3, 'Group should contain 3 messages now') - + # No model passed and no matching alias must raise mail_spam = MAIL_TEMPLATE.format(to='noone@example.com', subject='spam', extra='') self.assertRaises(Exception, @@ -192,7 +189,7 @@ class test_mail(common.TransactionCase): self.assertEqual(follower_ids, set([partner_bert_id]), 'Bert should be the follower of dummy mail.thread data') fol_obj_ids = self.mail_followers.search(cr, uid, [('res_model', '=', 'mail.group'), ('res_id', '=', group_dummy_id)]) follower_ids = set([follower.partner_id.id for follower in self.mail_followers.browse(cr, uid, fol_obj_ids)]) - self.assertEqual(follower_ids,set([partner_bert_id, user_admin.partner_id.id]), 'Bert and Admin should be the followers of dummy mail.group data') + self.assertEqual(follower_ids, set([partner_bert_id, user_admin.partner_id.id]), 'Bert and Admin should be the followers of dummy mail.group data') def test_11_message_followers(self): """ Tests designed for the subscriber API. """ @@ -252,9 +249,8 @@ class test_mail(common.TransactionCase): msg_id = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body1, subject=_subject, type='comment') message = self.mail_message.browse(cr, uid, msg_id) sent_email = self._build_email_kwargs - - self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id','=',msg_id)]), 'mail.mail notifications should have been auto-deleted!') - + # Test: notifications have been deleted + self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg_id)]), 'mail.mail notifications should have been auto-deleted!') # Test: mail_message: subject is _subject, body is _body1 (no formatting done) self.assertEqual(message.subject, _subject, 'mail.message subject incorrect') self.assertEqual(message.body, _body1, 'mail.message body incorrect') @@ -279,7 +275,7 @@ class test_mail(common.TransactionCase): partner_ids=[(6, 0, [p_d_id])], parent_id=msg_id, attachments=_attachments) message = self.mail_message.browse(cr, uid, msg_id2) sent_email = self._build_email_kwargs - self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id','=',msg_id2)]), 'mail.mail notifications should have been auto-deleted!') + self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg_id2)]), 'mail.mail notifications should have been auto-deleted!') # Test: mail_message: subject is False, body is _body2 (no formatting done), parent_id is msg_id self.assertEqual(message.subject, False, 'mail.message subject incorrect') @@ -300,11 +296,11 @@ class test_mail(common.TransactionCase): # Test: sent_email: email_to should contain b@b, c@c, not a@a (writer) self.assertEqual(set(sent_email['email_to']), set(['b@b', 'c@c']), 'sent_email email_to incorrect') # Test: attachments - for i in range(len(message.attachment_ids)): - self.assertEqual(message.attachment_ids[i].name, _attachments[i][0], 'mail.message attachment name incorrect') - self.assertEqual(message.attachment_ids[i].res_model, 'mail.group', 'mail.message attachment res_model incorrect') - self.assertEqual(message.attachment_ids[i].res_id, self.group_pigs_id, 'mail.message attachment res_id incorrect') - self.assertEqual(message.attachment_ids[i].datas.decode('base64'), _attachments[i][1], 'mail.message attachment data incorrect') + for attach in message.attachment_ids: + self.assertEqual(attach.res_model, 'mail.group', 'mail.message attachment res_model incorrect') + self.assertEqual(attach.res_id, self.group_pigs_id, 'mail.message attachment res_id incorrect') + self.assertIn((attach.name, attach.datas.decode('base64')), _attachments, + 'mail.message attachment name / data incorrect') def test_21_message_compose_wizard(self): """ Tests designed for the mail.compose.message wizard. """ @@ -313,19 +309,19 @@ class test_mail(common.TransactionCase): self.res_users.write(cr, uid, [uid], {'signature': 'Admin', 'email': 'a@a'}) user_admin = self.res_users.browse(cr, uid, uid) group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id) + group_bird_id = self.mail_group.create(cr, uid, {'name': 'Bird', 'description': 'Bird resistance'}) + group_bird = self.mail_group.browse(cr, uid, group_bird_id) # Mail data _subject = 'Pigs' - _subject_reply = 'Re: Pigs' - _mail_subject = '%s posted on %s' % (user_admin.name, group_pigs.name) _body_text = 'Pigs rules' - _msg_body1 = '
Pigs rules
' - _body_html = 'Pigs rules' - _msg_body2 = 'Pigs rules' + _msg_reply = 'Re: Pigs' + _msg_body = '
Pigs rules
' _attachments = [ - {'name': 'First', 'datas': 'My first attachment'.encode('base64')}, - {'name': 'Second', 'datas': 'My second attachment'.encode('base64')} + {'name': 'First', 'datas_fname': 'first.txt', 'datas': 'My first attachment'.encode('base64')}, + {'name': 'Second', 'datas_fname': 'second.txt', 'datas': 'My second attachment'.encode('base64')} ] + _attachments_test = [('first.txt', 'My first attachment'), ('second.txt', 'My second attachment')] # Create partners # 0 - Admin @@ -340,23 +336,27 @@ class test_mail(common.TransactionCase): # Subscribe #1 group_pigs.message_subscribe([p_b_id]) - # CASE1: comment group_pigs with body_text and subject + # ---------------------------------------- + # CASE1: comment on group_pigs + # ---------------------------------------- + + # 1. Comment group_pigs with body_text and subject compose_id = mail_compose.create(cr, uid, {'subject': _subject, 'body_text': _body_text, 'partner_ids': [(4, p_c_id), (4, p_d_id)]}, {'default_composition_mode': 'comment', 'default_model': 'mail.group', 'default_res_id': self.group_pigs_id}) compose = mail_compose.browse(cr, uid, compose_id) - # Test: mail.compose.message: model, res_id + # Test: mail.compose.message: composition_mode, model, res_id + self.assertEqual(compose.composition_mode, 'comment', 'mail.compose.message incorrect composition_mode') self.assertEqual(compose.model, 'mail.group', 'mail.compose.message incorrect model') self.assertEqual(compose.res_id, self.group_pigs_id, 'mail.compose.message incorrect res_id') - # Post the comment, get created message + # 2. Post the comment, get created message mail_compose.send_mail(cr, uid, [compose_id]) group_pigs.refresh() message = group_pigs.message_ids[0] - # Test: mail.message: subject, body inside pre self.assertEqual(message.subject, False, 'mail.message incorrect subject') - self.assertEqual(message.body, _msg_body1, 'mail.message incorrect body') + self.assertEqual(message.body, _msg_body, 'mail.message incorrect body') # Test: mail.message: partner_ids = entries in mail.notification: group_pigs fans (a, b) + mail.compose.message partner_ids (c, d) msg_pids = [partner.id for partner in message.partner_ids] test_pids = [p_a_id, p_b_id, p_c_id, p_d_id] @@ -364,114 +364,165 @@ class test_mail(common.TransactionCase): self.assertEqual(len(notif_ids), 4, 'mail.message: too much notifications created') self.assertEqual(set(msg_pids), set(test_pids), 'mail.message partner_ids incorrect') - # CASE2: reply to last comment (update its subject) with attachments + # ---------------------------------------- + # CASE2: reply to last comment with attachments + # ---------------------------------------- + + # 1. Update last comment subject, reply with attachments message.write({'subject': _subject}) compose_id = mail_compose.create(cr, uid, {'attachment_ids': [(0, 0, _attachments[0]), (0, 0, _attachments[1])]}, {'default_composition_mode': 'reply', 'default_model': 'mail.thread', 'default_res_id': self.group_pigs_id, 'default_parent_id': message.id}) compose = mail_compose.browse(cr, uid, compose_id) - - # Test: form view methods # Test: model, res_id, parent_id, content_subtype self.assertEqual(compose.model, 'mail.group', 'mail.compose.message incorrect model') self.assertEqual(compose.res_id, self.group_pigs_id, 'mail.compose.message incorrect res_id') self.assertEqual(compose.parent_id.id, message.id, 'mail.compose.message incorrect parent_id') self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message incorrect content_subtype') - # Post the comment, get created message + # 2. Post the comment, get created message mail_compose.send_mail(cr, uid, [compose_id]) group_pigs.refresh() message = group_pigs.message_ids[0] - # Test: subject as Re:.., body in html - self.assertEqual(message.subject, _subject_reply, 'mail.message incorrect subject') + # Test: mail.message: subject as Re:.., body in html + self.assertEqual(message.subject, _msg_reply, 'mail.message incorrect subject') self.assertIn('Administrator wrote:
Pigs rules
', message.body, 'mail.message body is incorrect') - # Test: attachments - for i in range(len(message.attachment_ids)): - self.assertEqual(message.attachment_ids[i].name, _attachments[i]['name'], 'mail.message attachment name incorrect') - self.assertEqual(message.attachment_ids[i].res_model, 'mail.group', 'mail.message attachment res_model incorrect') - self.assertEqual(message.attachment_ids[i].res_id, self.group_pigs_id, 'mail.message attachment res_id incorrect') - self.assertEqual(message.attachment_ids[i].datas.decode('base64'), _attachments[i]['datas'].decode('base64'), 'mail.message attachment data incorrect') + # Test: mail.message: attachments + for attach in message.attachment_ids: + self.assertEqual(attach.res_model, 'mail.group', 'mail.message attachment res_model incorrect') + self.assertEqual(attach.res_id, self.group_pigs_id, 'mail.message attachment res_id incorrect') + self.assertIn((attach.name, attach.datas.decode('base64')), _attachments_test, + 'mail.message attachment name / data incorrect') - # CASE3 - Create in mass_mail composition mode that should work with or without email_template installed + # ---------------------------------------- + # CASE3: mass_mail on Pigs and Bird + # ---------------------------------------- + + # 1. mass_mail on pigs and bird compose_id = mail_compose.create(cr, uid, {'subject': _subject, 'body': '${object.description}'}, {'default_composition_mode': 'mass_mail', 'default_model': 'mail.group', 'default_res_id': -1, - 'active_ids': [self.group_pigs_id]}) + 'active_ids': [self.group_pigs_id, group_bird_id]}) compose = mail_compose.browse(cr, uid, compose_id) + # Test: content_subtype is html + self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message content_subtype incorrect') - # Post the comment, get created message - mail_compose.send_mail(cr, uid, [compose_id], {'default_res_id': -1, 'active_ids': [self.group_pigs_id]}) + # 2. Post the comment, get created message for each group + mail_compose.send_mail(cr, uid, [compose_id], + context={'default_res_id': -1, 'active_ids': [self.group_pigs_id, group_bird_id]}) group_pigs.refresh() - message = group_pigs.message_ids[0] - # Test: last message on Pigs = last created message - test_msg = self.mail_message.browse(cr, uid, self.mail_message.search(cr, uid, [], limit=1))[0] - self.assertEqual(message.id, test_msg.id, 'Pigs did not receive its mass mailing message') + group_bird.refresh() + message1 = group_pigs.message_ids[0] + message2 = group_bird.message_ids[0] + # Test: Pigs and Bird did receive their message + test_msg_ids = self.mail_message.search(cr, uid, [], limit=2) + self.assertIn(message1.id, test_msg_ids, 'Pigs did not receive its mass mailing message') + self.assertIn(message2.id, test_msg_ids, 'Bird did not receive its mass mailing message') # Test: mail.message: subject, body - self.assertEqual(message.subject, _subject, 'mail.message subject is incorrect') - self.assertEqual(message.body, group_pigs.description, 'mail.message body is incorrect') + self.assertEqual(message1.subject, _subject, 'mail.message subject incorrect') + self.assertEqual(message1.body, group_pigs.description, 'mail.message body incorrect') + self.assertEqual(message2.subject, _subject, 'mail.message subject incorrect') + self.assertEqual(message2.body, group_bird.description, 'mail.message body incorrect') def test_30_message_read(self): """ Tests designed for message_read. """ # TDE NOTE: this test is not finished, as the message_read method is not fully specified. - # It wil be updated as soon as we have fixed specs ! - cr, uid = self.cr, self.uid + # It will be updated as soon as we have fixed specs ! + cr, uid = self.cr, self.uid group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id) + def _compare_structures(struct1, struct2, n=0): + # print '%scompare structure' % ('\t' * n) + self.assertEqual(len(struct1), len(struct2), 'message_read structure number of childs incorrect') + for x in range(len(struct1)): + # print '%s' % ('\t' * n), struct1[x]['id'], struct2[x]['id'], struct1[x].get('subject') or '' + self.assertEqual(struct1[x]['id'], struct2[x]['id'], 'message_read failure %s' % struct1[x].get('subject')) + _compare_structures(struct1[x]['child_ids'], struct2[x]['child_ids'], n + 1) + # print '%send compare' % ('\t' * n) - # Test message_read_tree_flatten that flattens a thread according to a given thread_level + # ---------------------------------------- + # CASE1: Flattening test + # ---------------------------------------- + + # Create dummy message structure import copy - tree = [{'id': 1, 'child_ids':[ - {'id': 3, 'child_ids': [] }, - {'id': 4, 'child_ids': [ - {'id': 5, 'child_ids': []}, - {'id': 12, 'child_ids': []}, - ] }, - {'id': 8, 'child_ids': [ - {'id': 10, 'child_ids': []}, - ] }, - ] }, - {'id': 2, 'child_ids': [ + tree = [{'id': 2, 'child_ids': [ + {'id': 6, 'child_ids': [ + {'id': 8, 'child_ids': []}, + ]}, + ]}, + {'id': 1, 'child_ids':[ {'id': 7, 'child_ids': [ {'id': 9, 'child_ids': []}, - ] }, - ] }, - {'id': 6, 'child_ids': [ - {'id': 11, 'child_ids': [] }, - ] }, + ]}, + {'id': 4, 'child_ids': [ + {'id': 10, 'child_ids': []}, + {'id': 5, 'child_ids': []}, + ]}, + {'id': 3, 'child_ids': []}, + ]}, ] + # Test: completely flat new_tree = self.mail_message.message_read_tree_flatten(cr, uid, copy.deepcopy(tree), 0, 0) - self.assertTrue(len(new_tree) == 12, 'Flattening wrongly produced') + self.assertEqual(len(new_tree), 10, 'message_read_tree_flatten wrong in flat') + # Test: 1 thread level + tree_test = [{'id': 2, 'child_ids': [ + {'id': 8, 'child_ids': []}, {'id': 6, 'child_ids': []}, + ]}, + {'id': 1, 'child_ids': [ + {'id': 10, 'child_ids': []}, {'id': 9, 'child_ids': []}, + {'id': 7, 'child_ids': []}, {'id': 5, 'child_ids': []}, + {'id': 4, 'child_ids': []}, {'id': 3, 'child_ids': []}, + ]}, + ] new_tree = self.mail_message.message_read_tree_flatten(cr, uid, copy.deepcopy(tree), 0, 1) - self.assertTrue(len(new_tree) == 3 and len(new_tree[0]['child_ids']) == 6 and len(new_tree[1]['child_ids']) == 2 and len(new_tree[2]['child_ids']) == 1, - 'Flattening wrongly produced') + _compare_structures(new_tree, tree_test) + # Test: 2 thread levels new_tree = self.mail_message.message_read_tree_flatten(cr, uid, copy.deepcopy(tree), 0, 2) - self.assertTrue(len(new_tree) == 3 and len(new_tree[0]['child_ids']) == 3 and len(new_tree[0]['child_ids'][1]) == 2, - 'Flattening wrongly produced') + _compare_structures(new_tree, tree) - # Add a few messages to pigs group - msgid1 = group_pigs.message_post(body='My Body', subject='1', parent_id=False) - msgid2 = group_pigs.message_post(body='My Body', subject='1-1', parent_id=msgid1) - msgid3 = group_pigs.message_post(body='My Body', subject='1-2', parent_id=msgid1) - msgid4 = group_pigs.message_post(body='My Body', subject='2', parent_id=False) - msgid5 = group_pigs.message_post(body='My Body', subject='1-1-1', parent_id=msgid2) - msgid6 = group_pigs.message_post(body='My Body', subject='2-1', parent_id=msgid4) + # ---------------------------------------- + # CASE2: message_read test + # ---------------------------------------- - # First try: read flat - first_try_ids = [msgid6, msgid5, msgid4, msgid3, msgid2, msgid1] - tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], thread_level=0) - self.assertTrue(all(elem['id'] in first_try_ids for elem in tree) and len(tree) == 6, - 'Incorrect structure and/or number of childs in purely flat message_read') + # 1. Add a few messages to pigs group + msgid1 = group_pigs.message_post(body='1', subject='1', parent_id=False) + msgid2 = group_pigs.message_post(body='2', subject='1-1', parent_id=msgid1) + msgid3 = group_pigs.message_post(body='3', subject='1-2', parent_id=msgid1) + msgid4 = group_pigs.message_post(body='4', subject='2', parent_id=False) + msgid5 = group_pigs.message_post(body='5', subject='1-1-1', parent_id=msgid2) + msgid6 = group_pigs.message_post(body='6', subject='2-1', parent_id=msgid4) - # Second try: read with thread_level 1 - tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], thread_level=1) - self.assertTrue(len(tree) == 2 and len(tree[1]['child_ids']) == 3, 'Incorrect number of child in message_read') + # Test: read all messages flat + tree_test = [{'id': msgid6, 'child_ids': []}, {'id': msgid5, 'child_ids': []}, + {'id': msgid4, 'child_ids': []}, {'id': msgid3, 'child_ids': []}, + {'id': msgid2, 'child_ids': []}, {'id': msgid1, 'child_ids': []}] + tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], thread_level=0, limit=10) + _compare_structures(tree, tree_test) + # Test: read with 1 level of thread + tree_test = [{'id': msgid4, 'child_ids': [{'id': msgid6, 'child_ids': []}, ]}, + {'id': msgid1, 'child_ids': [ + {'id': msgid5, 'child_ids': []}, {'id': msgid3, 'child_ids': []}, + {'id': msgid2, 'child_ids': []}, + ]}, + ] + tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], thread_level=1, limit=10) + _compare_structures(tree, tree_test) + # Test: read with 2 levels of thread + tree_test = [{'id': msgid4, 'child_ids': [{'id': msgid6, 'child_ids': []}, ]}, + {'id': msgid1, 'child_ids': [ + {'id': msgid3, 'child_ids': []}, + {'id': msgid2, 'child_ids': [{'id': msgid5, 'child_ids': []}, ]}, + ]}, + ] + tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], thread_level=2, limit=10) + _compare_structures(tree, tree_test) - # Third try: read with thread_level 2 - tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], thread_level=2) - self.assertTrue(len(tree) == 2 and len(tree[1]['child_ids']) == 2 and len(tree[1]['child_ids'][0]['child_ids']) == 1, 'Incorrect number of child in message_read') + # 2. Test expandables + # TDE FIXME: add those tests when expandables are specified and implemented def test_40_needaction(self): """ Tests for mail.message needaction. """ - cr, uid = self.cr, self.uid + cr, uid = self.cr, self.uid group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id) user_admin = self.res_users.browse(cr, uid, uid) @@ -480,7 +531,7 @@ class test_mail(common.TransactionCase): ('partner_id', '=', user_admin.partner_id.id), ('read', '=', False) ]) - na_count = self.mail_message._needaction_count(cr, uid, domain = []) + na_count = self.mail_message._needaction_count(cr, uid, domain=[]) self.assertEqual(len(notif_ids), na_count, 'unread notifications count does not match needaction count') # Post 4 message on group_pigs @@ -492,11 +543,11 @@ class test_mail(common.TransactionCase): ('partner_id', '=', user_admin.partner_id.id), ('read', '=', False) ]) - na_count = self.mail_message._needaction_count(cr, uid, domain = []) + na_count = self.mail_message._needaction_count(cr, uid, domain=[]) self.assertEqual(len(notif_ids), na_count, 'unread notifications count does not match needaction count') # Check there are 4 needaction on mail.message with particular domain - na_count = self.mail_message._needaction_count(cr, uid, domain = [('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)]) + na_count = self.mail_message._needaction_count(cr, uid, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)]) self.assertEqual(na_count, 4, 'posted message count does not match needaction count') def test_50_thread_parent_resolution(self): @@ -505,7 +556,7 @@ class test_mail(common.TransactionCase): group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id) msg1 = group_pigs.message_post(body='My Body', subject='1') msg2 = group_pigs.message_post(body='My Body', subject='2') - msg1, msg2 = self.mail_message.browse(cr, uid, [msg1,msg2]) + msg1, msg2 = self.mail_message.browse(cr, uid, [msg1, msg2]) self.assertTrue(msg1.message_id, "New message should have a proper message_id") # Reply to msg1, make sure the reply is properly attached using the various reply identification mechanisms diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index 3c93440642d..3153ccc2069 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -36,7 +36,7 @@ class mail_compose_message(osv.TransientModel): at model and view levels to provide specific features. The behavior of the wizard depends on the composition_mode field: - - 'reply': reply to a previous message. The wizard is pre-populated + - 'reply': reply to a previous message. The wizard is pre-populated via ``get_message_data``. - 'comment': new post on a record. The wizard is pre-populated via ``get_record_data`` @@ -63,7 +63,6 @@ class mail_compose_message(osv.TransientModel): - active_ids: record IDs - default_model or active_model """ - # get some important values from context if context is None: context = {} result = super(mail_compose_message, self).default_get(cr, uid, fields, context=context) @@ -81,7 +80,7 @@ class mail_compose_message(osv.TransientModel): elif composition_mode == 'comment' and model and res_id: vals = self.get_record_data(cr, uid, model, res_id, context=context) elif composition_mode == 'mass_mail' and model and active_ids: - vals = {'model': model, 'res_id': res_id} + vals = {'model': model, 'res_id': res_id, 'content_subtype': 'html'} else: vals = {'model': model, 'res_id': res_id} if composition_mode: @@ -114,10 +113,10 @@ class mail_compose_message(osv.TransientModel): _defaults = { 'composition_mode': 'comment', - 'content_subtype': lambda self,cr, uid, context={}: 'plain', - 'body_text': lambda self,cr, uid, context={}: False, - 'body': lambda self,cr, uid, context={}: '', - 'subject': lambda self,cr, uid, context={}: False, + 'content_subtype': lambda self, cr, uid, ctx={}: 'plain', + 'body_text': lambda self, cr, uid, ctx={}: False, + 'body': lambda self, cr, uid, ctx={}: '', + 'subject': lambda self, cr, uid, ctx={}: False, } def notify(self, cr, uid, newid, context=None): @@ -144,13 +143,10 @@ class mail_compose_message(osv.TransientModel): :param int message_id: id of the mail.message to which the user is replying. """ + if not message_id: + return {} if context is None: context = {} - result = {} - if not message_id: - return result - - current_user = self.pool.get('res.users').browse(cr, uid, uid, context=context) message_data = self.pool.get('mail.message').browse(cr, uid, message_id, context=context) # create subject @@ -159,15 +155,16 @@ class mail_compose_message(osv.TransientModel): if not (reply_subject.startswith('Re:') or reply_subject.startswith(re_prefix)): reply_subject = "%s %s" % (re_prefix, reply_subject) # create the reply in the body - reply_header = _('On %(date)s, %(sender_name)s wrote:') % { + reply_body = _('
On %(date)s, %(sender_name)s wrote:
%(body)s
') % { 'date': message_data.date if message_data.date else '', - 'sender_name': message_data.author_id.name } - reply_body = '
%s
%s
' % (reply_header, message_data.body) + 'sender_name': message_data.author_id.name, + 'body': message_data.body, + } # get partner_ids from original message partner_ids = [partner.id for partner in message_data.partner_ids] if message_data.partner_ids else [] # update the result - result.update({ + result = { 'model': message_data.model, 'res_id': message_data.res_id, 'parent_id': message_data.id, @@ -175,11 +172,11 @@ class mail_compose_message(osv.TransientModel): 'subject': reply_subject, 'partner_ids': partner_ids, 'content_subtype': 'html', - }) + } return result def toggle_content_subtype(self, cr, uid, ids, context=None): - """ hit toggle formatting mode button: calls onchange_formatting to + """ hit toggle formatting mode button: calls onchange_formatting to emulate an on_change, then writes the value to update the form. """ for record in self.browse(cr, uid, ids, context=context): content_st_new_value = 'plain' if record.content_subtype == 'html' else 'html' @@ -209,17 +206,18 @@ class mail_compose_message(osv.TransientModel): warning_msg += '\n- %s' % (partner.name) return {'warning': { 'title': _('Partners email addresses not found'), - 'message': warning_msg } + 'message': warning_msg, + } } def onchange_partner_ids(self, cr, uid, ids, value, context=None): - """ onchange_partner_ids (value format: [[6, False, [3, 4]]]). The + """ onchange_partner_ids (value format: [[6, 0, [3, 4]]]). The basic purpose of this method is to check that destination partners effectively have email addresses. Otherwise a warning is thrown. """ res = {'value': {}} if not value or not value[0] or not value[0][0] == 6: - return + return res.update(self._verify_partner_email(cr, uid, value[0][2], context=context)) return res @@ -227,7 +225,7 @@ class mail_compose_message(osv.TransientModel): # Cascade delete all attachments, as they are owned by the composition wizard for wizard in self.read(cr, uid, ids, ['attachment_ids'], context=context): self.pool.get('ir.attachment').unlink(cr, uid, wizard['attachment_ids'], context=context) - return super(mail_compose_message,self).unlink(cr, uid, ids, context=context) + return super(mail_compose_message, self).unlink(cr, uid, ids, context=context) def dummy(self, cr, uid, ids, context=None): """ TDE: defined to have buttons that do basically nothing. It is @@ -258,13 +256,13 @@ class mail_compose_message(osv.TransientModel): 'subject': wizard.subject if wizard.content_subtype == 'html' else False, 'body': wizard.body if wizard.content_subtype == 'html' else '
%s
' % tools.ustr(wizard.body_text), 'partner_ids': [(4, partner.id) for partner in wizard.partner_ids], - 'attachments': [(attach.name or attach.datas_fname, base64.b64decode(attach.datas)) for attach in wizard.attachment_ids], + 'attachments': [(attach.datas_fname or attach.name, base64.b64decode(attach.datas)) for attach in wizard.attachment_ids], } # mass mailing: render and override default values if mass_mail_mode and wizard.model: - email_dict = self.render_message(cr, uid, wizard, wizard.model, res_id, context=context) + email_dict = self.render_message(cr, uid, wizard, res_id, context=context) new_partner_ids = email_dict.pop('partner_ids', []) - post_values['partner_ids'] += new_partner_ids + post_values['partner_ids'] += [(4, partner_id) for partner_id in new_partner_ids] new_attachments = email_dict.pop('attachments', []) post_values['attachments'] += new_attachments post_values.update(email_dict) @@ -273,14 +271,13 @@ class mail_compose_message(osv.TransientModel): return {'type': 'ir.actions.act_window_close'} - def render_message(self, cr, uid, wizard, model, res_id, context=None): - """ Generate an email from the template for given (model, res_id) pair. - This method is meant to be inherited by email_template that will - produce a more complete dictionary, with email_to, ... - """ + def render_message(self, cr, uid, wizard, res_id, context=None): + """ Generate an email from the template for given (wizard.model, res_id) + pair. This method is meant to be inherited by email_template that + will produce a more complete dictionary. """ return { - 'subject': self.render_template(cr, uid, wizard.subject, model, res_id, context), - 'body': self.render_template(cr, uid, wizard.body, model, res_id, context), + 'subject': self.render_template(cr, uid, wizard.subject, wizard.model, res_id, context), + 'body': self.render_template(cr, uid, wizard.body, wizard.model, res_id, context), } def render_template(self, cr, uid, template, model, res_id, context=None): @@ -302,8 +299,8 @@ class mail_compose_message(osv.TransientModel): def merge(match): exp = str(match.group()[2:-1]).strip() result = eval(exp, { - 'user' : self.pool.get('res.users').browse(cr, uid, uid, context=context), - 'object' : self.pool.get(model).browse(cr, uid, res_id, context=context), + 'user': self.pool.get('res.users').browse(cr, uid, uid, context=context), + 'object': self.pool.get(model).browse(cr, uid, res_id, context=context), 'context': dict(context), # copy context to prevent side-effects of eval }) return result and tools.ustr(result) or ''