diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py index 19af105c950..bbcb25fefa9 100644 --- a/addons/account/account_invoice.py +++ b/addons/account/account_invoice.py @@ -395,18 +395,23 @@ class account_invoice(osv.osv): template_id = template and template[1] or False res = mod_obj.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form') res_id = res and res[1] or False - ctx = dict(context, active_model='account.invoice', active_id=ids[0]) - ctx.update({'mail.compose.template_id': template_id}) + ctx = dict(context) + ctx.update({ + 'default_model': 'account.invoice', + 'default_res_id': ids[0], + 'default_use_template': True, + 'default_template_id': template_id, + }) return { - 'view_type': 'form', - 'view_mode': 'form', - 'res_model': 'mail.compose.message', - 'views': [(res_id, 'form')], - 'view_id': res_id, - 'type': 'ir.actions.act_window', - 'target': 'new', - 'context': ctx, - 'nodestroy': True, + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'mail.compose.message', + 'views': [(res_id, 'form')], + 'view_id': res_id, + 'type': 'ir.actions.act_window', + 'target': 'new', + 'context': ctx, + 'nodestroy': True, } def confirm_paid(self, cr, uid, ids, context=None): diff --git a/addons/mail/__openerp__.py b/addons/mail/__openerp__.py index 99dd6bda6ac..08a98cb0a02 100644 --- a/addons/mail/__openerp__.py +++ b/addons/mail/__openerp__.py @@ -30,27 +30,27 @@ A business oriented Social Networking with a fully-integrated email and message =========================================================================================== The Social Networking module provides an unified social network -abstraction layer allowing applications to display a complete +abstraction layer allowing applications to display a complete communication history on documents. It gives the users the possibility to read and send messages and emails in an unified way. -It also provides a feeds page combined to a subscription mechanism, that +It also provides a feeds page combined to a subscription mechanism, that allows to follow documents, and to be constantly updated about recent news. - + The main features of the module are: ------------------------------------ * a clean and renewed communication history for any OpenERP document that can act as a discussion topic, * a discussion mean on documents, - * a subscription mechanism to be updated about new messages on + * a subscription mechanism to be updated about new messages on interesting documents, - * an unified feeds page to see recent messages and activity + * an unified feeds page to see recent messages and activity on followed documents, * user communication through the feeds page, * a threaded discussion design, * relies on the global outgoing mail server, an integrated email - management system allowing to send emails with a configurable + management system allowing to send emails with a configurable scheduler-based processing engine * includes an extensible generic email composition assistant, that can turn into a mass-mailing assistant, and is capable of interpreting diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py index e4862ca9fb9..18a88bb0b61 100644 --- a/addons/mail/mail_message.py +++ b/addons/mail/mail_message.py @@ -147,7 +147,7 @@ class mail_message(osv.Model): def _message_dict_get(self, cr, uid, msg, context=None): """ Return a dict representation of the message browse record. """ - attachment_ids = self.pool.get('ir.attachment').name_get(cr, uid, [x.id for x in msg.attachment_ids], context=context) + attachment_ids = [{'id': attach[0], 'name': attach[1]} for attach in self.pool.get('ir.attachment').name_get(cr, uid, [x.id for x in msg.attachment_ids], context=context)] author_id = self.pool.get('res.partner').name_get(cr, uid, [msg.author_id.id], context=context)[0] author_user_id = self.pool.get('res.users').name_get(cr, uid, [msg.author_id.user_ids[0].id], context=context)[0] partner_ids = self.pool.get('res.partner').name_get(cr, uid, [x.id for x in msg.partner_ids], context=context) @@ -306,15 +306,15 @@ class mail_message(osv.Model): def unlink(self, cr, uid, ids, context=None): # cascade-delete attachments that are directly attached to the message (should only happen - # for mail.messages that act as parent for a standalone mail.mail record. + # for mail.messages that act as parent for a standalone mail.mail record). attachments_to_delete = [] - for mail in self.browse(cr, uid, ids, context=context): - for attach in mail.attachment_ids: - if attach.res_model == 'mail.message' and attach.res_id == mail.id: + for message in self.browse(cr, uid, ids, context=context): + for attach in message.attachment_ids: + if attach.res_model == self._name and attach.res_id == message.id: attachments_to_delete.append(attach.id) if attachments_to_delete: self.pool.get('ir.attachment').unlink(cr, uid, attachments_to_delete, context=context) - return super(mail_message,self).unlink(cr, uid, ids, context=context) + return super(mail_message, self).unlink(cr, uid, ids, context=context) def notify(self, cr, uid, newid, context=None): """ Add the related record followers to the destination partner_ids. diff --git a/addons/mail/static/src/css/mail.css b/addons/mail/static/src/css/mail.css index 815f0ec92c4..92ead6f2432 100644 --- a/addons/mail/static/src/css/mail.css +++ b/addons/mail/static/src/css/mail.css @@ -230,6 +230,10 @@ clear: both; } +.openerp .oe_mail_msg_content a { + cursor: pointer; +} + .openerp img.oe_mail_icon { width: 50px; height: 50px; @@ -307,6 +311,29 @@ padding: 1px; } +/* attachment button: override of openerp values */ +.openerp .oe_mail_msg_content .oe_mail_compose_message_icons div.oe_hidden_input_file { + display: inline-block; + width: 24px; + height: 24px; + margin: 2px; +} +.openerp .oe_mail_msg_content .oe_mail_compose_message_icons div.oe_hidden_input_file button { + margin: 0px; +} +.openerp .oe_mail_msg_content .oe_mail_compose_message_icons input.oe_form_binary_file { + bottom: 0px; + top: auto; + left: auto; + right: 28px; + height: 26px; + width: 26px; + min-width: 22px; + font-size: 0px; + margin: 0px; + padding: 0px; +} + /* ------------------------------------------------------------ */ /* Messages layout /* ------------------------------------------------------------ */ @@ -322,31 +349,20 @@ text-decoration: none; } -.openerp .oe_mail_msg_footer { - color: #888; -} - -.openerp .oe_mail_msg_footer li { - float: left; - margin-right: 3px; -} - -.openerp .oe_mail_msg_body { +.openerp .oe_mail_msg .oe_mail_msg_body { margin-bottom: .5em; text-align: justify; } -.openerp .oe_mail_msg_record_body pre { +.openerp .oe_mail_msg .oe_mail_msg_body pre { font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif; margin: 0px; -} - -.openerp .oe_mail_msg_body span.oe_mail_msg_tail { + white-space: pre-wrap; display: inline; } /* Read more/less link */ -.openerp .oe_mail_msg_content .oe_mail_reduce { +.openerp .oe_mail_msg span.oe_mail_reduce { position: absolute; right: 0; } @@ -364,16 +380,23 @@ border-top: 4px solid #404040; } -.openerp .oe_mail_msg_footer li:after { +/* Message footer */ +.openerp .oe_mail_msg .oe_mail_msg_footer { + color: #888; +} +.openerp .oe_mail_msg .oe_mail_msg_footer li { + float: left; + margin-right: 3px; +} +.openerp .oe_mail_msg .oe_mail_msg_footer li:after { content: " ยท "; } - -.openerp .oe_mail_msg_footer li:last-child:after { +.openerp .oe_mail_msg .oe_mail_msg_footer li:last-child:after { content: ""; } /* Attachments list */ -.openerp ul.oe_mail_msg_attachments { +.openerp .oe_mail_msg_content ul.oe_mail_msg_attachments { display: none; width: 100%; border-top: 1px solid #CCC; @@ -381,7 +404,7 @@ padding: .5em 0; list-style-position: inside; } -.openerp ul.oe_mail_msg_attachments li { +.openerp .oe_mail_msg_content ul.oe_mail_msg_attachments li { float: none; margin: 0; padding: 0; diff --git a/addons/mail/static/src/css/mail_group.css b/addons/mail/static/src/css/mail_group.css index 8f4ca61d531..b700cb8c80f 100644 --- a/addons/mail/static/src/css/mail_group.css +++ b/addons/mail/static/src/css/mail_group.css @@ -12,7 +12,6 @@ min-height: 0px; max-height: none; padding: 0px 18px; - max-width: 80%; } /* Resize footer width */ diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js index 3f23e65f00d..71665bc69fb 100644 --- a/addons/mail/static/src/js/mail.js +++ b/addons/mail/static/src/js/mail.js @@ -20,10 +20,8 @@ openerp.mail = function(session) { do_action: function(action, on_close) { if (action.res_model == 'mail.compose.message' && action.context && action.context.redirect == true && - this.fields && this.fields.message_ids && - this.fields.message_ids.view.get("actual_mode") != 'create') { - var record_thread = this.fields.message_ids; - var thread = record_thread.thread; + this.fields && this.fields.message_ids && this.fields.message_ids.view.get("actual_mode") != 'create') { + var thread = this.fields.message_ids.thread; thread.refresh_composition_form(action.context); return true; } @@ -33,35 +31,32 @@ openerp.mail = function(session) { }, }); + /** * ------------------------------------------------------------ * ChatterUtils * ------------------------------------------------------------ * - * This class holds a few tools method that will be used by - * the various Chatter widgets. - * + * This class holds a few tools method for Chatter. * Some regular expressions not used anymore, kept because I want to * - (^|\s)@((\w|@|\.)*): @login@log.log - * 1. '(void)' - * 2. login@log.log * - (^|\s)\[(\w+).(\w+),(\d)\|*((\w|[@ .,])*)\]: [ir.attachment,3|My Label], * for internal links - * 1. '(void)' - * 2. 'ir' - * 3. 'attachment' - * 4. '3' - * 5. 'My Label' */ mail.ChatterUtils = { - /** get an image in /web/binary/image?... */ - get_image: function(session_prefix, session_id, model, field, id) { - return session_prefix + '/web/binary/image?session_id=' + session_id + '&model=' + model + '&field=' + field + '&id=' + (id || ''); + /** Get an image in /web/binary/image?... */ + get_image: function(session, model, field, id) { + return session.prefix + '/web/binary/image?session_id=' + session.session_id + '&model=' + model + '&field=' + field + '&id=' + (id || ''); }, - /** check if the current user is the message author */ + /** Get the url of an attachment {'id': id} */ + get_attachment_url: function (session, attachment) { + return session.origin + '/web/binary/saveas?session_id=' + session.session_id + '&model=ir.attachment&field=datas&filename_field=datas_fname&id=' + attachment['id']; + }, + + /** Check if the current user is the message author */ is_author: function (widget, message_user_id) { return (widget.session && widget.session.uid != 0 && widget.session.uid == message_user_id); }, @@ -107,21 +102,19 @@ openerp.mail = function(session) { init: function (parent, options) { var self = this; this._super(parent); + this.attachment_ids = []; // options this.options = options || {}; this.options.context = options.context || {}; this.options.form_xml_id = options.form_xml_id || 'email_compose_message_wizard_form_chatter'; this.options.form_view_id = options.form_view_id || false; - // debug - // console.groupCollapsed('New ComposeMessage: model', this.options.context.default_res_model, ', id', this.options.context.default_res_id); - // console.log('context:', this.options.context); - // console.groupEnd(); + this.show_attachment_delete = true; }, 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); + var user_avatar = mail.ChatterUtils.get_image(this.session, 'res.users', 'image_small', this.session.uid); this.$el.find('img.oe_mail_icon').attr('src', user_avatar); this.$el.find('div.oe_mail_msg_content').empty(); // create a context for the dataset and default_get of the wizard @@ -152,10 +145,52 @@ openerp.mail = function(session) { }); // add the form, bind events, activate the form var msg_node = this.$el.find('div.oe_mail_msg_content'); - return $.when(this.form_view.appendTo(msg_node)).pipe(function() { - self.bind_events(); - self.form_view.do_show(); - }); + return $.when(this.form_view.appendTo(msg_node)).pipe(this.proxy('postprocess_create_form_view')); + }, + + postprocess_create_form_view: function () { + // handle attachment button + this.fileupload_id = _.uniqueId('oe_fileupload'); + var button_attach = this.$el.find('button.oe_mail_compose_message_attachment'); + var rendered = session.web.qweb.render('mail.compose_message.add_attachment', {'widget': this}); + $(rendered).insertBefore(button_attach); + // move the button inside div.oe_hidden_input_file + var input_node = this.$el.find('input[name=ufile]'); + button_attach.detach().insertAfter(input_node); + // set the function called when attachments are added + this.$el.find('input.oe_form_binary_file').change(this.on_attachment_change); + this.bind_events(); + this.form_view.do_show(); + }, + + on_attachment_change: function (event) { + var $target = $(event.target); + if ($target.val() !== '') { + this.$el.find('form.oe_form_binary_form').submit(); + session.web.blockUI(); + } + }, + + on_attachment_delete: function (event) { + if (event.target.dataset && event.target.dataset.id) { + var attachment_id = parseInt(event.target.dataset.id); + var idx = _.pluck(this.attachment_ids, 'id').indexOf(attachment_id); + if (idx == -1) return false; + new session.web.DataSetSearch(this, 'ir.attachment').unlink(attachment_id); + this.attachment_ids.splice(idx, 1); + this.display_attachments(); + } + }, + + display_attachments: function () { + var attach_node = this.$el.find('div.oe_mail_compose_message_attachments'); + var rendered = session.web.qweb.render('mail.thread.message.attachments', {'record': this}); + attach_node.empty(); + $(rendered).appendTo(attach_node); + this.$el.find('.oe_mail_msg_attachments').show(); + var composer_attachment_ids = _.pluck(this.attachment_ids, 'id'); + var onchange_like = {'value': {'attachment_ids': composer_attachment_ids}} + this.form_view.on_processed_onchange(onchange_like, []); }, /** @@ -166,13 +201,18 @@ openerp.mail = function(session) { refresh: function (new_context) { if (! this.form_view) return; var self = this; + this.attachments = []; this.options.context = _.extend(this.options.context, new_context || {}); this.ds_compose.context = _.extend(this.ds_compose.context, this.options.context); return this.ds_compose.call('default_get', [ - ['subject', 'body_text', 'body', 'attachment_ids', 'partner_ids', 'composition_mode', + ['subject', 'body_text', 'body', 'partner_ids', 'composition_mode', 'use_template', 'template_id', 'model', 'res_id', 'parent_id', 'content_subtype'], this.ds_compose.get_context(), - ]).then( function (result) { self.form_view.on_processed_onchange({'value': result}, []); }); + ]).then( function (result) { + self.form_view.on_processed_onchange({'value': result}, []); + self.attachment_ids = []; + self.display_attachments(); + }); }, /** @@ -180,11 +220,17 @@ openerp.mail = function(session) { * in the function. */ bind_events: function() { var self = this; - // event: click on 'Attachment' icon-link that opens the dialog to - // add an attachment. - this.$el.on('click', 'button.oe_mail_compose_message_attachment', function (event) { - event.stopImmediatePropagation(); + // event: add a new attachment + $(window).on(this.fileupload_id, function() { + var args = [].slice.call(arguments).slice(1); + var attachment = args[0]; + attachment['url'] = mail.ChatterUtils.get_attachment_url(self.session, attachment); + self.attachment_ids.push(attachment); + self.display_attachments(); + session.web.unblockUI(); }); + // event: delete an attachment + this.$el.on('click', '.oe_mail_attachment_delete', self.on_attachment_delete); }, }), @@ -206,47 +252,48 @@ openerp.mail = function(session) { /** * @param {Object} parent parent - * @param {Object} [options] - * @param {Object} [options.context] context of the thread. It should + * @param {Array} [domain] + * @param {Object} [context] context of the thread. It should contain at least default_model, default_res_id. Please refer to the ComposeMessage widget for more information about it. - * @param {Number} [options.thread_level=0] number of thread levels + * @param {Object} [options] * @param {Number} [options.message_ids=null] ids for message_fetch * @param {Number} [options.message_data=null] already formatted message data, for subthreads getting data from their parent - * @param {Boolean} [options.composer] use the advanced composer, or + * @param {Number} [options.thread_level=0] number of thread levels + * @param {Boolean} [options.use_composer] use the advanced composer, or the default basic textarea if not set * @param {Number} [options.truncate_limit=250] number of character to * display before having a "show more" link; note that the text * will not be truncated if it does not have 110% of the parameter */ - init: function(parent, options) { + init: function(parent, domain, context, options) { this._super(parent); - // options - this.options = options || {}; - this.options.domain = options.domain || []; - this.options.context = _.extend({ + this.domain = domain || []; + this.context = _.extend({ default_model: 'mail.thread', default_res_id: 0, - default_parent_id: false }, options.context || {}); - this.options.thread_level = options.thread_level || 0; - this.options.composer = options.composer || false; - this.options.message_ids = options.message_ids || null; - this.options.message_data = options.message_data || null; + default_parent_id: false }, context || {}); + // options + this.options = { + message_ids: options.message_ids || null, + message_data: options.message_data || null, + thread_level: options.thread_level || 0, + use_composer: options.use_composer || false, + show_header_compose: options.show_header_compose != undefined ? options.show_header_compose: true, + show_record_name: options.show_record_name != undefined ? options.show_record_name: true, + show_reply: options.show_reply || false, + show_reply_by_email: options.show_reply_by_email || false, + show_dd_reply_by_email:options.show_dd_reply_by_email != undefined ? options.show_dd_reply_by_email: true, + show_dd_delete: options.show_dd_delete || false, + show_dd_hide: options.show_dd_hide || false, + show_more: options.show_more || false, + truncate_limit: options.truncate_limit || 250, + } // datasets and internal vars - this.ds_thread = new session.web.DataSetSearch(this, this.options.context.default_model); + this.ds_thread = new session.web.DataSetSearch(this, this.context.default_model); this.ds_notification = new session.web.DataSetSearch(this, 'mail.notification'); this.ds_message = new session.web.DataSetSearch(this, 'mail.message'); - // display customization vars - this.display = { - truncate_limit: options.truncate_limit || 250, - show_header_compose: options.show_header_compose || false, - show_reply: options.show_reply || false, - show_delete: options.show_delete || false, - show_hide: options.show_hide || false, - show_reply_by_email: options.show_reply_by_email || false, - show_more: options.show_more || false, - } }, start: function() { @@ -256,7 +303,7 @@ openerp.mail = function(session) { // fetch and display message, using message_ids if set var display_done = $.when(this.message_fetch(true, [], {})).then(this.proxy('do_customize_display')); // add message composition form view - if (this.display.show_header_compose && this.options.composer) { + if (this.options.show_header_compose && this.options.use_composer) { var compose_done = this.instantiate_composition_form(); } return display_done && compose_done; @@ -266,7 +313,9 @@ openerp.mail = function(session) { * - show_header_compose: show the composition form in the header */ do_customize_display: function() { this.display_user_avatar(); - if (this.display.show_header_compose) { this.$el.find('div.oe_mail_thread_action').eq(0).show(); } + if (this.options.show_header_compose) { + this.$el.find('div.oe_mail_thread_action').eq(0).show(); + } }, /** @@ -274,10 +323,8 @@ openerp.mail = function(session) { * in the function. */ bind_events: function() { var self = this; - // event: click on 'more' at bottom of thread - this.$el.find('button.oe_mail_button_more').click(function () { - self.do_message_fetch(); - }); + // event: click on 'More' at bottom of thread + this.$el.on('click', 'button.oe_mail_button_more', this.do_message_fetch_more); // event: writing in basic textarea of composition form (quick reply) this.$el.find('textarea.oe_mail_compose_textarea').keyup(function (event) { var charCode = (event.which) ? event.which : window.event.keyCode; @@ -286,41 +333,21 @@ openerp.mail = function(session) { }); // event: click on 'Reply' in msg this.$el.on('click', 'a.oe_mail_msg_reply', function (event) { - event.preventDefault(); - event.stopPropagation(); var act_dom = $(this).parents('li.oe_mail_thread_msg').eq(0).find('div.oe_mail_thread_action:first'); act_dom.toggle(); }); - // event: click on 'attachment(s)' in msg + // event: click on 'Attachment(s)' in msg this.$el.on('click', 'a.oe_mail_msg_view_attachments', function (event) { - event.preventDefault(); - event.stopPropagation(); var act_dom = $(this).parent().parent().parent().find('.oe_mail_msg_attachments'); act_dom.toggle(); }); // event: click on 'Delete' in msg side menu - this.$el.on('click', 'a.oe_mail_msg_delete', function (event) { - event.preventDefault(); - event.stopPropagation(); - if (! confirm(_t("Do you really want to delete this message?"))) { return false; } - var msg_id = event.srcElement.dataset.id; - if (! msg_id) return false; - $(event.srcElement).parents('li.oe_mail_thread_msg').eq(0).remove(); - return self.ds_msg.unlink([parseInt(msg_id)]); - }); + this.$el.on('click', 'a.oe_mail_msg_delete', this.on_message_delete); // event: click on 'Hide' in msg side menu - this.$el.on('click', 'a.oe_mail_msg_hide', function (event) { - event.preventDefault(); - event.stopPropagation(); - var msg_id = event.srcElement.dataset.id; - if (! msg_id) return false; - $(event.srcElement).parents('li.oe_mail_thread_msg').eq(0).remove(); - return self.ds_notif.call('set_message_read', [parseInt(msg_id)]); - }); - // event: click on "Reply by email" in msg side menu (email style) + this.$el.on('click', 'a.oe_mail_msg_hide', this.on_message_read); + // event: click on 'Reply by email' in msg side menu this.$el.on('click', 'a.oe_mail_msg_reply_by_email', function (event) { - event.preventDefault(); - event.stopPropagation(); + if (! self.compose_message_widget) return true; var msg_id = event.srcElement.dataset.msg_id; if (! msg_id) return false; self.compose_message_widget.refresh({ @@ -330,20 +357,37 @@ openerp.mail = function(session) { }); }, + on_message_delete: function (event) { + if (! confirm(_t("Do you really want to delete this message?"))) { return false; } + var msg_id = event.srcElement.dataset.id; + if (! msg_id) return false; + $(event.srcElement).parents('li.oe_mail_thread_msg').eq(0).remove(); + return this.ds_message.unlink([parseInt(msg_id)]); + }, + + on_message_read: function (event) { + //TDE: TODO + var msg_id = event.srcElement.dataset.id; + if (! msg_id) return false; + $(event.srcElement).parents('li.oe_mail_thread_msg').eq(0).remove(); + return this.ds_notification.call('set_message_read', [parseInt(msg_id)]); + }, + /** * Override-hack of do_action: automatically reload the chatter. * Normally it should be called only when clicking on 'Post/Send' * in the composition form. */ do_action: function(action, on_close) { + //TDE: TODO: instead of reloading, push the message ? this.message_clean(); this.message_fetch(); if (this.compose_message_widget) { this.compose_message_widget.refresh({ 'default_composition_mode': 'comment', - 'default_parent_id': this.options.default_parent_id, + 'default_parent_id': this.context.default_parent_id, 'default_content_subtype': 'plain'} ); } - return this._super(action, on_close); + // return this._super(action, on_close); }, /** Instantiate the composition form, with every parameters in context @@ -353,7 +397,7 @@ openerp.mail = function(session) { this.compose_message_widget.destroy(); } this.compose_message_widget = new mail.ComposeMessage(this, { - 'context': _.extend(context || {}, this.options.context), + 'context': _.extend(context || {}, this.context), }); var composition_node = this.$el.find('div.oe_mail_thread_action'); composition_node.empty(); @@ -375,20 +419,20 @@ openerp.mail = function(session) { * @param {Bool} initial_mode: initial mode: try to use message_data or * message_ids, if nothing available perform a message_read; otherwise * directly perform a message_read - * @param {Array} additional_domain: added to options.domain - * @param {Object} additional_context: added to options.context + * @param {Array} additional_domain: added to this.domain + * @param {Object} additional_context: added to this.context */ message_fetch: function (initial_mode, additional_domain, additional_context) { var self = this; // domain and context: options + additional - fetch_domain = _.flatten([this.options.domain, additional_domain || []], true) - fetch_context = _.extend(this.options.context, additional_context || {}) - // if message_ids is set: try to use it + fetch_domain = _.flatten([this.domain, additional_domain || []], true) + fetch_context = _.extend(this.context, additional_context || {}) + // initial mode: try to use message_data or message_ids if (initial_mode && this.options.message_data) { return this.message_display(this.options.message_data); } - return this.ds_message.call('message_read', - [(initial_mode && this.options.message_ids) || false, fetch_domain, this.options.thread_level, undefined, fetch_context] + message_ids = initial_mode && this.options.message_ids != null && this.options.message_ids || false; + return this.ds_message.call('message_read', [message_ids, fetch_domain, this.options.thread_level, undefined, fetch_context] ).then(this.proxy('message_display')); }, @@ -408,19 +452,19 @@ openerp.mail = function(session) { } else { self.display_record(record); - // if (self.options.thread_level >= 0) { - self.thread = new mail.Thread(self, { - 'context': { - 'default_model': record.model, + self.thread = new mail.Thread(self, self.domain, + { 'default_model': record.model, 'default_res_id': record.res_id, 'default_parent_id': record.id }, - 'message_data': record.child_ids, 'thread_level': self.options.thread_level-1, - 'show_header_compose': false, 'show_reply': self.options.thread_level > 1, - 'show_hide': self.display.show_hide, 'show_delete': self.display.show_delete, - }); + { 'message_data': record.child_ids, + 'thread_level': self.options.thread_level - 1, + 'show_header_compose': false, + 'show_reply': self.options.show_reply && self.options.thread_level > 1, + 'show_reply_by_email': self.options.show_reply_by_email, + 'show_dd_hide': self.options.show_dd_hide, + 'show_dd_delete': self.options.show_dd_delete }); self.$el.find('li.oe_mail_thread_msg:last').append('
'); self.thread.appendTo(self.$el.find('div.oe_mail_thread_subthread:last')); - // } } }); if (! _expendable) { @@ -432,7 +476,7 @@ openerp.mail = function(session) { * - record.date: formatting according to the user timezone * - record.timerelative: relative time givein by timeago lib * - record.avatar: image url - * - record.attachments[].url: url of each attachment + * - record.attachment_ids[].url: url of each attachment * - record.is_author: is the current user the author of the record */ display_record: function (record) { // formatting and additional fields @@ -441,21 +485,18 @@ openerp.mail = function(session) { if (record.type == 'email') { record.avatar = ('/mail/static/src/img/email_icon.png'); } else { - record.avatar = mail.ChatterUtils.get_image(this.session.prefix, this.session.session_id, 'res.partner', 'image_small', record.author_id[0]); + record.avatar = mail.ChatterUtils.get_image(this.session, 'res.partner', 'image_small', record.author_id[0]); } - //TDE: FIX - if (record.attachments) { - for (var l in record.attachments) { - var url = self.session.origin + '/web/binary/saveas?session_id=' + self.session.session_id + '&model=ir.attachment&field=datas&filename_field=datas_fname&id='+records[k].attachments[l].id; - record.attachments[l].url = url; - } + for (var l in record.attachment_ids) { + var attach = record.attachment_ids[l]; + attach['url'] = mail.ChatterUtils.get_attachment_url(this.session, attach); } record.is_author = mail.ChatterUtils.is_author(this, record.author_user_id[0]); // render, add the expand feature - var rendered = session.web.qweb.render('mail.thread.message', {'record': record, 'thread': this, 'params': this.options, 'display': this.display}); + var rendered = session.web.qweb.render('mail.thread.message', {'record': record, 'thread': this, 'options': this.options}); $(rendered).appendTo(this.$el.children('div.oe_mail_thread_display:first')); - this.$el.find('div.oe_mail_msg_record_body').expander({ - slicePoint: this.options.msg_more_limit, + this.$el.find('div.oe_mail_msg_body').expander({ + slicePoint: this.options.truncate_limit, expandText: 'read more', userCollapseText: '[^]', detailClass: 'oe_mail_msg_tail', @@ -474,7 +515,7 @@ openerp.mail = function(session) { }, display_user_avatar: function () { - var avatar = mail.ChatterUtils.get_image(this.session.prefix, this.session.session_id, 'res.users', 'image_small', this.session.uid); + var avatar = mail.ChatterUtils.get_image(this.session, 'res.users', 'image_small', this.session.uid); return this.$el.find('img.oe_mail_icon').attr('src', avatar); }, @@ -486,12 +527,12 @@ openerp.mail = function(session) { comment_node.val(''); } return this.ds_thread.call('message_post', [ - [this.options.context.default_res_id], body, false, 'comment', this.options.context.default_parent_id, undefined] + [this.context.default_res_id], body, false, 'comment', this.context.default_parent_id, undefined] ).then(self.message_fetch()); }, /** Action: 'shows more' to fetch new messages */ - do_message_fetch: function () { + do_message_fetch_more: function () { return this.message_fetch(false, this.fetch_more_domain, this.fetch_more_context); }, @@ -534,13 +575,11 @@ openerp.mail = function(session) { * mail_thread Widget * ------------------------------------------------------------ * - * This widget handles the display of the Chatter on documents. + * This widget handles the display of messages on a document. Its main + * use is to receive a context and a domain, and to delegate the message + * fetching and displaying to the Thread widget. */ - - /* Add mail_thread widget to registry */ session.web.form.widgets.add('mail_thread', 'openerp.mail.RecordThread'); - - /** mail_thread widget: thread of comments */ mail.RecordThread = session.web.form.AbstractField.extend({ template: 'mail.record_thread', @@ -553,8 +592,7 @@ openerp.mail = function(session) { start: function() { this._super.apply(this, arguments); - // NB: all the widget should be modified to check the actual_mode property on view, not use - // any other method to know if the view is in create mode anymore + // NB: check the actual_mode property on view to know if the view is in create mode anymore this.view.on("change:actual_mode", this, this._check_visibility); this._check_visibility(); }, @@ -563,11 +601,6 @@ openerp.mail = function(session) { this.$el.toggle(this.view.get("actual_mode") !== "create"); }, - destroy: function() { - if (this.thread) { this.thread.destroy(); } - this._super.apply(this, arguments); - }, - set_value: function() { var self = this; this._super.apply(this, arguments); @@ -579,15 +612,15 @@ openerp.mail = function(session) { _.extend(this.options.context, { default_res_id: this.view.datarecord.id, default_model: this.view.model }); + // update domain + var domain = this.options.domain.concat([['model', '=', this.view.model], ['res_id', '=', this.view.datarecord.id]]); // create and render Thread widget this.$el.find('div.oe_mail_recthread_main').empty(); - if (this.thread) { this.thread.destroy(); } - var thread = new mail.Thread(self, { - 'context': this.options.context, - 'thread_level': this.options.thread_level, 'show_header_compose': true, - 'message_ids': this.get_value(), - 'show_delete': true, 'composer': true }); - this.thread = thread; + var thread = new mail.Thread(self, domain, this.options.context, + { 'thread_level': this.options.thread_level, + 'use_composer': true, + 'show_dd_delete': true, + 'show_reply_by_email': true }); return thread.appendTo(this.$el.find('div.oe_mail_recthread_main')); }, }); @@ -598,22 +631,22 @@ openerp.mail = function(session) { * Wall Widget * ------------------------------------------------------------ * - * This widget handles the display of the Chatter on the Wall. + * This widget handles the display of messages on a Wall. Its main + * use is to receive a context and a domain, and to delegate the message + * fetching and displaying to the Thread widget. */ - - /* Add WallView widget to registry */ session.web.client_actions.add('mail.wall', 'session.mail.Wall'); - - /* WallView widget: a wall of messages */ mail.Wall = session.web.Widget.extend({ template: 'mail.wall', /** * @param {Object} parent parent * @param {Object} [options] - * @param {Number} [options.domain] domain on the Wall, is an array. - * @param {Number} [options.domain] context, is an object. It should + * @param {Array} [options.domain] domain on the Wall + * @param {Object} [options.context] context, is an object. It should * contain default_model, default_res_id, to give it to the threads. + * @param {Number} [options.thread_level] number of thread levels to display + * 0 being flat. */ init: function (parent, options) { this._super(parent); @@ -621,23 +654,15 @@ openerp.mail = function(session) { this.options.domain = options.domain || []; this.options.context = options.context || {}; this.options.thread_level = options.thread_level || 1; - this.thread_list = []; - this.ds_msg = new session.web.DataSetSearch(this, 'mail.message'); - // for search view - this.search = {'domain': [], 'context': {}, 'groupby': {}} this.search_results = {'domain': [], 'context': {}, 'groupby': {}} + this.ds_msg = new session.web.DataSetSearch(this, 'mail.message'); }, start: function () { this._super.apply(this, arguments); - var search_view_ready = this.load_search_view({}, false); - var thread_displayed = this.message_display(); - return (search_view_ready && thread_displayed); - }, - - destroy: function () { - for (var i in this.thread_list) { this.thread_list[i].destroy(); } - this._super.apply(this, arguments); + var searchview_ready = this.load_searchview({}, false); + var thread_displayed = this.message_render(); + return (searchview_ready && thread_displayed); }, /** @@ -645,7 +670,7 @@ openerp.mail = function(session) { * @param {Object} defaults ?? * @param {Boolean} hidden some kind of trick we do not care here */ - load_search_view: function (defaults, hidden) { + load_searchview: function (defaults, hidden) { var self = this; this.searchview = new session.web.SearchView(this, this.ds_msg, false, defaults || {}, hidden || false); return this.searchview.appendTo(this.$el.find('.oe_view_manager_view_search')).then(function () { @@ -654,9 +679,8 @@ openerp.mail = function(session) { }, /** - * Aggregate the domains, contexts and groupbys in parameter - * with those from search form, and then calls fetch_comments - * to actually fetch comments + * Get the domains, contexts and groupbys in parameter from search + * view, then render the filtered threads. * @param {Array} domains * @param {Array} contexts * @param {Array} groupbys @@ -671,30 +695,24 @@ openerp.mail = function(session) { self.search_results['context'] = results.context; self.search_results['domain'] = results.domain; self.search_results['groupby'] = results.group_by; - self.message_clean(); - return self.message_display(); + return self.message_render(); }); }, - /** Clean the wall */ - message_clean: function() { + /** Clean and display the threads */ + message_render: function () { this.$el.find('ul.oe_mail_wall_threads').empty(); - }, - - /** Display the Wall threads */ - message_display: function () { + var domain = this.options.domain.concat(this.search_results['domain']); var render_res = session.web.qweb.render('mail.wall_thread_container', {}); - $('Please call me as soon as possible this afternoon!
- +--
Sylvie
@@ -153,7 +153,7 @@ class test_mail(common.TransactionCase):
test_msg_id = '\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n
\n',
'plaintext mail incorrectly parsed')
@@ -409,12 +409,14 @@ class test_mail(common.TransactionCase):
self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message incorrect content_subtype')
# 2. Post the comment, get created message
+ parent_id = message.id
mail_compose.send_mail(cr, uid, [compose_id])
group_pigs.refresh()
message = group_pigs.message_ids[0]
- # Test: mail.message: subject as Re:.., body in html
+ # Test: mail.message: subject as Re:.., body in html, parent_id
self.assertEqual(message.subject, _msg_reply, 'mail.message incorrect subject')
self.assertIn('Administrator wrote:
', message.body, 'mail.message body is incorrect')
+ self.assertEqual(message.parent_id and message.parent_id.id, parent_id, 'mail.message parent_id incorrect')
# Test: mail.message: attachments
for attach in message.attachment_ids:
self.assertEqual(attach.res_model, 'mail.group', 'mail.message attachment res_model incorrect')
@@ -458,6 +460,7 @@ class test_mail(common.TransactionCase):
# It will be updated as soon as we have fixed specs !
cr, uid = self.cr, self.uid
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
+
def _compare_structures(struct1, struct2, n=0):
# print '%scompare structure' % ('\t' * n)
self.assertEqual(len(struct1), len(struct2), 'message_read structure number of childs incorrect')
diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py
index 3153ccc2069..03c5a7cfe8b 100644
--- a/addons/mail/wizard/mail_compose_message.py
+++ b/addons/mail/wizard/mail_compose_message.py
@@ -31,6 +31,7 @@ from tools.translate import _
# main mako-like expression pattern
EXPRESSION_PATTERN = re.compile('(\$\{.+?\})')
+
class mail_compose_message(osv.TransientModel):
""" Generic message composition wizard. You may inherit from this wizard
at model and view levels to provide specific features.
@@ -117,6 +118,7 @@ class mail_compose_message(osv.TransientModel):
'body_text': lambda self, cr, uid, ctx={}: False,
'body': lambda self, cr, uid, ctx={}: '',
'subject': lambda self, cr, uid, ctx={}: False,
+ 'partner_ids': lambda self, cr, uid, ctx={}: [],
}
def notify(self, cr, uid, newid, context=None):
@@ -152,7 +154,7 @@ class mail_compose_message(osv.TransientModel):
# 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)):
+ if not (reply_subject.startswith('Re:') or reply_subject.startswith(re_prefix)) and message_data.subject:
reply_subject = "%s %s" % (re_prefix, reply_subject)
# create the reply in the body
reply_body = _('Pigs rules
%(body)s
%s
' % tools.ustr(wizard.body_text),
+ 'parent_id': wizard.parent_id and wizard.parent_id.id,
'partner_ids': [(4, partner.id) for partner in wizard.partner_ids],
'attachments': [(attach.datas_fname or attach.name, base64.b64decode(attach.datas)) for attach in wizard.attachment_ids],
}
@@ -268,6 +264,8 @@ class mail_compose_message(osv.TransientModel):
post_values.update(email_dict)
# post the message
active_model_pool.message_post(cr, uid, [res_id], type='comment', context=context, **post_values)
+ # post process: update attachments, because id is not necessarily known when adding attachments in Chatter
+ self.pool.get('ir.attachment').write(cr, uid, [attach.id for attach in wizard.attachment_ids], {'res_id': wizard.id}, context=context)
return {'type': 'ir.actions.act_window_close'}
@@ -296,6 +294,7 @@ class mail_compose_message(osv.TransientModel):
"""
if context is None:
context = {}
+
def merge(match):
exp = str(match.group()[2:-1]).strip()
result = eval(exp, {
diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py
index 592bf783a64..1f6279af3ad 100644
--- a/addons/purchase/purchase.py
+++ b/addons/purchase/purchase.py
@@ -347,13 +347,18 @@ class purchase_order(osv.osv):
template_id = template and template[1] or False
res = mod_obj.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')
res_id = res and res[1] or False
- ctx = dict(context, active_model='purchase.order', active_id=ids[0])
- ctx.update({'mail.compose.template_id': template_id})
+ ctx = dict(context)
+ ctx.update({
+ 'default_model': 'purchase.order',
+ 'default_res_id': ids[0],
+ 'default_use_template': True,
+ 'default_template_id': template_id,
+ })
return {
'view_type': 'form',
'view_mode': 'form',
'res_model': 'mail.compose.message',
- 'views': [(res_id,'form')],
+ 'views': [(res_id, 'form')],
'view_id': res_id,
'type': 'ir.actions.act_window',
'target': 'new',