[MERGE] Sync with trunk.
bzr revid: tde@openerp.com-20121221133349-u2lpl29430abrkyw
This commit is contained in:
commit
839d61f97f
|
@ -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)
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue