[IMP] Chatter: refactoring of 'include new emails/partners' in Chatter compose. It does not create partner everytime; discarding the creation popup discards the creation and addition as recipient.

bzr revid: tde@openerp.com-20130220124923-w3e9hxomniphadj7
This commit is contained in:
Thibault Delavallée 2013-02-20 13:49:23 +01:00
parent cb2e28f59f
commit c3b6408715
6 changed files with 209 additions and 114 deletions

View File

@ -963,6 +963,15 @@ class crm_lead(base_stage, format_address, osv.osv):
return [lead.section_id.message_get_reply_to()[0] if lead.section_id else False
for lead in self.browse(cr, uid, ids, context=context)]
def message_get_suggested_recipients(self, cr, uid, ids, context=None):
recipients = super(crm_lead, self).message_get_suggested_recipients(cr, uid, ids, context=context)
for lead in self.browse(cr, uid, ids, context=context):
if lead.partner_id:
recipients[lead.id].append([lead.partner_id.id, '%s<%s>' % (lead.partner_id.name, lead.partner_id.email)])
elif lead.email_from:
recipients[lead.id].append([False, lead.email_from])
return recipients
def message_new(self, cr, uid, msg, custom_values=None, context=None):
""" Overrides mail_thread message_new that is called by the mailgateway
through message_process.

View File

@ -820,41 +820,42 @@ class mail_thread(osv.AbstractModel):
"now deprecated res.log.")
self.message_post(cr, uid, [id], message, context=context)
def message_create_partners_from_emails(self, cr, uid, emails, context=None):
def message_get_suggested_recipients(self, cr, uid, ids, context=None):
return dict.fromkeys(ids, list())
def message_get_partner_info_from_emails(self, cr, uid, emails, link_mail=False, context=None):
""" Convert a list of emails into a list partner_ids and a list
new_partner_ids. The return value is non conventional because
it is meant to be used by the mail widget.
:return dict: partner_ids and new_partner_ids
"""
partner_obj = self.pool.get('res.partner')
mail_message_obj = self.pool.get('mail.message')
partner_ids = []
new_partner_ids = []
partner_obj = self.pool.get('res.partner')
partner_info = dict()
for email in emails:
m = re.search(r"((.+?)\s*<)?([^<>]+@[^<>]+)>?", email, re.IGNORECASE | re.DOTALL)
name = m.group(2) or m.group(0)
email = m.group(3)
ids = partner_obj.search(cr, SUPERUSER_ID, [('email', '=', email)], context=context)
email_address = m.group(3)
partner_info.setdefault(email_address, dict()).setdefault('full_name', email)
ids = partner_obj.search(cr, SUPERUSER_ID, [('email', '=', email_address)], context=context)
if ids:
partner_ids.append(ids[0])
partner_id = ids[0]
partner_info[email_address]['partner_id'] = ids[0]
else:
partner_id = partner_obj.create(cr, uid, {
'name': name or email,
'email': email,
}, context=context)
new_partner_ids.append(partner_id)
partner_info[email_address]['partner_id'] = False
# link mail with this from mail to the new partner id
message_ids = mail_message_obj.search(cr, SUPERUSER_ID, ['|', ('email_from', '=', email), ('email_from', 'ilike', '<%s>' % email), ('author_id', '=', False)], context=context)
if message_ids:
mail_message_obj.write(cr, SUPERUSER_ID, message_ids, {'email_from': None, 'author_id': partner_id}, context=context)
return {
'partner_ids': partner_ids,
'new_partner_ids': new_partner_ids,
}
if link_mail and ids:
print email, ids
message_ids = mail_message_obj.search(cr, SUPERUSER_ID, [
'|',
('email_from', '=', email),
('email_from', 'ilike', '<%s>' % email),
('author_id', '=', False)
], context=context)
if message_ids:
# mail_message_obj.write(cr, SUPERUSER_ID, message_ids, {'author_id': ids[0]}, context=context)
print 'found', message_ids
return partner_info
def message_post(self, cr, uid, thread_id, body='', subject=None, type='notification',
subtype=None, parent_id=False, attachments=None, context=None, **kwargs):

View File

@ -272,20 +272,20 @@
margin-top: 4px;
margin-bottom: 4px;
}
.openerp .oe_mail .oe_msg_composer .oe_msg_attachment_list{
.openerp .oe_mail .oe_msg_composer .oe_msg_attachment_list {
display: block;
}
.openerp .oe_mail .oe_msg_composer .oe_emails_from{
.openerp .oe_mail .oe_msg_composer .oe_recipients {
font-size: 12px;
margin-left: 20px;
margin-bottom: 2px;
}
.openerp .oe_mail .oe_msg_composer .oe_emails_from label{
.openerp .oe_mail .oe_msg_composer .oe_recipients label{
vertical-align: middle;
display: block;
line-height: 14px;
}
.openerp .oe_mail .oe_msg_composer .oe_emails_from input{
.openerp .oe_mail .oe_msg_composer .oe_recipients input{
vertical-align: middle;
}
.openerp .oe_mail .oe_attachment{

View File

@ -21,6 +21,15 @@ openerp.mail = function (session) {
mail.ChatterUtils = {
/** parse text to find email: Tagada <address@mail.fr> -> [Tagada, address@mail.fr] or False */
parse_email: function (text) {
var email = text.match(/(.*)<(.*@.*)>/);
if (! email) {
return [text, false];
}
return [_.str.trim(email[1]), email[2]];
},
/* Get an image in /web/binary/image?... */
get_image: function (session, model, field, id, resize) {
var r = resize ? encodeURIComponent(resize) : '';
@ -255,12 +264,8 @@ openerp.mail = function (session) {
this.avatar = mail.ChatterUtils.get_image(this.session, 'res.users', 'image_small', this.session.uid);
}
if (this.author_id && this.author_id[1]) {
var email = this.author_id[1].match(/(.*)<(.*@.*)>/);
if (!email) {
this.author_id.push(_.str.escapeHTML(this.author_id[1]), '', this.author_id[1]);
} else {
this.author_id.push(_.str.escapeHTML(email[0]), _.str.trim(email[1]), email[2]);
}
var parsed_email = mail.ChatterUtils.parse_email(this.author_id[1]);
this.author_id.push(parsed_email[0], parsed_email[1]);
}
if (this.partner_ids && this.partner_ids.length > 3) {
this.extra_partners_nbr = this.partner_ids.length - 3;
@ -379,14 +384,23 @@ openerp.mail = function (session) {
* @param {Object} [context] context passed to the
* mail.compose.message DataSetSearch. Please refer to this model
* for more details about fields and default values.
* @param {Object} recipients = [
{
'email_address': [str],
'partner_id': False/[int],
'name': [str],
'full_name': name<email_address>,
},
{ ... },
]
*/
init: function (parent, datasets, options) {
this._super(parent, datasets, options);
this.show_compact_message = false;
this.show_delete_attachment = true;
this.emails_from = [];
this.partners_from = [];
this.recipients = [];
this.recipient_ids = [];
},
start: function () {
@ -487,16 +501,12 @@ openerp.mail = function (session) {
bind_events: function () {
var self = this;
this.$('.oe_compact').on('click', _.bind( this.on_compose_expandable, this));
// set the function called when attachments are added
this.$('input.oe_form_binary_file').on('change', _.bind( this.on_attachment_change, this) );
this.$('.oe_cancel').on('click', _.bind( this.on_cancel, this) );
this.$('.oe_post').on('click', _.bind( this.on_message_post, this) );
this.$('.oe_log').on('click', _.bind( this.on_message_log, this) );
this.$('.oe_full').on('click', _.bind( this.on_compose_fullmail, this, this.id ? 'reply' : 'comment') );
this.$('.oe_compact').on('click', _.bind( this.on_toggle_quick_composer, this));
this.$('input.oe_form_binary_file').on('change', _.bind( this.on_attachment_change, this));
this.$('.oe_cancel').on('click', _.bind( this.on_cancel, this));
this.$('.oe_post').on('click', _.bind( this.on_message_post, this));
this.$('.oe_log').on('click', _.bind( this.on_message_post, this));
this.$('.oe_full').on('click', _.bind( this.on_compose_fullmail, this, this.id ? 'reply' : 'comment'));
/* stack for don't close the compose form if the user click on a button */
this.$('.oe_msg_left, .oe_msg_center').on('mousedown', _.bind( function () { this.stay_open = true; }, this));
this.$('.oe_msg_left, .oe_msg_content').on('mouseup', _.bind( function () { this.$('textarea').focus(); }, this));
@ -506,13 +516,12 @@ openerp.mail = function (session) {
this.$('textarea').autosize();
// auto close
this.$('textarea').on('blur', _.bind( this.on_compose_expandable, this));
this.$('textarea').on('blur', _.bind( this.on_toggle_quick_composer, this));
// event: delete child attachments off the oe_msg_attachment_list box
this.$(".oe_msg_attachment_list").on('click', '.oe_delete', this.on_attachment_delete);
this.$(".oe_emails_from").on('change', 'input', this.on_checked_email_from);
this.$(".oe_partners_from").on('change', 'input', this.on_checked_partner_from);
this.$(".oe_recipients").on('change', 'input', this.on_checked_recipient);
},
on_compose_fullmail: function (default_composition_mode) {
@ -582,64 +591,92 @@ openerp.mail = function (session) {
check_recipient_partners: function () {
var self = this;
var partners_from = [];
var emails = [];
_.each(this.emails_from, function (email_from) {
if (email_from[1] && !_.find(emails, function (email) {return email == email_from[0][4];})) {
emails.push(email_from[0][1]);
}
});
var deferred_check = $.Deferred();
if (emails.length == 0) {
return deferred_check.resolve(partners_from);
var check_done = $.Deferred();
var recipients = _.filter(this.recipients, function (recipient) { return recipient.checked });
var recipients_to_check = _.filter(recipients, function (recipient) { return (! recipient.partner_id || ! recipient.email_address) });
var names_to_check = _.pluck(recipients_to_check, 'full_name');
var names_to_remove = [];
var recipient_ids = _.pluck(_.filter(recipients, function (recipient) { return recipient.partner_id && recipient.email_address }), 'partner_id');
// some debug
console.group('check_recipient_partners');
console.log('recipients', recipients);
console.log('recipient_ids', recipient_ids);
console.log('recipients_to_check', recipients_to_check);
console.log('names_to_check', names_to_check);
// no emails -> do not make call, proceed to next step
if (names_to_check.length == 0) {
console.groupEnd();
return check_done.resolve(recipient_ids);
}
self.parent_thread.ds_thread._model.call('message_create_partners_from_emails', [emails]).then(function (partners) {
partners_from = _.clone(partners.partner_ids);
var deferreds = [];
_.each(partners.new_partner_ids, function (id) {
// for each unknown email -> filter already existing partners
self.parent_thread.ds_thread._model.call('message_get_partner_info_from_emails', [names_to_check]).done(function (result) {
var emails_deferred = [];
_.each(result, function (partner_info) {
var deferred = $.Deferred()
deferreds.push(deferred);
var pop = new session.web.form.FormOpenPopup(this);
emails_deferred.push(deferred);
var partner_name = partner_info.full_name;
var partner_id = partner_info.partner_id;
var parsed_email = mail.ChatterUtils.parse_email(partner_name);
console.log('checking', partner_name, 'parsed email', parsed_email);
var pop = new session.web.form.FormOpenPopup(this);
pop.show_element(
'res.partner',
id,
{
'force_email': true,
partner_id,
{ 'force_email': true,
'ref': "compound_context",
},
{
'default_name': parsed_email[0],
'default_email': parsed_email[1],
}, {
title: _t("Please complete partner's informations"),
}
);
pop.on('closed', self, function () {
deferred.resolve();
});
partners_from.push(id);
pop.view_form.on('on_button_cancel', self, function () {
names_to_remove.push(partner_name);
});
});
$.when.apply( $, deferreds ).then(function () {
deferred_check.resolve(partners_from);
$.when.apply($, emails_deferred).then(function () {
console.log('post truc', names_to_remove);
var new_names_to_check = _.difference(names_to_check, names_to_remove);
self.parent_thread.ds_thread._model.call('message_get_partner_info_from_emails', [new_names_to_check, true]).done(function (result) {
_.each(result, function (partner_info) {
recipient_ids.push(partner_info.partner_id);
});
}).pipe(function (caca) {
console.log('caca', caca);
check_done.resolve(recipient_ids);
});
});
});
return deferred_check;
console.groupEnd();
return check_done;
},
on_message_post: function (event) {
var self = this;
var is_log = $(event.target).data('is_log');
console.log(is_log);
if (this.do_check_attachment_upload() && (this.attachment_ids.length || this.$('textarea').val().match(/\S+/))) {
// create list of new partners
this.check_recipient_partners().done(function (partner_ids) {
self.do_send_message_post(partner_ids);
});
if (is_log) {
self.do_send_message_post([], is_log);
}
else {
this.check_recipient_partners().done(function (partner_ids) {
self.do_send_message_post(partner_ids, is_log);
});
}
}
},
on_message_log: function (event) {
if (this.do_check_attachment_upload() && (this.attachment_ids.length || this.$('textarea').val().match(/\S+/))) {
this.do_send_message_post([], true);
}
},
/*do post a message and fetch the message*/
/* do post a message and fetch the message */
do_send_message_post: function (partner_ids, log) {
var self = this;
var values = {
@ -669,18 +706,60 @@ openerp.mail = function (session) {
});
},
/* convert the compact mode into the compose message
*/
on_compose_expandable: function (event) {
this.get_emails_from();
if ((!this.stay_open || (event && event.type == 'click')) && (!this.show_composer || !this.$('textarea:not(.oe_compact)').val().match(/\S+/) && !this.attachment_ids.length)) {
this.show_composer = !this.show_composer || this.stay_open;
this.reinit();
/* Quick composer: toggle minimal / expanded mode
* - toggle minimal (one-liner) / expanded (textarea, buttons) mode
* - when going into expanded mode:
* - call `message_get_suggested_partners` to have a list of partners to add
* - compute email_from list (list of unknown email_from to propose to create partners)
*/
on_toggle_quick_composer: function (event) {
var self = this;
this.compute_emails_from();
var email_addresses = _.pluck(this.recipients, 'email_address');
var suggested_partners = $.Deferred();
// some debug
console.group('on_toggle_quick_composer');
console.log('event', event);
console.log('computed recipients', this.recipients);
// if clicked: call for suggested recipients
if (event.type == 'click') {
suggested_partners = this.parent_thread.ds_thread.call('message_get_suggested_recipients', [[this.context.default_res_id]]).done(function (additional_recipients) {
var thread_recipients = additional_recipients[self.context.default_res_id];
console.log('message_get_suggested_recipients:', thread_recipients);
_.each(thread_recipients, function (recipient) {
var parsed_email = mail.ChatterUtils.parse_email(recipient[1]);
if (_.indexOf(email_addresses, parsed_email[1]) == -1) {
self.recipients.push({
'checked': true,
'partner_id': recipient[0],
'full_name': recipient[1],
'name': parsed_email[0],
'email_address': parsed_email[1],
})
}
});
});
}
if (!this.stay_open && this.show_composer && (!event || event.type != 'blur')) {
this.$('textarea:not(.oe_compact):first').focus();
else {
suggested_partners.resolve({});
}
return true;
// when call for suggested partners finished: re-render the widget
$.when(suggested_partners).pipe(function (additional_recipients) {
console.log('recipients after toogle', self.recipients);
console.groupEnd();
if ((!self.stay_open || (event && event.type == 'click')) && (!self.show_composer || !self.$('textarea:not(.oe_compact)').val().match(/\S+/) && !self.attachment_ids.length)) {
self.show_composer = !self.show_composer || self.stay_open;
self.reinit();
}
if (!self.stay_open && self.show_composer && (!event || event.type != 'blur')) {
self.$('textarea:not(.oe_compact):first').focus();
}
});
return suggested_partners;
},
do_hide_compact: function () {
@ -697,7 +776,10 @@ openerp.mail = function (session) {
}
},
get_emails_from: function () {
/** Compute the list of unknown email_from the the given thread
* TDE FIXME: seems odd to delegate to the composer
* TDE TODO: please de-obfuscate and comment your code */
compute_emails_from: function () {
var self = this;
var messages = [];
@ -710,22 +792,26 @@ openerp.mail = function (session) {
// get all wall messages if is not a mail.Wall
_.each(this.options.root_thread.messages, function (msg) {messages.push(msg); messages.concat(msg.get_childs());});
}
_.each(messages, function (thread) {
if (thread.author_id && !thread.author_id[0] &&
!_.find(self.emails_from, function (from) {return from[0][4] == thread.author_id[4];})) {
self.emails_from.push([thread.author_id, true]);
!_.find(self.recipients, function (recipient) {return recipient.email_address == thread.author_id[3];})) {
self.recipients.push({ 'full_name': thread.author_id[1],
'name': thread.author_id[2],
'email_address': thread.author_id[3],
'partner_id': false,
'checked': true });
}
});
return self.emails_from;
return self.recipients;
},
on_checked_email_from: function (event) {
on_checked_recipient: function (event) {
var $input = $(event.target);
var email = $input.attr("data");
_.each(this.emails_from, function (email_from) {
if (email_from[0][4] == email) {
email_from[1] = $input.is(":checked");
_.each(this.recipients, function (recipient) {
if (recipient.email_address == email) {
recipient.checked = $input.is(":checked");
}
});
},
@ -1255,12 +1341,12 @@ openerp.mail = function (session) {
/**
*If compose_message doesn't exist, instantiate the compose message.
* Call the on_compose_expandable method to allow the user to write his message.
* Call the on_toggle_quick_composer method to allow the user to write his message.
* (Is call when a user click on "Reply" button)
*/
on_compose_message: function (event) {
this.instantiate_compose_message();
this.compose_message.on_compose_expandable(event);
this.compose_message.on_toggle_quick_composer(event);
return false;
},

View File

@ -33,9 +33,8 @@
<div class="oe_msg_footer">
<div class="oe_msg_attachment_list"></div>
<button class="oe_post">Send to followers</button>
or <button class="oe_log">Log a note</button>
or <button class="oe_log" data-is_log="true">Log a note</button>
<t t-call="mail.compose_message.add_attachment"/>
<!--<a class="oe_cancel oe_e">X</a>-->
</div>
</div>
</div>
@ -130,11 +129,11 @@
<a class="oe_more_hidden">&lt;&lt;&lt;</a>
</t>
</div>
<div class="oe_emails_from">
<t t-foreach='widget.emails_from' t-as='email_from'>
<label title="Add them into recipients and followers">
<input type="checkbox" t-att-checked="email_from[1] ? 'checked' : undefind" t-att-data="email_from[0][4]"/>
<t t-raw="email_from[0][2]"/>
<div class="oe_recipients">
<t t-foreach='widget.recipients' t-as='recipient'>
<label title="Add as recipient and follower">
<input type="checkbox" t-att-checked="recipient.checked ? 'checked' : undefined" t-att-data="recipient.email_address"/>
<t t-raw="recipient.name"/> (<t t-raw="recipient.email_address"/>)
</label>
</t>
</div>

View File

@ -119,18 +119,18 @@ class test_mail(TestMailBase):
# Previously-created group can be emailed now - it should have an implicit alias group+frogs@...
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
group_messages = frog_group.message_ids
self.assertTrue(len(group_messages) == 2, 'New group should only have the original message + creation log')
self.assertTrue(len(group_messages) == 1, 'New group should only have the original message')
mail_frog_news = MAIL_TEMPLATE.format(to='Friendly Frogs <group+frogs@example.com>', subject='news', extra='')
self.mail_thread.message_process(cr, uid, None, mail_frog_news)
frog_group.refresh()
self.assertTrue(len(frog_group.message_ids) == 3, 'Group should contain 3 messages now')
self.assertTrue(len(frog_group.message_ids) == 2, 'Group should contain 2 messages now')
# 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)
self.mail_thread.message_process(cr, uid, None, mail_reply)
frog_group.refresh()
self.assertTrue(len(frog_group.message_ids) == 4, 'Group should contain 4 messages now')
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='')