[MERGE] Sync with trunk.

bzr revid: tde@openerp.com-20121221133349-u2lpl29430abrkyw
This commit is contained in:
Thibault Delavallée 2012-12-21 14:33:49 +01:00
commit 839d61f97f
12 changed files with 97 additions and 52 deletions

View File

@ -1744,12 +1744,13 @@ class res_partner(osv.osv):
return super(res_partner, self).copy(cr, uid, id, default, context)
class mail_compose_message(osv.osv):
class mail_compose_message(osv.Model):
_inherit = 'mail.compose.message'
def send_mail(self, cr, uid, ids, context=None):
context = context or {}
if context.get('default_model') == 'account.invoice' and context.get('default_res_id') and context.get('mark_invoice_as_sent'):
context = dict(context, mail_post_autofollow=True)
self.pool.get('account.invoice').write(cr, uid, [context['default_res_id']], {'sent': True}, context=context)
return super(mail_compose_message, self).send_mail(cr, uid, ids, context=context)

View File

@ -224,23 +224,25 @@ class mail_message(osv.Model):
# Notification API
#------------------------------------------------------
def set_message_read(self, cr, uid, msg_ids, read, context=None):
def set_message_read(self, cr, uid, msg_ids, read, create_missing=True, context=None):
""" Set messages as (un)read. Technically, the notifications related
to uid are set to (un)read. If for some msg_ids there are missing
notifications (i.e. due to load more or thread parent fetching),
they are created.
:param bool read: set notification as (un)read
:param bool create_missing: create notifications for missing entries
(i.e. when acting on displayed messages not notified)
"""
notification_obj = self.pool.get('mail.notification')
user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
notif_ids = notification_obj.search(cr, uid, [
('partner_id', '=', user_pid),
('message_id', 'in', msg_ids)
], context=context)
domain = [('partner_id', '=', user_pid), ('message_id', 'in', msg_ids)]
if not create_missing:
domain += [('read', '=', not read)]
notif_ids = notification_obj.search(cr, uid, domain, context=context)
# all message have notifications: already set them as (un)read
if len(notif_ids) == len(msg_ids):
if len(notif_ids) == len(msg_ids) or not create_missing:
return notification_obj.write(cr, uid, notif_ids, {'read': read}, context=context)
# some messages do not have notifications: find which one, create notification, update read status
@ -250,23 +252,23 @@ class mail_message(osv.Model):
notification_obj.create(cr, uid, {'partner_id': user_pid, 'read': read, 'message_id': msg_id}, context=context)
return notification_obj.write(cr, uid, notif_ids, {'read': read}, context=context)
def set_message_starred(self, cr, uid, msg_ids, starred, context=None):
def set_message_starred(self, cr, uid, msg_ids, starred, create_missing=True, context=None):
""" Set messages as (un)starred. Technically, the notifications related
to uid are set to (un)starred. If for some msg_ids there are missing
notifications (i.e. due to load more or thread parent fetching),
they are created.
to uid are set to (un)starred.
:param bool starred: set notification as (un)starred
:param bool create_missing: create notifications for missing entries
(i.e. when acting on displayed messages not notified)
"""
notification_obj = self.pool.get('mail.notification')
user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
notif_ids = notification_obj.search(cr, uid, [
('partner_id', '=', user_pid),
('message_id', 'in', msg_ids)
], context=context)
domain = [('partner_id', '=', user_pid), ('message_id', 'in', msg_ids)]
if not create_missing:
domain += [('starred', '=', not starred)]
notif_ids = notification_obj.search(cr, uid, domain, context=context)
# all message have notifications: already set them as (un)starred
if len(notif_ids) == len(msg_ids):
if len(notif_ids) == len(msg_ids) or not create_missing:
notification_obj.write(cr, uid, notif_ids, {'starred': starred}, context=context)
return starred
@ -350,9 +352,15 @@ class mail_message(osv.Model):
vote_nb = len(message.vote_user_ids)
has_voted = uid in [user.id for user in message.vote_user_ids]
try:
body_html = html_email_clean(message.body)
except Exception:
body_html = '<p><b>Encoding Error : </b><br/>Unable to convert this message (id: %s).</p>' % message.id
_logger.exception(Exception)
return {'id': message.id,
'type': message.type,
'body': html_email_clean(message.body or ''),
'body': body_html,
'model': message.model,
'res_id': message.res_id,
'record_name': message.record_name,
@ -492,6 +500,7 @@ class mail_message(osv.Model):
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)]
notification_obj = self.pool.get('mail.notification')
limit = limit or self._message_read_limit
message_tree = {}
message_list = []
@ -529,6 +538,7 @@ class mail_message(osv.Model):
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))
# create final ordered message_list based on parent_tree
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]
@ -600,12 +610,14 @@ class mail_message(osv.Model):
model_ids.setdefault(message.get('model'), {}).setdefault(message.get('res_id'), set()).add(message.get('id'))
allowed_ids = self._find_allowed_doc_ids(cr, uid, model_ids, context=context)
final_ids = author_ids | partner_ids | allowed_ids
if count:
return len(final_ids)
else:
return list(final_ids)
# re-construct a list based on ids, because set did not keep the original order
id_list = [id for id in ids if id in final_ids]
return id_list
def check_access_rule(self, cr, uid, ids, operation, context=None):
""" Access rules of mail.message:

View File

@ -590,7 +590,11 @@ class mail_thread(osv.AbstractModel):
assert thread_id == 0, "Posting a message without model should be with a null res_id, to create a private message."
model_pool = self.pool.get('mail.thread')
new_msg_id = model_pool.message_post_user_api(cr, uid, [thread_id], context=context, content_subtype='html', **msg)
# when posting an incoming email to a document: subscribe the author, if a partner, as follower
if model and thread_id and msg.get('author_id'):
model_pool.message_subscribe(cr, uid, [thread_id], [msg.get('author_id')], context=context)
if partner_ids:
# postponed after message_post, because this is an external message and we don't want to create
# duplicate emails due to notifications
@ -824,7 +828,7 @@ class mail_thread(osv.AbstractModel):
mail_message = self.pool.get('mail.message')
model = context.get('thread_model', self._name) if thread_id else False
attachment_ids = []
attachment_ids = kwargs.pop('attachment_ids', [])
for name, content in attachments:
if isinstance(content, unicode):
content = content.encode('utf-8')
@ -899,7 +903,6 @@ class mail_thread(osv.AbstractModel):
- extra_email: [ 'Fabien <fpi@openerp.com>', 'al@openerp.com' ]
"""
ir_attachment = self.pool.get('ir.attachment')
mail_message = self.pool.get('mail.message')
# 1. Pre-processing: body, partner_ids, type and subtype
if content_subtype == 'plaintext':
@ -932,11 +935,7 @@ class mail_thread(osv.AbstractModel):
message_type = kwargs.pop('type', 'comment')
message_subtype = kwargs.pop('subtype', 'mail.mt_comment')
# 2. Post message
new_message_id = self.message_post(cr, uid, thread_id=thread_id, body=body, subject=subject, type=message_type,
subtype=message_subtype, parent_id=parent_id, context=context, partner_ids=partner_ids, **kwargs)
# 3. Post-processing
# 2. Pre-processing: free attachments linked to the model
# HACK TDE FIXME: Chatter: attachments linked to the document (not done JS-side), load the message
if attachment_ids:
# TDE FIXME (?): when posting a private message, we use mail.thread as a model
@ -952,7 +951,13 @@ class mail_thread(osv.AbstractModel):
if filtered_attachment_ids:
if thread_id and model:
ir_attachment.write(cr, SUPERUSER_ID, attachment_ids, {'res_model': model, 'res_id': thread_id}, context=context)
mail_message.write(cr, SUPERUSER_ID, [new_message_id], {'attachment_ids': [(6, 0, [pid for pid in attachment_ids])]}, context=context)
else:
attachment_ids = []
# 3. Post message
new_message_id = self.message_post(cr, uid, thread_id=thread_id, body=body, subject=subject, type=message_type,
subtype=message_subtype, parent_id=parent_id, attachment_ids=[(4, id) for id in attachment_ids],
context=context, partner_ids=partner_ids, **kwargs)
return new_message_id

View File

@ -939,7 +939,7 @@ openerp.mail = function (session) {
}
var message_ids = _.map(messages, function (val) { return val.id; });
this.ds_message.call('set_message_read', [message_ids, read_value, this.context])
this.ds_message.call('set_message_read', [message_ids, read_value, true, this.context])
.then(function () {
// apply modification
_.each(messages, function (msg) {
@ -988,7 +988,7 @@ openerp.mail = function (session) {
var self=this;
var button = self.$('.oe_star:first');
this.ds_message.call('set_message_starred', [[self.id], !self.is_favorite])
this.ds_message.call('set_message_starred', [[self.id], !self.is_favorite, true])
.then(function (star) {
self.is_favorite=star;
if (self.is_favorite) {
@ -1040,6 +1040,7 @@ openerp.mail = function (session) {
* use with browse, fetch... [O]= top parent
*/
init: function (parent, datasets, options) {
var self = this;
this._super(parent, options);
this.domain = options.domain || [];
this.context = _.extend(options.context || {});
@ -1052,14 +1053,15 @@ openerp.mail = function (session) {
this.parent_message= parent.thread!= undefined ? parent : false ;
// data of this thread
this.id = datasets.id || false,
this.last_id = datasets.last_id || false,
this.parent_id = datasets.parent_id || false,
this.is_private = datasets.is_private || false,
this.author_id = datasets.author_id || false,
this.thread_level = (datasets.thread_level+1) || 0,
this.partner_ids = _.filter(datasets.partner_ids, function (partner) { return partner[0]!=datasets.author_id[0]; } )
this.id = datasets.id || false;
this.last_id = datasets.last_id || false;
this.parent_id = datasets.parent_id || false;
this.is_private = datasets.is_private || false;
this.author_id = datasets.author_id || false;
this.thread_level = (datasets.thread_level+1) || 0;
this.partner_ids = datasets.partner_ids || [];
if (datasets.author_id)
this.partner_ids.push(datasets.author_id);
this.messages = [];
this.options.flat_mode = !!(this.options.display_indented_thread > this.thread_level ? this.options.display_indented_thread - this.thread_level : 0);
@ -1069,6 +1071,7 @@ openerp.mail = function (session) {
this.ds_thread = new session.web.DataSetSearch(this, this.context.default_model || 'mail.thread');
this.ds_message = new session.web.DataSetSearch(this, 'mail.message');
this.render_mutex = new $.Mutex();
},
start: function () {
@ -1261,7 +1264,17 @@ openerp.mail = function (session) {
(replace_context ? replace_context : this.context),
// parent_id
this.context.default_parent_id || undefined
]).done(callback ? _.bind(callback, this, arguments) : this.proxy('switch_new_message'));
]).done(callback ? _.bind(callback, this, arguments) : this.proxy('switch_new_message')
).done(this.proxy('message_fetch_set_read'));
},
message_fetch_set_read: function (message_list) {
if (! this.context.mail_read_set_read) return;
this.render_mutex.exec(_.bind(function() {
msg_ids = _.pluck(message_list, 'id');
return this.ds_message.call('set_message_read', [
msg_ids, true, false, this.context]);
}, this));
},
/**
@ -1622,6 +1635,7 @@ openerp.mail = function (session) {
'show_compose_message': this.view.is_action_enabled('edit'),
});
this.node.context = {
'mail_read_set_read': true, // set messages as read in Chatter
'default_res_id': this.view.datarecord.id || false,
'default_model': this.view.model || false,
};

View File

@ -25,7 +25,7 @@ from openerp.tests import common
class TestMailBase(common.TransactionCase):
def _mock_smtp_gateway(self, *args, **kwargs):
return True
return args[2]['Message-Id']
def _init_mock_build_email(self):
self._build_email_args_list = []

View File

@ -26,7 +26,8 @@ class test_mail_access_rights(TestMailBase):
def test_00_message_read(self):
""" Tests for message_read and expandables. """
cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
cr, uid, user_admin, user_raoul, group_pigs = self.cr, self.uid, self.user_admin, self.user_raoul, self.group_pigs
self.mail_group.message_subscribe_users(cr, uid, [group_pigs.id], [user_raoul.id])
pigs_domain = [('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)]
# Data: create a discussion in Pigs (3 threads, with respectively 0, 4 and 4 answers)
@ -44,16 +45,20 @@ class test_mail_access_rights(TestMailBase):
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: raoul received notifications
raoul_notification_ids = self.mail_notification.search(cr, user_raoul.id, [('read', '=', False), ('message_id', 'in', msg_ids), ('partner_id', '=', user_raoul.partner_id.id)])
self.assertEqual(len(raoul_notification_ids), 11, 'message_post: wrong number of produced notifications')
# Test: read some specific ids
read_msg_list = self.mail_message.message_read(cr, uid, ids=msg_ids[2:4], domain=[('body', 'like', 'dummy')])
read_msg_list = self.mail_message.message_read(cr, user_raoul.id, ids=msg_ids[2:4], domain=[('body', 'like', 'dummy')], context={'mail_read_set_read': True})
read_msg_ids = [msg.get('id') for msg in read_msg_list]
self.assertEqual(msg_ids[2:4], read_msg_ids, 'message_read with direct ids should read only the requested ids')
# Test: read messages of Pigs through a domain, being thread or not threaded
read_msg_list = self.mail_message.message_read(cr, uid, domain=pigs_domain, limit=200)
read_msg_list = self.mail_message.message_read(cr, user_raoul.id, domain=pigs_domain, limit=200)
read_msg_ids = [msg.get('id') for msg in read_msg_list]
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_list = self.mail_message.message_read(cr, user_raoul.id, domain=pigs_domain, limit=200, thread_level=1)
read_msg_ids = [msg.get('id') for msg in read_msg_list]
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')

View File

@ -214,6 +214,9 @@ class mail_compose_message(osv.TransientModel):
new_attachments = email_dict.pop('attachments', [])
post_values['attachments'] += new_attachments
post_values.update(email_dict)
# automatically subscribe recipients if asked to
if context.get('mail_post_autofollow') and wizard.model and post_values.get('partner_ids'):
active_model_pool.message_subscribe(cr, uid, [res_id], [item[1] for item in post_values.get('partner_ids')], context=context)
# post the message
active_model_pool.message_post(cr, uid, [res_id], type='comment', subtype='mt_comment', context=context, **post_values)

View File

@ -212,6 +212,7 @@ class wizard_user(osv.osv_memory):
'subject': _(WELCOME_EMAIL_SUBJECT) % data,
'body_html': '<pre>%s</pre>' % (_(WELCOME_EMAIL_BODY) % data),
'state': 'outgoing',
'type': 'email',
}
mail_id = mail_mail.create(cr, uid, mail_values, context=this_context)
return mail_mail.send(cr, uid, [mail_id], context=this_context)

View File

@ -1,3 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_hr_employee_user,hr.employee user,hr.model_hr_employee,portal.group_portal,1,0,0,0
access_hr_employee_user,hr.employee user,hr.model_hr_employee,portal.group_anonymous,1,0,0,0
access_hr_employee_portal,hr.employee user,hr.model_hr_employee,portal.group_portal,1,0,0,0
access_hr_employee_anonymous,hr.employee user,hr.model_hr_employee,portal.group_anonymous,1,0,0,0

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_hr_employee_user access_hr_employee_portal hr.employee user hr.model_hr_employee portal.group_portal 1 0 0 0
3 access_hr_employee_user access_hr_employee_anonymous hr.employee user hr.model_hr_employee portal.group_anonymous 1 0 0 0

View File

@ -68,6 +68,7 @@ class sale_order(osv.Model):
'subject': 'Invitation to follow %s' % document.name_get()[0][1],
'body_html': 'You have been invited to follow %s' % document.name_get()[0][1],
'auto_delete': True,
'type': 'email',
}
mail_obj = self.pool.get('mail.mail')
mail_id = mail_obj.create(cr, uid, mail_values, context=context)
@ -128,6 +129,7 @@ class account_invoice(osv.Model):
'subject': 'Invitation to follow %s' % document.name_get()[0][1],
'body_html': 'You have been invited to follow %s' % document.name_get()[0][1],
'auto_delete': True,
'type': 'email',
}
mail_obj = self.pool.get('mail.mail')
mail_id = mail_obj.create(cr, uid, mail_values, context=context)

View File

@ -1163,7 +1163,7 @@ class procurement_order(osv.osv):
return super(procurement_order, self)._product_virtual_get(cr, uid, order_point)
class mail_mail(osv.osv):
class mail_mail(osv.Model):
_name = 'mail.mail'
_inherit = 'mail.mail'
@ -1173,9 +1173,8 @@ class mail_mail(osv.osv):
wf_service.trg_validate(uid, 'purchase.order', mail.res_id, 'send_rfq', cr)
return super(mail_mail, self)._postprocess_sent_message(cr, uid, mail=mail, context=context)
mail_mail()
class product_template(osv.osv):
class product_template(osv.Model):
_name = 'product.template'
_inherit = 'product.template'
_columns = {
@ -1185,13 +1184,14 @@ class product_template(osv.osv):
'purchase_ok': 1,
}
product_template()
class mail_compose_message(osv.osv):
class mail_compose_message(osv.Model):
_inherit = 'mail.compose.message'
def send_mail(self, cr, uid, ids, context=None):
context = context or {}
if context.get('default_model') == 'purchase.order' and context.get('default_res_id'):
context = dict(context, mail_post_autofollow=True)
wf_service = netsvc.LocalService("workflow")
wf_service.trg_validate(uid, 'purchase.order', context['default_res_id'], 'send_rfq', cr)
return super(mail_compose_message, self).send_mail(cr, uid, ids, context=context)

View File

@ -977,11 +977,13 @@ class sale_order_line(osv.osv):
return super(sale_order_line, self).unlink(cr, uid, ids, context=context)
class mail_compose_message(osv.osv):
class mail_compose_message(osv.Model):
_inherit = 'mail.compose.message'
def send_mail(self, cr, uid, ids, context=None):
context = context or {}
if context.get('default_model') == 'sale.order' and context.get('default_res_id') and context.get('mark_so_as_sent'):
context = dict(context, mail_post_autofollow=True)
wf_service = netsvc.LocalService("workflow")
wf_service.trg_validate(uid, 'sale.order', context['default_res_id'], 'quotation_sent', cr)
return super(mail_compose_message, self).send_mail(cr, uid, ids, context=context)