diff --git a/addons/mail/__init__.py b/addons/mail/__init__.py
index 003369be38f..b34993ffd32 100644
--- a/addons/mail/__init__.py
+++ b/addons/mail/__init__.py
@@ -22,12 +22,12 @@
import mail_message_subtype
import mail_alias
import mail_followers
+import mail_vote
import mail_favorite
import mail_message
import mail_mail
import mail_thread
import mail_group
-import mail_vote
import res_partner
import res_users
import report
diff --git a/addons/mail/data/mail_demo.xml b/addons/mail/data/mail_demo.xml
index 2ab391a3f02..f189b10650c 100644
--- a/addons/mail/data/mail_demo.xml
+++ b/addons/mail/data/mail_demo.xml
@@ -7,6 +7,7 @@
none
+
mail.group
@@ -15,7 +16,6 @@ This month you also get 250 EUR of eco-vouchers if you have been in the company
comment
-
mail.group
@@ -24,7 +24,6 @@ This month you also get 250 EUR of eco-vouchers if you have been in the company
comment
-
mail.group
@@ -33,7 +32,7 @@ This month you also get 250 EUR of eco-vouchers if you have been in the company
comment
-
+
mail.group
@@ -41,8 +40,8 @@ This month you also get 250 EUR of eco-vouchers if you have been in the company
comment
+
-
mail.group
@@ -52,5 +51,196 @@ This month you also get 250 EUR of eco-vouchers if you have been in the company
+
+
+ Hello Demo User! I was wondering whether you had some issues with our secret task about putting cats everywhere in OpenERP.
+ comment
+
+
+
+
+
+ No specific issues, I think everything is clear.
+
+ comment
+
+
+
+
+
+ Ow, just to be sure... we were talking about lolcats, right ?
+
+ comment
+
+
+
+
+
+ Absolutely!
+
+ comment
+
+
+
+
+
+
+
+ Plan to install OpenERP
+ mail.message
+
+ <![CDATA[Email0 inquiry]]>
+ <div><font size="2">Hello,</font></div><div><font size="2"><br></font></div>
+ <div><font size="2">I am interested in your company's product and I plan to install OpenERP for my company and affordable price.</font></div><br/>
+ <div><font size="2">Can you please send me services catalogue?</font></div><br/>
+ Sophie
+
+ email
+
+
+
+
+ ir.attachment
+ bWlncmF0aW9uIHRlc3Q=
+ catalogue 2012.pdf
+ catalogue 2012.pdf
+
+
+ Re: Plan to install OpenERP
+
+ Dear Customer,<br/>
+ Thanks for showing interest in our products.<br/>
+ We have attached the catalogue,<br/>
+ We would like to know your interests, so let us know when we can call you for more details.<br/>
+ Regards
+
+ comment
+
+
+
+
+
+
+
+
+
+ ir.attachment
+ bWlncmF0aW9uIHRlc3Q=
+ migration.doc
+ migration.doc
+
+
+ ir.attachment
+
+ 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
+ CAEAIAQAgP/Z
+
+ activity graph 2012.jpg
+ activity graph 2012
+
+
+ mail.group
+
+ Hi,<br/>
+ The beta OpenERP 7 is scheduled for November 12.<br/>
+ You will find attached the document for migration from version 6 to 7, and the activity graph for the current year. Good reading.<br/>
+ Sincerely
+
+ comment
+
+
+
+
+
+
+ mail.group
+
+ Thank you,<br/>
+ Could you prepare and send us also the document for version 7.1 which will come soon?<br/>
+ Sincerely
+
+ comment
+
+
+
+
+
+
+
+
+ mail.group
+ I changed the infrastructure of networks, if there are still changes to be made please do not hesitate to contact me.
+ comment
+
+
+
+
+
+ mail.group
+ Thank you, the networks is perfect now ! Could you add a IP phone for Jhon ?
+
+ comment
+
+
+
+
+
+ mail.group
+ It's right, his internal phone number is 0093
+
+ comment
+
+
+
+
+
diff --git a/addons/mail/mail_followers.py b/addons/mail/mail_followers.py
index e8cf2648f29..6a8b24f85a8 100644
--- a/addons/mail/mail_followers.py
+++ b/addons/mail/mail_followers.py
@@ -61,10 +61,10 @@ class mail_notification(osv.Model):
_columns = {
'partner_id': fields.many2one('res.partner', string='Contact',
- ondelete='cascade', required=True),
- 'read': fields.boolean('Read'),
+ ondelete='cascade', required=True, select=1),
+ 'read': fields.boolean('Read', select=1),
'message_id': fields.many2one('mail.message', string='Message',
- ondelete='cascade', required=True),
+ ondelete='cascade', required=True, select=1),
}
_defaults = {
@@ -102,7 +102,7 @@ class mail_notification(osv.Model):
# some messages do not have notifications: find which one, create notification, update read status
exist_notification = dict.fromkeys(msg_ids, False)
for notification in self.browse(cr, uid, notif_ids, context=context):
- exist_notification[notification.message_id] = True
+ exist_notification[notification.message_id.id] = True
for msg_id in exist_notification.keys():
self.create(cr, uid, {'partner_id': user_pid, 'read': read, 'message_id': msg_id}, context=context)
return self.write(cr, uid, notif_ids, {'read': read}, context=context)
diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py
index bd0ade31b68..cdf61b043ec 100644
--- a/addons/mail/mail_message.py
+++ b/addons/mail/mail_message.py
@@ -50,8 +50,9 @@ class mail_message(osv.Model):
_description = 'Message'
_inherit = ['ir.needaction_mixin']
_order = 'id desc'
+ _rec_name = 'record_name'
- _message_read_limit = 10
+ _message_read_limit = 30
_message_read_fields = ['id', 'parent_id', 'model', 'res_id', 'body', 'subject', 'date', 'to_read', 'email_from',
'type', 'vote_user_ids', 'attachment_ids', 'author_id', 'partner_ids', 'record_name', 'favorite_user_ids']
_message_record_name_length = 18
@@ -120,21 +121,26 @@ class mail_message(osv.Model):
"message, comment for other messages such as user replies"),
'email_from': fields.char('From',
help="Email address of the sender. This field is set when no matching partner is found for incoming emails."),
- 'author_id': fields.many2one('res.partner', 'Author',
+ 'author_id': fields.many2one('res.partner', 'Author', select=1,
+ ondelete='set null',
help="Author of the message. If not set, email_from may hold an email address that did not match any partner."),
'partner_ids': fields.many2many('res.partner', string='Recipients'),
'notified_partner_ids': fields.many2many('res.partner', 'mail_notification',
- 'message_id', 'partner_id', 'Recipients'),
+ 'message_id', 'partner_id', 'Notified partners',
+ help='Partners that have a notification pushing this message in their mailboxes'),
'attachment_ids': fields.many2many('ir.attachment', 'message_attachment_rel',
'message_id', 'attachment_id', 'Attachments'),
- 'parent_id': fields.many2one('mail.message', 'Parent Message', select=True, ondelete='set null', help="Initial thread message."),
+ 'parent_id': fields.many2one('mail.message', 'Parent Message', select=True,
+ ondelete='set null', help="Initial thread message."),
'child_ids': fields.one2many('mail.message', 'parent_id', 'Child Messages'),
'model': fields.char('Related Document Model', size=128, select=1),
'res_id': fields.integer('Related Document ID', select=1),
'record_name': fields.function(_get_record_name, type='char',
store=True, string='Message Record Name',
help="Name get of the related document."),
- 'notification_ids': fields.one2many('mail.notification', 'message_id', 'Notifications'),
+ 'notification_ids': fields.one2many('mail.notification', 'message_id',
+ string='Notifications',
+ help='Technical field holding the message notifications. Use notified_partner_ids to access notified partners.'),
'subject': fields.char('Subject'),
'date': fields.datetime('Date'),
'message_id': fields.char('Message-Id', help='Message unique identifier', select=1, readonly=1),
@@ -142,7 +148,8 @@ class mail_message(osv.Model):
'to_read': fields.function(_get_to_read, fnct_search=_search_to_read,
type='boolean', string='To read',
help='Functional field to search for messages the current user has to read'),
- 'subtype_id': fields.many2one('mail.message.subtype', 'Subtype'),
+ 'subtype_id': fields.many2one('mail.message.subtype', 'Subtype',
+ ondelete='set null', select=1,),
'vote_user_ids': fields.many2many('res.users', 'mail_vote',
'message_id', 'user_id', string='Votes',
help='Users that voted for this message'),
@@ -200,67 +207,99 @@ class mail_message(osv.Model):
# Message loading for web interface
#------------------------------------------------------
- def _message_get_dict(self, cr, uid, message, context=None):
- """ Return a dict representation of the message. This representation is
- used in the JS client code, to display the messages.
+ def _message_read_dict_postprocess(self, cr, uid, messages, message_tree, context=None):
+ """ Post-processing on values given by message_read. This method will
+ handle partners in batch to avoid doing numerous queries.
- :param dict message: read result of a mail.message
+ :param list messages: list of message, as get_dict result
+ :param dict message_tree: {[msg.id]: msg browse record}
"""
- # TDE note: this method should be optimized, to lessen the number of queries, will be done ASAP
- is_author = False
- if message['author_id']:
- is_author = message['author_id'][0] == self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0]
- author_id = message['author_id']
- elif message['email_from']:
- author_id = (0, message['email_from'])
+ res_partner_obj = self.pool.get('res.partner')
+ ir_attachment_obj = self.pool.get('ir.attachment')
+ pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0]
- has_voted = False
- if uid in message.get('vote_user_ids'):
- has_voted = True
+ # 1. Aggregate partners (author_id and partner_ids) and attachments
+ partner_ids = set()
+ attachment_ids = set()
+ for key, message in message_tree.iteritems():
+ if message.author_id:
+ partner_ids |= set([message.author_id.id])
+ if message.partner_ids:
+ partner_ids |= set([partner.id for partner in message.partner_ids])
+ if message.attachment_ids:
+ attachment_ids |= set([attachment.id for attachment in message.attachment_ids])
- is_favorite = False
- if uid in message.get('favorite_user_ids'):
- is_favorite = True
+ # Filter author_ids uid can see
+ # partner_ids = self.pool.get('res.partner').search(cr, uid, [('id', 'in', partner_ids)], context=context)
+ partners = res_partner_obj.name_get(cr, uid, list(partner_ids), context=context)
+ partner_tree = dict((partner[0], partner) for partner in partners)
- is_private = True
- if message.get('model') and message.get('res_id'):
- is_private = False
+ # 2. Attachments
+ attachments = ir_attachment_obj.read(cr, uid, list(attachment_ids), ['id', 'datas_fname'], context=context)
+ attachments_tree = dict((attachment['id'], {'id': attachment['id'], 'filename': attachment['datas_fname']}) for attachment in attachments)
- try:
- attachment_ids = [{'id': attach[0], 'name': attach[1]} for attach in self.pool.get('ir.attachment').name_get(cr, uid, message['attachment_ids'], context=context)]
- except (orm.except_orm, osv.except_osv):
- attachment_ids = []
-
- # TDE note: should we send partner_ids ?
- # TDE note: shouldn't we separated followers and other partners ? costly to compute maybe ,
- try:
- partner_ids = self.pool.get('res.partner').name_get(cr, uid, message['partner_ids'], context=context)
- except (orm.except_orm, osv.except_osv):
+ # 3. Update message dictionaries
+ for message_dict in messages:
+ message_id = message_dict.get('id')
+ message = message_tree[message_id]
+ if message.author_id:
+ author = partner_tree[message.author_id.id]
+ else:
+ author = (0, message.email_from)
partner_ids = []
+ for partner in message.partner_ids:
+ if partner.id in partner_tree:
+ partner_ids.append(partner_tree[partner.id])
+ attachment_ids = []
+ for attachment in message.attachment_ids:
+ if attachment.id in attachments_tree:
+ attachment_ids.append(attachments_tree[attachment.id])
+ message_dict.update({
+ 'is_author': pid == author[0],
+ 'author_id': author,
+ 'partner_ids': partner_ids,
+ 'attachment_ids': attachment_ids,
+ })
+ return True
- return {
- 'id': message['id'],
- 'type': message['type'],
- 'attachment_ids': attachment_ids,
- 'body': message['body'],
- 'model': message['model'],
- 'res_id': message['res_id'],
- 'record_name': message['record_name'],
- 'subject': message['subject'],
- 'date': message['date'],
- 'author_id': author_id,
- 'is_author': is_author,
- 'partner_ids': partner_ids,
- 'parent_id': False,
- 'vote_nb': len(message['vote_user_ids']),
- 'has_voted': has_voted,
- 'is_private': is_private,
- 'is_favorite': is_favorite,
- 'to_read': message['to_read'],
- }
+ def _message_read_dict(self, cr, uid, message, parent_id=False, context=None):
+ """ Return a dict representation of the message. This representation is
+ used in the JS client code, to display the messages. Partners and
+ attachments related stuff will be done in post-processing in batch.
- def _message_read_add_expandables(self, cr, uid, message_list, read_messages,
- thread_level=0, message_loaded_ids=[], domain=[], parent_id=False, context=None, limit=None):
+ :param dict message: mail.message browse record
+ """
+ # private message: no model, no res_id
+ is_private = False
+ if not message.model or not message.res_id:
+ is_private = True
+ # votes and favorites: res.users ids, no prefetching should be done
+ vote_nb = len(message.vote_user_ids)
+ has_voted = uid in [user.id for user in message.vote_user_ids]
+ is_favorite = uid in [user.id for user in message.favorite_user_ids]
+
+ return {'id': message.id,
+ 'type': message.type,
+ 'body': message.body,
+ 'model': message.model,
+ 'res_id': message.res_id,
+ 'record_name': message.record_name,
+ 'subject': message.subject,
+ 'date': message.date,
+ 'to_read': message.to_read,
+ 'parent_id': parent_id,
+ 'is_private': is_private,
+ 'author_id': False,
+ 'is_author': False,
+ 'partner_ids': [],
+ 'vote_nb': vote_nb,
+ 'has_voted': has_voted,
+ 'is_favorite': is_favorite,
+ 'attachment_ids': [],
+ }
+
+ def _message_read_add_expandables(self, cr, uid, messages, message_tree, parent_tree,
+ message_unload_ids=[], thread_level=0, domain=[], parent_id=False, context=None):
""" Create expandables for message_read, to load new messages.
1. get the expandable for new threads
if display is flat (thread_level == 0):
@@ -275,96 +314,82 @@ class mail_message(osv.Model):
for each hole in the child list based on message displayed,
create an expandable
- :param list message_list:list of message structure for the Chatter
+ :param list messages: list of message structure for the Chatter
widget to which expandables are added
- :param dict read_messages: dict [id]: read result of the messages to
- easily have access to their values, given their ID
+ :param dict message_tree: dict [id]: browse record of this message
+ :param dict parent_tree: dict [parent_id]: [child_ids]
+ :param list message_unload_ids: list of message_ids we do not want
+ to load
:return bool: True
"""
- def _get_expandable(domain, message_nb, parent_id, id, model):
+ def _get_expandable(domain, message_nb, parent_id, max_limit):
return {
'domain': domain,
'nb_messages': message_nb,
'type': 'expandable',
'parent_id': parent_id,
- 'id': id,
- # TDE note: why do we need model sometimes, and sometimes not ???
- 'model': model,
+ 'max_limit': max_limit,
}
- # all_not_loaded_ids = []
- id_list = sorted(read_messages.keys())
- if not id_list:
- return message_list
+ if not messages:
+ return True
+ message_ids = sorted(message_tree.keys())
# 1. get the expandable for new threads
if thread_level == 0:
- exp_domain = domain + [('id', '<', min(message_loaded_ids + id_list))]
+ exp_domain = domain + [('id', '<', min(message_unload_ids + message_ids))]
else:
- exp_domain = domain + ['!', ('id', 'child_of', message_loaded_ids + id_list)]
+ exp_domain = domain + ['!', ('id', 'child_of', message_unload_ids + parent_tree.keys())]
ids = self.search(cr, uid, exp_domain, context=context, limit=1)
if ids:
- message_list.append(_get_expandable(exp_domain, -1, parent_id, -1, None))
+ # inside a thread: prepend
+ if parent_id:
+ messages.insert(0, _get_expandable(exp_domain, -1, parent_id, True))
+ # new threads: append
+ else:
+ messages.append(_get_expandable(exp_domain, -1, parent_id, True))
# 2. get the expandables for new messages inside threads if display is not flat
if thread_level == 0:
return True
- for message_id in id_list:
- message = read_messages[message_id]
+ for message_id in message_ids:
+ message = message_tree[message_id]
- # message is not a thread header (has a parent_id)
- # TDE note: parent_id is false is there is a parent we can not see -> ok
- if message.get('parent_id'):
+ # generate only for thread header messages (TDE note: parent_id may be False is uid cannot see parent_id, seems ok)
+ if message.parent_id:
continue
- # TDE note: check search is correctly implemented in mail.message
- not_loaded_ids = self.search(cr, uid, [
- ('id', 'child_of', message['id']),
- ('id', 'not in', message_loaded_ids),
- ], context=context, limit=self._message_read_more_limit)
- if not not_loaded_ids:
+ # check there are message for expandable
+ child_ids = set([child.id for child in message.child_ids]) - set(message_unload_ids)
+ child_ids = sorted(list(child_ids), reverse=True)
+ if not child_ids:
continue
- # all_not_loaded_ids += not_loaded_ids
- # group childs not read
- id_min, id_max, nb = max(not_loaded_ids), 0, 0
- for not_loaded_id in not_loaded_ids:
- if not read_messages.get(not_loaded_id):
+ # make groups of unread messages
+ id_min, id_max, nb = max(child_ids), 0, 0
+ for child_id in child_ids:
+ if not child_id in message_ids:
nb += 1
- if id_min > not_loaded_id:
- id_min = not_loaded_id
- if id_max < not_loaded_id:
- id_max = not_loaded_id
+ if id_min > child_id:
+ id_min = child_id
+ if id_max < child_id:
+ id_max = child_id
elif nb > 0:
exp_domain = [('id', '>=', id_min), ('id', '<=', id_max), ('id', 'child_of', message_id)]
- message_list.append(_get_expandable(exp_domain, nb, message_id, id_min, message.get('model')))
- id_min, id_max, nb = max(not_loaded_ids), 0, 0
+ messages.append(_get_expandable(exp_domain, nb, message_id, False))
+ id_min, id_max, nb = max(child_ids), 0, 0
else:
- id_min, id_max, nb = max(not_loaded_ids), 0, 0
+ id_min, id_max, nb = max(child_ids), 0, 0
if nb > 0:
exp_domain = [('id', '>=', id_min), ('id', '<=', id_max), ('id', 'child_of', message_id)]
- message_list.append(_get_expandable(exp_domain, nb, message_id, id_min, message.get('model')))
-
- # message_loaded_ids = list(set(message_loaded_ids + read_messages.keys() + all_not_loaded_ids))
+ idx = [msg.get('id') for msg in messages].index(message_id) + 1
+ # messages.append(_get_expandable(exp_domain, nb, message_id, id_min))
+ messages.insert(idx, _get_expandable(exp_domain, nb, message_id, False))
return True
- def _get_parent(self, cr, uid, message, context=None):
- """ Tools method that tries to get the parent of a mail.message. If
- no parent, or if uid has no access right on the parent, False
- is returned.
-
- :param dict message: read result of a mail.message
- """
- if not message['parent_id']:
- return False
- parent_id = message['parent_id'][0]
- try:
- return self.read(cr, uid, parent_id, self._message_read_fields, context=context)
- except (orm.except_orm, osv.except_osv):
- return False
-
- def message_read(self, cr, uid, ids=None, domain=None, message_unload_ids=None, thread_level=0, context=None, parent_id=False, limit=None):
+ def message_read(self, cr, uid, ids=None, domain=None, message_unload_ids=None,
+ thread_level=0, context=None, parent_id=False, limit=None):
""" Read messages from mail.message, and get back a list of structured
messages to be displayed as discussion threads. If IDs is set,
fetch these records. Otherwise use the domain to fetch messages.
@@ -388,46 +413,56 @@ class mail_message(osv.Model):
ancestors and expandables
:return list: list of message structure for the Chatter widget
"""
- # print 'message_read', ids, domain, message_unload_ids, thread_level, context, parent_id, limit
assert thread_level in [0, 1], 'message_read() thread_level should be 0 (flat) or 1 (1 level of thread); given %s.' % thread_level
domain = domain if domain is not None else []
message_unload_ids = message_unload_ids if message_unload_ids is not None else []
if message_unload_ids:
domain += [('id', 'not in', message_unload_ids)]
limit = limit or self._message_read_limit
- read_messages = {}
+ message_tree = {}
message_list = []
+ parent_tree = {}
# no specific IDS given: fetch messages according to the domain, add their parents if uid has access to
if ids is None:
ids = self.search(cr, uid, domain, context=context, limit=limit)
- for message in self.read(cr, uid, ids, self._message_read_fields, context=context):
- message_id = message['id']
- # if not in tree and not in message_loaded list
- if not message_id in read_messages and not message_id in message_unload_ids:
- read_messages[message_id] = message
- message_list.append(self._message_get_dict(cr, uid, message, context=context))
+ # fetch parent if threaded, sort messages
+ for message in self.browse(cr, uid, ids, context=context):
+ message_id = message.id
+ if message_id in message_tree:
+ continue
+ message_tree[message_id] = message
- # get the older ancestor the user can read, update its ancestor field
- if not thread_level:
- message_list[-1]['parent_id'] = parent_id
- continue
- parent = self._get_parent(cr, uid, message, context=context)
- while parent and parent.get('id') != parent_id:
- message_list[-1]['parent_id'] = parent.get('id')
- message = parent
- parent = self._get_parent(cr, uid, message, context=context)
- # if in thread: add its ancestor to the list of messages
- if not message['id'] in read_messages and not message['id'] in message_unload_ids:
- read_messages[message['id']] = message
- message_list.append(self._message_get_dict(cr, uid, message, context=context))
+ # find parent_id
+ if thread_level == 0:
+ tree_parent_id = parent_id
+ else:
+ tree_parent_id = message_id
+ parent = message
+ while parent.parent_id and parent.parent_id.id != parent_id:
+ parent = parent.parent_id
+ tree_parent_id = parent.id
+ if not parent.id in message_tree:
+ message_tree[parent.id] = parent
+ # newest messages first
+ parent_tree.setdefault(tree_parent_id, [])
+ if tree_parent_id != message_id:
+ parent_tree[tree_parent_id].append(self._message_read_dict(cr, uid, message_tree[message_id], parent_id=tree_parent_id, context=context))
+
+ if thread_level:
+ for key, message_id_list in parent_tree.iteritems():
+ message_id_list.sort(key=lambda item: item['id'])
+ message_id_list.insert(0, self._message_read_dict(cr, uid, message_tree[key], context=context))
+
+ parent_list = parent_tree.items()
+ parent_list = sorted(parent_list, key=lambda item: max([msg.get('id') for msg in item[1]]) if item[1] else item[0], reverse=True)
+ message_list = [message for (key, msg_list) in parent_list for message in msg_list]
# get the child expandable messages for the tree
- message_list = sorted(message_list, key=lambda k: k['id'])
- self._message_read_add_expandables(cr, uid, message_list, read_messages, thread_level=thread_level,
- message_loaded_ids=message_unload_ids, domain=domain, parent_id=parent_id, context=context, limit=limit)
-
+ self._message_read_dict_postprocess(cr, uid, message_list, message_tree, context=context)
+ self._message_read_add_expandables(cr, uid, message_list, message_tree, parent_tree,
+ thread_level=thread_level, message_unload_ids=message_unload_ids, domain=domain, parent_id=parent_id, context=context)
return message_list
# TDE Note: do we need this ?
@@ -461,7 +496,6 @@ class mail_message(osv.Model):
- otherwise: remove the id
"""
# Rules do not apply to administrator
- # print '_search', uid, args
if uid == SUPERUSER_ID:
return super(mail_message, self)._search(cr, uid, args, offset=offset, limit=limit, order=order,
context=context, count=count, access_rights_uid=access_rights_uid)
diff --git a/addons/mail/mail_message_view.xml b/addons/mail/mail_message_view.xml
index 93b35797fd5..7d2e9483773 100644
--- a/addons/mail/mail_message_view.xml
+++ b/addons/mail/mail_message_view.xml
@@ -59,21 +59,26 @@
+
+
-
+
+
@@ -84,7 +89,6 @@
formtree,form
- {'search_default_to_read_message':True}
diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py
index f3203336615..ee00b88dbd9 100644
--- a/addons/mail/mail_thread.py
+++ b/addons/mail/mail_thread.py
@@ -74,17 +74,17 @@ class mail_thread(osv.AbstractModel):
- message_unread: has uid unread message for the document
- message_summary: html snippet summarizing the Chatter for kanban views """
res = dict((id, dict(message_unread=False, message_summary='')) for id in ids)
+ user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
- # search for unread messages, by reading directly mail.notification, as SUPERUSER
- notif_obj = self.pool.get('mail.notification')
- notif_ids = notif_obj.search(cr, SUPERUSER_ID, [
- ('partner_id.user_ids', 'in', [uid]),
- ('message_id.res_id', 'in', ids),
- ('message_id.model', '=', self._name),
- ('read', '=', False)
- ], context=context)
- for notif in notif_obj.browse(cr, SUPERUSER_ID, notif_ids, context=context):
- res[notif.message_id.res_id]['message_unread'] = True
+ # search for unread messages, directly in SQL to improve performances
+ cr.execute(""" SELECT m.res_id FROM mail_message m
+ RIGHT JOIN mail_notification n
+ ON (n.message_id = m.id AND n.partner_id = %s AND n.read = False)
+ WHERE m.model = %s AND m.res_id in %s""",
+ (user_pid, self._name, tuple(ids),))
+ msg_ids = [result[0] for result in cr.fetchall()]
+ for msg_id in msg_ids:
+ res[msg_id]['message_unread'] = True
for thread in self.browse(cr, uid, ids, context=context):
cls = res[thread.id]['message_unread'] and ' class="oe_kanban_mail_new"' or ''
diff --git a/addons/mail/mail_thread_view.xml b/addons/mail/mail_thread_view.xml
index 07b24725b05..b568cacfa53 100644
--- a/addons/mail/mail_thread_view.xml
+++ b/addons/mail/mail_thread_view.xml
@@ -4,42 +4,123 @@
Inboxmail.wall
-
+ mail.message
+ {
+ 'default_model': 'res.users',
+ 'default_res_id': uid
+ }
+
+
+
+ Hurray! No message here!
+
+ Congratulations! You have processed all your incoming emails.
+ Remember that when you receive new messages, you can either
+ set them as done, or use the star button to send those messages
+ into your Todo mailbox.
+
+ There are no new messages matching the current search
+ that were directly sent to you.
+ Remember you can always uncheck the Read filter to see
+ messages sent to you that you already read.
+
+ Congratulations! You have processed all your messages in
+ your todo list, or at least those matching your search.
+ Remember you can always search for read messages by
+ unchecking the Read filter.
+
@@ -247,9 +247,9 @@
mail.compose_message.button_top_bar
render of the button on the user bar for open wizard compose message
-->
-
-
-
+
+
+
@@ -257,12 +257,11 @@
Template used to display Like/Unlike in a mail.message
-->
-
8
-
+ likeunlike
diff --git a/addons/mail/tests/test_mail.py b/addons/mail/tests/test_mail.py
index 8e880a611a7..aea67dcdc13 100644
--- a/addons/mail/tests/test_mail.py
+++ b/addons/mail/tests/test_mail.py
@@ -576,7 +576,7 @@ class test_mail(TestMailMockups):
cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
pigs_domain = [('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)]
- # Data: create a discussion in Pigs (2 messages, one with 2 and one with 3 answers)
+ # Data: create a discussion in Pigs (3 threads, with respectively 0, 4 and 4 answers)
msg_id0 = self.group_pigs.message_post(body='0', subtype='mt_comment')
msg_id1 = self.group_pigs.message_post(body='1', subtype='mt_comment')
msg_id2 = self.group_pigs.message_post(body='2', subtype='mt_comment')
@@ -588,7 +588,8 @@ class test_mail(TestMailMockups):
msg_id8 = self.group_pigs.message_post(body='2-1-1', subtype='mt_comment', parent_id=msg_id4)
msg_id9 = self.group_pigs.message_post(body='1-1-1', subtype='mt_comment', parent_id=msg_id3)
msg_id10 = self.group_pigs.message_post(body='2-1-1', subtype='mt_comment', parent_id=msg_id4)
- msg_ids = [msg_id0, msg_id1, msg_id2, msg_id3, msg_id4, msg_id5, msg_id6, msg_id7, msg_id8, msg_id9, msg_id10]
+ msg_ids = [msg_id10, msg_id9, msg_id8, msg_id7, msg_id6, msg_id5, msg_id4, msg_id3, msg_id2, msg_id1, msg_id0]
+ ordered_msg_ids = [msg_id2, msg_id4, msg_id6, msg_id8, msg_id10, msg_id1, msg_id3, msg_id5, msg_id7, msg_id9, msg_id0]
# Test: read some specific ids
read_msg_list = self.mail_message.message_read(cr, uid, ids=msg_ids[2:4], domain=[('body', 'like', 'dummy')])
@@ -601,7 +602,8 @@ class test_mail(TestMailMockups):
self.assertEqual(msg_ids, read_msg_ids, 'message_read flat with domain on Pigs should equal all messages of Pigs')
read_msg_list = self.mail_message.message_read(cr, uid, domain=pigs_domain, limit=200, thread_level=1)
read_msg_ids = [msg.get('id') for msg in read_msg_list]
- self.assertEqual(msg_ids, read_msg_ids, 'message_read threaded with domain on Pigs should equal all messages of Pigs')
+ self.assertEqual(ordered_msg_ids, read_msg_ids,
+ 'message_read threaded with domain on Pigs should equal all messages of Pigs, and sort them with newer thread first, last message last in thread')
# ----------------------------------------
# CASE1: message_read with domain, threaded
@@ -611,6 +613,8 @@ class test_mail(TestMailMockups):
# Do: read last message, threaded
read_msg_list = self.mail_message.message_read(cr, uid, domain=pigs_domain, limit=1, thread_level=1)
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
+ # TDE TODO: test expandables order
+ type_list = map(lambda item: item.get('type'), read_msg_list)
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables
self.assertEqual(len(read_msg_list), 4, 'message_read on last Pigs message should return 2 messages and 2 expandables')
self.assertEqual(set([msg_id2, msg_id10]), set(read_msg_ids), 'message_read on the last Pigs message should also get its parent')
@@ -618,7 +622,7 @@ class test_mail(TestMailMockups):
# Data: get expandables
new_threads_exp, new_msg_exp = None, None
for msg in read_msg_list:
- if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('id') == -1:
+ if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('max_limit'):
new_threads_exp = msg
elif msg.get('type') == 'expandable':
new_msg_exp = msg
@@ -630,12 +634,20 @@ class test_mail(TestMailMockups):
self.assertIn(('id', 'child_of', msg_id2), domain, 'new messages expandable domain should contain a child_of condition')
self.assertIn(('id', '>=', msg_id4), domain, 'new messages expandable domain should contain an id greater than condition')
self.assertIn(('id', '<=', msg_id8), domain, 'new messages expandable domain should contain an id less than condition')
- self.assertEqual(new_msg_exp.get('parent_id'), msg_id2, 'new messages expandable should have ancestor_id set to the thread header')
- # Do: message_read with domain, thread_level=0, parent_id=msg_id2 (should be imposed by JS)
- read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=200, thread_level=0, parent_id=msg_id2)
+ self.assertEqual(new_msg_exp.get('parent_id'), msg_id2, 'new messages expandable should have parent_id set to the thread header')
+ # Do: message_read with domain, thread_level=0, parent_id=msg_id2 (should be imposed by JS), 2 messages
+ read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=2, thread_level=0, parent_id=msg_id2)
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
- # Test: other message in thread have been fetch
- self.assertEqual(set([msg_id4, msg_id6, msg_id8]), set(read_msg_ids), 'message_read in Pigs thread should return all the previous messages')
+ new_msg_exp = [msg for msg in read_msg_list if msg.get('type') == 'expandable'][0]
+ # Test: structure content, 2 messages and 1 thread expandable
+ self.assertEqual(len(read_msg_list), 3, 'message_read in Pigs thread should return 2 messages and 1 expandables')
+ self.assertEqual(set([msg_id6, msg_id8]), set(read_msg_ids), 'message_read in Pigs thread should return 2 more previous messages in thread')
+ # Do: read the last message
+ read_msg_list = self.mail_message.message_read(cr, uid, domain=new_msg_exp.get('domain'), limit=2, thread_level=0, parent_id=msg_id2)
+ read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
+ # Test: structure content, 1 message
+ self.assertEqual(len(read_msg_list), 1, 'message_read in Pigs thread should return 1 message')
+ self.assertEqual(set([msg_id4]), set(read_msg_ids), 'message_read in Pigs thread should return the last message in thread')
# Do: fetch a new thread, domain from expandable
self.assertIsNotNone(new_threads_exp, 'message_read on last Pigs message should have returned a new threads expandable')
@@ -643,7 +655,7 @@ class test_mail(TestMailMockups):
# Test: expandable, conditions in domain
for condition in pigs_domain:
self.assertIn(condition, domain, 'new threads expandable domain should contain the message_read domain parameter')
- self.assertFalse(new_threads_exp.get('parent_id'), 'new threads expandable should not have an ancestor_id')
+ self.assertFalse(new_threads_exp.get('parent_id'), 'new threads expandable should not have an parent_id')
# Do: message_read with domain, thread_level=1 (should be imposed by JS)
read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=1, thread_level=1)
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
@@ -654,7 +666,7 @@ class test_mail(TestMailMockups):
# Data: get expandables
new_threads_exp, new_msg_exp = None, None
for msg in read_msg_list:
- if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('id') == -1:
+ if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('max_limit'):
new_threads_exp = msg
elif msg.get('type') == 'expandable':
new_msg_exp = msg
@@ -701,7 +713,7 @@ class test_mail(TestMailMockups):
# Data: get expandables
new_threads_exp, new_msg_exp = None, None
for msg in read_msg_list:
- if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('id') == -1:
+ if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('max_limit'):
new_threads_exp = msg
# Do: fetch new messages, domain from expandable
@@ -715,7 +727,7 @@ class test_mail(TestMailMockups):
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables
self.assertEqual(len(read_msg_list), 9, 'message_read on Pigs should return 9 messages and 0 expandable')
- self.assertEqual([msg_id0, msg_id1, msg_id2, msg_id3, msg_id4, msg_id5, msg_id6, msg_id7, msg_id8], read_msg_ids,
+ self.assertEqual([msg_id8, msg_id7, msg_id6, msg_id5, msg_id4, msg_id3, msg_id2, msg_id1, msg_id0], read_msg_ids,
'message_read, More on flat, should return all remaning messages')
def test_40_needaction(self):
diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py
index 33712847534..45082b9be4d 100644
--- a/addons/mail/wizard/mail_compose_message.py
+++ b/addons/mail/wizard/mail_compose_message.py
@@ -135,7 +135,12 @@ class mail_compose_message(osv.TransientModel):
related to.
:param int res_id: id of the document record this mail is related to
"""
- return {'model': model, 'res_id': res_id}
+ doc_name_get = self.pool.get(model).name_get(cr, uid, res_id, context=context)
+ if doc_name_get:
+ record_name = doc_name_get[0][1]
+ else:
+ record_name = False
+ return {'model': model, 'res_id': res_id, 'record_name': record_name}
def get_message_data(self, cr, uid, message_id, context=None):
""" Returns a defaults-like dict with initial values for the composition
@@ -161,6 +166,7 @@ class mail_compose_message(osv.TransientModel):
# update the result
result = {
+ 'record_name': message_data.record_name,
'model': message_data.model,
'res_id': message_data.res_id,
'parent_id': message_data.id,
diff --git a/addons/mail/wizard/mail_compose_message_view.xml b/addons/mail/wizard/mail_compose_message_view.xml
index b9c3d237e78..86b1be6562f 100644
--- a/addons/mail/wizard/mail_compose_message_view.xml
+++ b/addons/mail/wizard/mail_compose_message_view.xml
@@ -14,10 +14,19 @@
-
+