[MERGE] Merged with addons/trunk.

bzr revid: tde@openerp.com-20120912142748-mnsaftqea2anwkrc
This commit is contained in:
Thibault Delavallée 2012-09-12 16:27:48 +02:00
commit 2b7112d7ae
13 changed files with 357 additions and 278 deletions

View File

@ -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):

View File

@ -1045,6 +1045,8 @@ class account_voucher(osv.osv):
# if the amount encoded in voucher is equal to the amount unreconciled, we need to compute the
# currency rate difference
if line.amount == line.amount_unreconciled:
if not line.move_line_id.amount_residual:
raise osv.except_osv(_('Wrong bank statement line'),_("You have to delete the bank statement line which the payment was reconciled to manually. Please check the payment of the partner %s by the amount of %s.")%(line.voucher_id.partner_id.name, line.voucher_id.amount))
currency_rate_difference = line.move_line_id.amount_residual - amount
else:
currency_rate_difference = 0.0

View File

@ -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

View File

@ -148,7 +148,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)
@ -307,15 +307,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.

View File

@ -226,6 +226,10 @@
clear: both;
}
.openerp .oe_mail_msg_content a {
cursor: pointer;
}
.openerp img.oe_mail_icon {
width: 50px;
height: 50px;
@ -303,6 +307,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
/* ------------------------------------------------------------ */
@ -318,31 +345,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;
}
@ -360,16 +376,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;
@ -377,7 +400,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;

View File

@ -12,7 +12,6 @@
min-height: 0px;
max-height: none;
padding: 0px 18px;
max-width: 80%;
}
/* Resize footer width */

View File

@ -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('<div class="oe_mail_thread_subthread"/>');
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', {});
$('<li class="oe_mail_wall_thread">').html(render_res).appendTo(this.$el.find('ul.oe_mail_wall_threads'));
var thread = new mail.Thread(this, {
'domain': this.options.domain, 'context': this.options.context,
'thread_level': this.options.thread_level, 'composer': true,
// display options
'show_header_compose': true, 'show_reply': this.options.thread_level > 0,
'show_hide': true, 'show_reply_by_email': true,
$(render_res).appendTo(this.$el.find('ul.oe_mail_wall_threads'));
var thread = new mail.Thread(this, domain, this.options.context,
{ 'thread_level': this.options.thread_level,
'use_composer': true,
'show_reply': this.options.thread_level > 0,
'show_dd_hide': true,
}
);
thread.appendTo(this.$el.find('li.oe_mail_wall_thread:last'));
this.thread_list.push(thread);
return thread.appendTo(this.$el.find('li.oe_mail_wall_thread:last'));
},
});
};

View File

@ -98,7 +98,7 @@ openerp_mail_followers = function(session, mail) {
var node_user_list = this.$el.find('ul.oe_mail_followers_display').empty();
this.$el.find('div.oe_mail_recthread_followers h4').html(this.options.title + ' (' + records.length + ')');
_(records).each(function (record) {
record.avatar_url = mail.ChatterUtils.get_image(self.session.prefix, self.session.session_id, 'res.partner', 'image_small', record.id);
record.avatar_url = mail.ChatterUtils.get_image(self.session, 'res.partner', 'image_small', record.id);
$(session.web.qweb.render('mail.followers.partner', {'record': record})).appendTo(node_user_list);
});
if (this.message_is_follower) {

View File

@ -39,8 +39,8 @@
wall_thread_container template for the wall
Each discussion thread is contained inside this template
-->
<t t-name="mail.wall_thread_container">
</t>
<li t-name="mail.wall_thread_container" class="oe_mail_wall_thread">
</li>
<!--
record_thread main template
@ -73,6 +73,20 @@
</div>
</t>
<!--
mail.compose_message.add_attachment template
Small template to be inserted in the composition form to add attachments
-->
<t t-name="mail.compose_message.add_attachment">
<t t-call="HiddenInputFile">
<t t-set="fileupload_id" t-value="widget.fileupload_id"/>
<t t-set="fileupload_action">/web/binary/upload_attachment</t>
<input type="hidden" name="model" t-att-value="widget.form_view.model"/>
<input type="hidden" name="id" t-att-value="widget.form_view.datarecord.id || 0"/>
<input type="hidden" name="session_id" t-att-value="widget.session.session_id"/>
</t>
</t>
<!--
thread template
This template holds a thread of comments. It begins with an actions
@ -100,16 +114,14 @@
<!-- dropdown menu with message options and actions -->
<span class="oe_dropdown_toggle oe_dropdown_arrow">
<ul class="oe_dropdown_menu">
<t t-if="record.is_author">
<li t-if="display['show_delete']"><a href="#" class="oe_mail_msg_delete" t-attf-data-id='{record.id}'>Delete</a></li>
</t>
<li t-if="display['show_hide']"><a href="#" class="oe_mail_msg_hide" t-attf-data-id='{record.id}'>Remove notification</a></li>
<li t-if="record.is_author &amp; options.show_dd_delete"><a class="oe_mail_msg_delete" t-attf-data-id='{record.id}'>Delete</a></li>
<li t-if="options.show_dd_hide"><a class="oe_mail_msg_hide" t-attf-data-id='{record.id}'>Remove notification</a></li>
<!-- Uncomment when adding subtype hiding
<li t-if="display['show_hide']">
<a href="#" class="oe_mail_msg_hide_type" t-attf-data-subtype='{record.subtype}'>Hide '<t t-esc="record.subtype"/>' for this document</a>
</li> -->
<li><a href="#" t-attf-data-msg_id="{record.id}" class="oe_mail_msg_reply_by_email">Quote and reply</a></li>
<li t-if="record.type == 'email'"><a t-attf-href="#model=mail.message&amp;id=#{record.id}" class="oe_mail_msg_details">Details</a></li>
<li t-if="options.show_dd_reply_by_email"><a class="oe_mail_msg_reply_by_email" t-attf-data-msg_id="{record.id}">Quote and reply</a></li>
<li t-if="record.type == 'email'"><a class="oe_mail_msg_details" t-attf-href="#model=mail.message&amp;id=#{record.id}" >Details</a></li>
</ul>
</span>
<!-- message itself -->
@ -118,38 +130,54 @@
<t t-raw="record.subject"/>
</h1>
<div class="oe_mail_msg_body">
<div class="oe_mail_msg_record_body">
<a t-attf-href="#model=#{record.model}&amp;id=#{record.res_id}" t-if="(params.thread_level > 0) &amp; (!record.subject)"><t t-raw="record.record_name"/></a>
<t t-raw="record.body"/>
</div>
<t t-if="options.show_record_name &amp; (!record.subject) &amp; (options.thread_level > 0)">
<a t-attf-href="#model=#{record.model}&amp;id=#{record.res_id}"><t t-raw="record.record_name"/></a>
</t>
<t t-raw="record.body"/>
</div>
<div class="oe_clear"/>
<ul class="oe_mail_msg_footer">
<li t-if="record.subject &amp; params.thread_level > 0"><a t-attf-href="#model=#{record.model}&amp;id=#{record.res_id}"><t t-raw="record.record_name"/></a></li>
<li><a t-attf-href="#model=res.partner&amp;id=#{record.author_id[0]}"><t t-raw="record.author_id[1]"/></a></li>
<li><span t-att-title="record.date"><t t-raw="record.timerelative"/></span></li>
<li t-if="display['show_reply']"><a href="#" class="oe_mail_msg_reply">Reply</a></li>
<!-- uncomment when merging vote
<li><a href="#">Like</a></li>
-->
<li t-if="record.attachment_ids.length > 0">
<a href="#" class="oe_mail_msg_view_attachments">
<t t-if="record.attachment_ids.length == 1">1 Attachment</t>
<t t-if="record.attachment_ids.length > 1"><t t-raw="record.attachment_ids.length"/> Attachments</t>
</a>
</li>
<li t-if="options.show_record_name &amp; record.subject &amp; options.thread_level > 0">
<a t-attf-href="#model=#{record.model}&amp;id=#{record.res_id}"><t t-raw="record.record_name"/></a>
</li>
<li><a t-attf-href="#model=res.partner&amp;id=#{record.author_id[0]}"><t t-raw="record.author_id[1]"/></a></li>
<li><span t-att-title="record.date"><t t-raw="record.timerelative"/></span></li>
<li t-if="options.show_reply"><a class="oe_mail_msg_reply">Reply</a></li>
<li t-if="options.show_reply_by_email"><a class="oe_mail_msg_reply_by_email" t-attf-data-msg_id="{record.id}">Reply</a></li>
<!-- uncomment when merging vote
<li><a href="#">Like</a></li>
-->
<li t-if="record.attachment_ids.length > 0">
<a class="oe_mail_msg_view_attachments">
<t t-if="record.attachment_ids.length == 1">1 Attachment</t>
<t t-if="record.attachment_ids.length > 1"><t t-raw="record.attachment_ids.length"/> Attachments</t>
</a>
</li>
</ul>
<t t-if="record.attachment_ids.length > 0">
<div class="oe_clear"></div>
<ul class="oe_mail_msg_attachments">
<t t-foreach="record.attachments" t-as="attachment">
<li> <a t-att-href="attachment.url"><t t-raw="attachment.name"/></a> </li>
</t>
</ul>
<t t-call="mail.thread.message.attachments"/>
</t>
</div>
</div>
</div>
</li>
<!--
mail.thread.message.attachments template
Template used to display attachments in a mail.message
-->
<t t-name="mail.thread.message.attachments">
<ul class="oe_mail_msg_attachments">
<t t-foreach="record.attachment_ids" t-as="attachment">
<li>
<a t-att-href="attachment.url"><t t-raw="attachment.name || attachment.filename"/></a>
<t t-if="record.show_attachment_delete">
<a class="oe_right oe_mail_attachment_delete" title="Delete this attachmentt" t-attf-data-id="{attachment.id}">x</a>
</t>
</li>
</t>
</ul>
</t>
</template>

View File

@ -431,12 +431,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:<blockquote><pre>Pigs rules</pre></blockquote></div>', 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')

View File

@ -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 = _('<div>On %(date)s, %(sender_name)s wrote:<blockquote>%(body)s</blockquote></div>') % {
@ -176,8 +178,8 @@ class mail_compose_message(osv.TransientModel):
return result
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. """
""" toggle content_subtype: 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)
@ -185,17 +187,16 @@ class mail_compose_message(osv.TransientModel):
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
on the subtype allows to have some specific behavior when switching
between text or html mode.
This method can be overridden for models that want to have their
specific behavior. """
""" This onchange allows to have some specific behavior when switching
between text or html mode. This method can be overridden.
:param values: 'plain' or 'html'
"""
return {'value': {'content_subtype': value}}
def onchange_partner_ids(self, cr, uid, ids, value, context=None):
""" onchange_partner_ids (value format: [[6, 0, [3, 4]]]). The
basic purpose of this method is to check that destination partners
""" The basic purpose of this method is to check that destination partners
effectively have email addresses. Otherwise a warning is thrown.
:param value: value format: [[6, 0, [3, 4]]]
"""
res = {'value': {}}
if not value or not value[0] or not value[0][0] == 6:
@ -203,12 +204,6 @@ class mail_compose_message(osv.TransientModel):
res.update(self.verify_partner_email(cr, uid, value[0][2], context=context))
return res
def unlink(self, cr, uid, ids, context=None):
# Cascade delete all attachments, as they are owned by the composition wizard
for wizard in self.read(cr, uid, ids, ['attachment_ids'], context=context):
self.pool.get('ir.attachment').unlink(cr, uid, wizard['attachment_ids'], context=context)
return super(mail_compose_message, self).unlink(cr, uid, ids, context=context)
def dummy(self, cr, uid, ids, context=None):
""" TDE: defined to have buttons that do basically nothing. It is
currently impossible to have buttons that do nothing special
@ -237,6 +232,7 @@ class mail_compose_message(osv.TransientModel):
post_values = {
'subject': wizard.subject if wizard.content_subtype == 'html' else False,
'body': wizard.body if wizard.content_subtype == 'html' else '<pre>%s</pre>' % 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],
}
@ -250,6 +246,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'}
@ -278,6 +276,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, {

View File

@ -50,8 +50,6 @@
<field name="parent_menu_id"
context="{'ir.ui.menu.full_list': True}"/>
<label for="partent_menu_id" colspan="1"/>
<button name="do_create_menu" type="object"
string="Create Parent Menu"/>
</group>
<separator string="Widgets Assigned to Users" colspan="2"/>
<field name="widget_ids" nolabel="1" colspan="2">

View File

@ -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',