[IMP] [CLEAN] [WIP] composer: cleaning of the server-side code. It now relies less on context keys and values; some fields have been added to manage the composition mode, model, res_id and message_id as a classic form. Updated form view, with invisible fields, to have those value accessible and modifiable through onchange or through JS. Updated JS-part of the composer to handle the new composer; less options, less logic client-side ! Still not finished, short in time today (have to check mass_mail, templates).

bzr revid: tde@openerp.com-20120822162151-n9o23ik0v45h7v6j
This commit is contained in:
Thibault Delavallée 2012-08-22 18:21:51 +02:00
parent 9c3e455799
commit 4e6e86fab5
4 changed files with 201 additions and 250 deletions

View File

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

View File

@ -102,15 +102,15 @@
<!-- dropdown menu with message options and actions -->
<span class="oe_dropdown_toggle oe_dropdown_arrow">
<ul class="oe_dropdown_menu">
<t t-if="display['show_delete']">
<li t-if="record.is_author"><a href="#" class="oe_mail_msg_delete" t-attf-data-id='{record.id}'>Delete</a></li>
<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}'>Hide</a></li>
<li t-if="display['show_hide']"><a href="#" 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}" t-attf-data-type="{record.type}" t-attf-data-formatting="{record.content_subtype}" class="oe_mail_msg_reply_by_email">Reply by email</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>
</ul>
</span>

View File

@ -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 = '<div>%s<blockquote>%s</blockquote></div>%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 = '<blockquote>%s</blockquote>' % (tools.ustr(body)),
reply_body = '<br /><br />%s%s<br />%s<br />%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

View File

@ -38,8 +38,13 @@
<field name="arch" type="xml">
<form string="Compose Email" version="7.0" >
<group>
<field name="composition_mode" colspan="2" nolabel="1" invisible="1"/>
<field name="model" colspan="2" nolabel="1" invisible="1"/>
<field name="res_id" colspan="2" nolabel="1" invisible="1"/>
<field name="parent_id" colspan="2" nolabel="1" invisible="1"/>
<!-- truly invisible fields for control and options -->
<field name="content_subtype" colspan="2" nolabel="1" invisible="1"/>
<!-- visible wizard -->
<field name="subject" colspan="2" nolabel="1" placeholder="Subject..."
class="oe_mail_compose_message_subject"
attrs="{'invisible':[('content_subtype', '=', 'plain')]}"/>
@ -49,9 +54,9 @@
<field name="body" colspan="2" nolabel="1" placeholder="What are you working on ?"
class="oe_mail_compose_message_body_html"
attrs="{'invisible':[('content_subtype', '=', 'plain')]}"/>
<field name="dest_partner_ids" colspan="2" nolabel="1" widget="many2many_tags" placeholder="Add contacts to notify..."
<field name="partner_ids" colspan="2" nolabel="1" widget="many2many_tags" placeholder="Add contacts to notify..."
context="{'force_create':True}"
on_change="onchange_dest_partner_ids(dest_partner_ids)"
on_change="onchange_partner_ids(partner_ids)"
class="oe_mail_compose_message_partner_ids"/>
<field name="attachment_ids" colspan="2" nolabel="1" widget="many2many_tags"
placeholder="Add attachments..." invisible="1"
@ -69,7 +74,7 @@
help="Add an attachment"/>
<button icon="/mail/static/src/img/formatting.png"
class="oe_mail_compose_message_formatting" string=""
type="object" name="toggle_formatting"
type="object" name="toggle_content_subtype"
help="Toggle advanced formatting mode"/>
</div>
</group>