diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js index 409ff41fcf5..fb788e305cd 100644 --- a/addons/mail/static/src/js/mail.js +++ b/addons/mail/static/src/js/mail.js @@ -104,87 +104,60 @@ openerp.mail = function(session) { * @param {Object} [options] * @param {String} [options.res_model] res_model of document [REQUIRED] * @param {Number} [options.res_id] res_id of record [REQUIRED] - * @param {Number} [options.formatting] true/false, tells whether - * we are in advance formatting mode - * @param {String} [options.model] mail.compose.message.mode (see - * composition wizard) + * @param {String} [options.composition_mode] mail.compose.message.mode + * (see composition wizard) * @param {Number} [options.msg_id] id of a message in case we are in * reply mode */ - init: function(parent, options) { + init: function (parent, options) { var self = this; this._super(parent); // options this.options = options || {}; - this.options.context = options.context || {}; - this.options.formatting = options.formatting || false; - this.options.mode = options.mode || 'comment'; + this.options.composition_mode = options.composition_mode || 'comment'; this.options.form_xml_id = options.form_xml_id || 'email_compose_message_wizard_form_chatter'; - this.options.form_view_id = false; - if (this.options.mode == 'reply') { - this.options.active_id = this.options.msg_id; - } else { - this.options.active_id = this.options.res_id; - } - this.formatting = false; + this.options.form_view_id = options.form_view_id || false; + this.options.context = options.context || {}; // debug console.groupCollapsed('New ComposeMessage: model', this.options.res_model, ', id', this.options.res_id); console.log('context:', this.options.context); console.groupEnd(); }, - /** - * Reinitialize the widget field values to the default values. The - * purpose is to avoid to destroy and re-build a form view. Default - * values are therefore given as for an onchange. */ - reinit: function() { - var self = this; - if (! this.form_view) return; - var call_defer = this.ds_compose.call('default_get', [['subject', 'body_text', 'body', 'attachment_ids', 'dest_partner_ids'], this.ds_compose.get_context()]).then( - function (result) { - self.form_view.on_processed_onchange({'value': result}, []); - }); - return call_defer; - }, - - /** - * Override-hack of do_action: clean the form */ - do_action: function(action, on_close) { - // this.init_comments(); - return this._super(action, on_close); - }, - - /** - * Widget start function - * - builds and initializes the form view */ - start: function() { - var self = this; + start: function () { this._super.apply(this, arguments); // customize display: add avatar, clean previous content var user_avatar = mail.ChatterUtils.get_image(this.session.prefix, this.session.session_id, 'res.users', 'image_small', this.session.uid); this.$element.find('img.oe_mail_icon').attr('src', user_avatar); this.$element.find('div.oe_mail_msg_content').empty(); - // create a context for the default_get of the compose form - var widget_context = { - 'active_model': this.options.res_model, - 'active_id': this.options.active_id, - 'mail.compose.message.mode': this.options.mode, - }; - var context = _.extend({}, this.options.context, widget_context); + // create a context for the dataset and default_get of the wizard + var context = this._update_context({}); + console.log(context); + // debugger this.ds_compose = new session.web.DataSetSearch(this, 'mail.compose.message', context); // find the id of the view to display in the chatter form var data_ds = new session.web.DataSetSearch(this, 'ir.model.data'); - var deferred_form_id = data_ds.call('get_object_reference', ['mail', this.options.form_xml_id]).then( function (result) { - if (result) { - self.options.form_view_id = result[1]; - } - }).pipe(this.proxy('create_form_view')); - return deferred_form_id; + return data_ds.call('get_object_reference', ['mail', this.options.form_xml_id]).pipe(this.proxy('create_form_view')); }, - /** - * Create a FormView, then append it to the to widget DOM. */ - create_form_view: function () { + /** Update the context of the compose wizard */ + _update_context: function (dest_context) { + _.extend(dest_context, this.options.context, { + 'default_model': this.options.res_model, + 'mail.compose.message.mode': this.options.composition_mode + }); + if (this.options.composition_mode == 'comment') { + _.extend(dest_context, {'default_res_id': this.options.res_id}); + } + else if (this.options.composition_mode == 'reply') { + _.extend(dest_context, {'active_id': this.options.msg_id}); + } + return dest_context + }, + + /** Create a FormView, then append it to the to widget DOM. */ + create_form_view: function (form_view_id) { + this.options.form_view_id = form_view_id[1] || false; var self = this; // destroy previous form_view if any if (this.form_view) { this.form_view.destroy(); } @@ -200,12 +173,31 @@ openerp.mail = function(session) { return $.when(this.form_view.appendTo(msg_node)).pipe(function() { self.bind_events(); self.form_view.do_show(); - if (self.options.formatting) { self.toggle_formatting_mode(); } }); }, - destroy: function() { - this._super.apply(this, arguments); + /** + * Reinitialize the widget field values to the default values. The + * purpose is to avoid to destroy and re-build a form view. Default + * values are therefore given as for an on_change. */ + refresh: function (options_update_values) { + var self = this; + // debugger + this.options = _.extend(this.options, options_update_values); + if (! this.form_view) return; + this.ds_compose.context = this._update_context(this.ds_compose.context); + return this.ds_compose.call('default_get', [ + ['subject', 'body_text', 'body', 'attachment_ids', 'partner_ids', 'composition_mode', + 'res_model', 'res_id', 'parent_id', 'content_subtype'], + this.ds_compose.get_context(), + ]).then( function (result) { self.form_view.on_processed_onchange({'value': result}, []); }); + }, + + /** + * Override-hack of do_action: clean the form */ + do_action: function(action, on_close) { + console.log('compose_message do_action', action, on_close); + return this._super(action, on_close); }, /** @@ -213,38 +205,23 @@ openerp.mail = function(session) { * in the function. */ bind_events: function() { var self = this; - this.$element.find('button.oe_form_button').click(function (event) { - event.preventDefault(); - }); + // this.$element.find('button.oe_form_button').click(function (event) { + // event.preventDefault(); + // event.stopPropagation(); + // }); // event: click on 'Formatting' icon-link that toggles the advanced // formatting options for writing a message (subject, body_html) - this.$element.on('click', 'button.oe_mail_compose_message_formatting', function (event) { - event.preventDefault(); - event.stopPropagation(); - self.toggle_formatting_mode(event); - }); + // this.$element.on('click', 'button.oe_mail_compose_message_formatting', function (event) { + // event.preventDefault(); + // event.stopPropagation(); + // self.toggle_formatting_mode(event); + // }); // event: click on 'Attachment' icon-link that opens the dialog to // add an attachment. this.$element.on('click', 'button.oe_mail_compose_message_attachment', function (event) { event.stopImmediatePropagation(); }); }, - - /** - * Toggle the formatting mode. */ - toggle_formatting_mode: function(event) { - this.formatting = ! this.formatting; - // update context of datasetsearch - this.ds_compose.context.formatting = this.formatting; - }, - - /** - * Update the values of the composition form; with possible different - * values for body and body_html. */ - set_body_value: function(body, body_html) { - this.form_view.fields.body.set_value(body); - this.form_view.fields.body_html.set_value(body_html); - }, }), /** @@ -398,12 +375,15 @@ openerp.mail = function(session) { }); // event: click on "Reply by email" in msg side menu (email style) this.$element.on('click', 'a.oe_mail_msg_reply_by_email', function (event) { + console.log('cacaprout'); event.preventDefault(); event.stopPropagation(); var msg_id = event.srcElement.dataset.msg_id; var formatting = (event.srcElement.dataset.formatting == 'html'); if (! msg_id) return false; - self.instantiate_composition_form('reply', formatting, msg_id); + // self.instantiate_composition_form('reply', formatting, msg_id); + console.log('cacaprout2'); + self.compose_message_widget.refresh({'composition_mode': 'reply', 'msg_id': parseInt(msg_id)}); }); }, @@ -420,15 +400,14 @@ openerp.mail = function(session) { return this._super(action, on_close); }, - /** Instantiate the composition form, with paramteres coming from thread parameters */ + /** Instantiate the composition form, with parameters coming from thread parameters */ instantiate_composition_form: function(mode, formatting, msg_id, context) { if (this.compose_message_widget) { this.compose_message_widget.destroy(); } this.compose_message_widget = new mail.ComposeMessage(this, { - 'extended_mode': false, 'uid': this.options.uid, 'res_model': this.options.context.res_model, - 'res_id': this.options.context.res_id, 'mode': mode || 'comment', 'msg_id': msg_id, - 'formatting': formatting || false, 'context': context || false } ); + 'res_model': this.options.context.res_model, 'res_id': this.options.context.res_id, + 'composition_mode': mode || 'comment', 'msg_id': msg_id, 'context': context || false } ); var composition_node = this.$element.find('div.oe_mail_thread_action'); composition_node.empty(); var compose_done = this.compose_message_widget.appendTo(composition_node); diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml index 9a6d8b92a3c..d0ae83316e7 100644 --- a/addons/mail/static/src/xml/mail.xml +++ b/addons/mail/static/src/xml/mail.xml @@ -102,15 +102,15 @@ diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index 19faaad9578..140773981e5 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -32,81 +32,75 @@ from tools.translate import _ EXPRESSION_PATTERN = re.compile('(\$\{.+?\})') class mail_compose_message(osv.TransientModel): - """ Generic Email composition wizard. This wizard is meant to be inherited - at model and view levels to provide specific wizard features. + """ Generic message composition wizard. You may inherit from this wizard + at model and view levels to provide specific features. - The behavior of the wizard can be modified through the use of context - parameters, among which are: - - mail.compose.message.mode: - - if set to 'reply', the wizard is a reply to a previous message. - It is pre-populated with the original quote - - if set to 'comment', it means you are writing a new message to - be attached to a document. It is pre-populated with values - coming from ``get_value``, related to the document, and that - can be overridden to add specific model-related behavior. - - if set to 'mass_mail', the wizard is in mass mailing mode where - the mail details can contain template placeholders that will be - merged with actual data before being sent to each recipient. - - active_model: model name of the document to which the mail being - composed is related - - active_id: id of the document to which the mail being composed is - related, or id of the message to which user is replying, in case - ``mail.compose.message.mode == 'reply'`` - - active_ids: ids of the documents to which the mail being composed is - related, in case ``mail.compose.message.mode == 'mass_mail'``. + The behavior of the wizard can be modified through the context key + mail.compose.message.mode: + - 'reply': reply to a previous message. The wizard is pre-populated + via ``get_message_data``. + - 'comment': new post on a record. The wizard is pre-populated via + ``get_record_data`` + - 'mass_mail': wizard in mass mailing mode where the mail details can + contain template placeholders that will be merged with actual data + before being sent to each recipient. """ _name = 'mail.compose.message' _inherit = 'mail.message' _description = 'Email composition wizard' def default_get(self, cr, uid, fields, context=None): - """ Overridden to provide specific defaults depending on the context - parameters. - - Composition mode - - comment: default mode; active_model, active_id = model and ID of a - document we are commenting, - - mass_mailing mode: active_model, active_id = model and ID of a - document we are commenting, - - reply: active_id = ID of a mail.message to which we are replying. - From this message we can find the related model and res_id, - - :param dict context: several context values will modify the behavior - of the wizard, cfr. the class description. + """ Handle composition mode. Some details about context keys: + - comment: default mode, model and ID of a record the user comments + - default_model or active_model + - default_res_id or active_id + - reply: active_id of a message the user replies to + - active_id: ID of a mail.message to which we are replying + - message.res_model or default_model + - message.res_id or default_res_id + - mass_mailing mode: model and IDs of records the user mass-mails + - active_ids: record IDs + - default_model or active_model """ + # get some important values from context if context is None: context = {} - compose_mode = context.get('mail.compose.message.mode', 'comment') - active_model = context.get('active_model') - active_id = context.get('active_id') result = super(mail_compose_message, self).default_get(cr, uid, fields, context=context) + # get some important values from context + composition_mode = context.get('mail.compose.message.mode') + model = context.get('default_model', context.get('active_model')) + res_id = context.get('default_res_id', context.get('active_id')) + active_id = context.get('active_id') + active_ids = context.get('active_ids') + # get default values according to the composition mode - vals = {} - if compose_mode in ['reply']: - vals = self.get_message_data(cr, uid, int(context['active_id']), context=context) - elif compose_mode in ['comment', 'mass_mail'] and active_model and active_id: - vals = self.get_value(cr, uid, active_model, active_id, context) + if composition_mode in ['reply']: + vals = self.get_message_data(cr, uid, active_id, context=context) + elif composition_mode in ['comment', 'mass_mail'] and model and res_id: + vals = self.get_record_data(cr, uid, model, res_id, context=context) + else: + vals = {'model': model, 'res_id': res_id} + if composition_mode: + vals['composition_mode'] = composition_mode + for field in vals: if field in fields: result[field] = vals[field] - - # link to model and record if not done yet - if not result.get('model') and active_model: - result['model'] = active_model - if not result.get('res_id') and active_id: - result['res_id'] = active_id return result _columns = { - 'dest_partner_ids': fields.many2many('res.partner', + 'composition_mode': fields.selection([ + ('comment', 'Comment a document'), + ('reply', 'Reply to a message'), + ('mass_mail', 'Mass mailing') + ], string='Composition mode'), + 'partner_ids': fields.many2many('res.partner', 'mail_compose_message_res_partner_rel', - 'wizard_id', 'partner_id', 'Destination partners', - help="When sending emails through the social network composition wizard"\ - "you may choose to send a copy of the mail to partners."), - 'attachment_ids': fields.many2many('ir.attachment','mail_compose_message_ir_attachments_rel', + 'wizard_id', 'partner_id', 'Additional contacts'), + 'attachment_ids': fields.many2many('ir.attachment', + 'mail_compose_message_ir_attachments_rel', 'wizard_id', 'attachment_id', 'Attachments'), - 'auto_delete': fields.boolean('Auto Delete', help="Permanently delete emails after sending"), 'filter_id': fields.many2one('ir.filters', 'Filters'), 'body_text': fields.text('Plain-text editor body'), 'content_subtype': fields.char('Message content subtype', size=32, readonly=1, @@ -115,46 +109,75 @@ class mail_compose_message(osv.TransientModel): } _defaults = { + 'composition_mode': 'comment', 'content_subtype': lambda self,cr, uid, context={}: 'plain', - 'body_text': lambda self,cr, uid, context={}: '', + 'body_text': lambda self,cr, uid, context={}: False, 'body': lambda self,cr, uid, context={}: '', + 'subject': lambda self,cr, uid, context={}: False, + 'partner_ids': [], } - def get_value(self, cr, uid, model, res_id, context=None): + def get_record_data(self, cr, uid, model, res_id, context=None): """ Returns a defaults-like dict with initial values for the composition wizard when sending an email related to the document record identified by ``model`` and ``res_id``. - The default implementation returns an empty dictionary, and is meant - to be overridden by subclasses. - :param str model: model name of the document record this mail is related to. - :param int res_id: id of the document record this mail is related to. - :param dict context: several context values will modify the behavior - of the wizard, cfr. the class description. + :param int res_id: id of the document record this mail is related to """ + return {'model': model, 'res_id': res_id} + + def get_message_data(self, cr, uid, message_id, context=None): + """ Returns a defaults-like dict with initial values for the composition + wizard when replying to the given message (e.g. including the quote + of the initial message, and the correct recipients). + + :param int message_id: id of the mail.message to which the user + is replying. + """ + if context is None: + context = {} result = {} - user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + if not message_id: + return result + + current_user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + message_data = self.pool.get('mail.message').browse(cr, uid, message_id, context=context) + + # create subject + re_prefix = _("Re:") + reply_subject = tools.ustr(message_data.subject or '') + if not (reply_subject.startswith('Re:') or reply_subject.startswith(re_prefix)): + reply_subject = "%s %s" % (re_prefix, reply_subject) + # create the reply in the body + reply_header = _('On %(date)s, %(sender_name)s wrote:') % { + 'date': message_data.date if message_data.date else '', + 'sender_name': message_data.author_id.name } + reply_body = '
%s
%s
%s' % (reply_header, message_data.body, current_user.signature) + # get partner_ids from original message + partner_ids = [partner.id for partner in message_data.partner_ids] if message_data.partner_ids else [] + + # update the result result.update({ - 'model': model, - 'res_id': res_id, - 'email_from': user.email or tools.config.get('email_from', False), - 'body': False, - 'subject': False, - 'dest_partner_ids': [], + 'model': message_data.model, + 'res_id': message_data.res_id, + 'parent_id': message_data.id, + 'body': reply_body, + 'subject': reply_subject, + 'partner_ids': partner_ids, + 'content_subtype': 'html', }) return result - def toggle_formatting(self, cr, uid, ids, context=None): + def toggle_content_subtype(self, cr, uid, ids, context=None): """ hit toggle formatting mode button: calls onchange_formatting to emulate an on_change, then writes the value to update the form. """ for record in self.browse(cr, uid, ids, context=context): content_st_new_value = 'plain' if record.content_subtype == 'html' else 'html' onchange_res = self.onchange_content_subtype(cr, uid, ids, content_st_new_value, record.model, record.res_id, context=context) self.write(cr, uid, [record.id], onchange_res['value'], context=context) - return False - + return True def onchange_content_subtype(self, cr, uid, ids, value, model, res_id, context=None): """ onchange_content_subtype (values: 'plain' or 'html'). This onchange @@ -164,12 +187,10 @@ class mail_compose_message(osv.TransientModel): This method can be overridden for models that want to have their specific behavior. """ - if value == 'plain': - return {'value': {'subject': False, 'content_subtype': value}} return {'value': {'content_subtype': value}} - def onchange_dest_partner_ids(self, cr, uid, ids, value, context=None): - """ onchange_dest_partner_ids (value format: [[6, False, [3, 4]]]). The + def onchange_partner_ids(self, cr, uid, ids, value, context=None): + """ onchange_partner_ids (value format: [[6, False, [3, 4]]]). The basic purpose of this method is to check that destination partners effectively have email addresses. Otherwise a warning is thrown. """ @@ -189,94 +210,40 @@ class mail_compose_message(osv.TransientModel): } return {'warning': warning, 'value': {}} - def get_message_data(self, cr, uid, message_id, context=None): - """ Returns a defaults-like dict with initial values for the composition - wizard when replying to the given message (e.g. including the quote - of the initial message, and the correct recipient). It should not be - called unless ``context['mail.compose.message.mode'] == 'reply'``. - - :param int message_id: id of the mail.message to which the user - is replying. - :param dict context: several context values will modify the behavior - of the wizard, cfr. the class description. - """ - if context is None: - context = {} - result = {} - if not message_id: - return result - - current_user = self.pool.get('res.users').browse(cr, uid, uid, context) - message_data = self.pool.get('mail.message').browse(cr, uid, message_id, context) - # Form the subject - re_prefix = _("Re:") - reply_subject = tools.ustr(message_data.subject or '') - if not (reply_subject.startswith('Re:') or reply_subject.startswith(re_prefix)): - reply_subject = "%s %s" % (re_prefix, reply_subject) - - # Form the bodies (text and html). We use the plain text version of the - # original mail, by default, as it is easier to quote than the HTML - # version. TODO: make it possible to switch to HTML on the fly - - sent_date = _('On %(date)s, ') % {'date': message_data.date} if message_data.date else '' - sender = _('%(sender_name)s wrote:') % {'sender_name': tools.ustr(message_data.email_from or _('You'))} - - body = message_data.body or '' - quoted_body = '
%s
' % (tools.ustr(body)), - reply_body = '

%s%s
%s
%s' % (sent_date, sender, quoted_body, current_user.signature) - - # form dest_partner_ids - dest_partner_ids = [partner.id for partner in message_data.partner_ids] - - # update the result - result.update({ - 'body': reply_body, - 'subject': reply_subject, - 'dest_partner_ids': dest_partner_ids, - 'model': message_data.model or False, - 'res_id': message_data.res_id or False, - }) - return result - def send_mail(self, cr, uid, ids, context=None): - '''Process the wizard contents and proceed with sending the corresponding - email(s), rendering any template patterns on the fly if needed. - If the wizard is in mass-mail mode (context['mail.compose.message.mode'] is - set to ``'mass_mail'``), the resulting email(s) are scheduled for being - sent the next time the mail.message scheduler runs, or the next time - ``mail.message.process_email_queue`` is called. - Otherwise the new message is sent immediately. - - :param dict context: several context values will modify the behavior - of the wizard, cfr. the class description. - ''' + """ Process the wizard content and proceed with sending the related + email(s), rendering any template patterns on the fly if needed. """ if context is None: context = {} - formatting = context.get('formatting') - # FIXME TODO: mass_mail_mode unused? - mass_mail_mode = context.get('mail.compose.message.mode') == 'mass_mail' + for wizard in self.browse(cr, uid, ids, context=context): + mass_mail_mode = wizard.composition_mode == 'mass_mail' - for mail_wiz in self.browse(cr, uid, ids, context=context): attachment = {} - for attach in mail_wiz.attachment_ids: + for attach in wizard.attachment_ids: attachment[attach.datas_fname] = attach.datas and attach.datas or False # default values, according to the wizard options - subject = mail_wiz.subject if formatting else False - # FIXME TODO: partner_ids not used?? - partner_ids = [partner.id for partner in mail_wiz.dest_partner_ids] - body = mail_wiz.body_html if mail_wiz.content_subtype == 'html' else mail_wiz.body + subject = wizard.subject if wizard.content_subtype == 'html' else False + partner_ids = [partner.id for partner in wizard.partner_ids] + body = wizard.body if wizard.content_subtype == 'html' else wizard.body_text - active_model_pool = self.pool.get('mail.thread') - active_id = context.get('default_res_id', False) + active_model_pool = self.pool.get(wizard.model if wizard.model else 'mail.thread') + + #TODO: TDE: WIP: have to check for mass mail and templates - no time anymore today if context.get('mail.compose.message.mode') == 'mass_mail' and context.get('default_model', False) and context.get('default_res_id', False): active_model = context.get('default_model', False) active_model_pool = self.pool.get(active_model) subject = self.render_template(cr, uid, subject, active_model, active_id) - body = self.render_template(cr, uid, mail_wiz.body_html, active_model, active_id) - active_model_pool.message_post(cr, uid, [active_id], body=body, subject=subject, msg_type='comment', - attachments=attachment, context=context) + body = self.render_template(cr, uid, wizard.body_html, active_model, active_id) + + # determine the ids we are commenting + if mass_mail_mode: + res_ids = context.get('active_ids', []) + else: + res_ids = [wizard.res_id] + active_model_pool.message_post(cr, uid, res_ids, body=body, subject=subject, msg_type='comment', + attachments=attachment, context=context, partner_ids=partner_ids) return {'type': 'ir.actions.act_window_close'} @@ -310,7 +277,7 @@ class mail_compose_message(osv.TransientModel): return template and EXPRESSION_PATTERN.sub(merge, template) def dummy(self, cr, uid, ids, context=None): - return False - - -#FIXME: check for models defining '_mail_compose_message' + """ TDE: defined to have buttons that do basically nothing. It is + currently impossible to have buttons that do nothing special + in views (if type not specified, considered as 'object'). """ + return True diff --git a/addons/mail/wizard/mail_compose_message_view.xml b/addons/mail/wizard/mail_compose_message_view.xml index a10dc6ccc86..8bf8df24f59 100644 --- a/addons/mail/wizard/mail_compose_message_view.xml +++ b/addons/mail/wizard/mail_compose_message_view.xml @@ -38,8 +38,13 @@
+ + + + + @@ -49,9 +54,9 @@ -