diff --git a/addons/account_accountant/account_accountant_data.xml b/addons/account_accountant/account_accountant_data.xml index 7a666c8d344..5b563017c62 100644 --- a/addons/account_accountant/account_accountant_data.xml +++ b/addons/account_accountant/account_accountant_data.xml @@ -21,13 +21,14 @@ - - - - With OpenERP's accounting, you can get an instant access to all your financial data, setup your analytic accounting, forecast your taxes, control your budgets, easily create and send invoices, record bank statements, etc. + + mail.group + + notification + Accounting and Finance application installed! + With OpenERP's accounting, you get instant access to your financial data, and can setup analytic accounting, forecast taxes, control budgets, easily create and send invoices, record bank statements, etc. -The accounting features are fully integrated with others OpenERP applications to automate all your processes: creation of customer invoices, control of supplier invoices, point-of-sale integration, automated follow-ups, etc. - Module Accounting and Finance has been installed. - +The accounting features are fully integrated with other OpenERP applications to automate all your processes: creation of customer invoices, control of supplier invoices, point-of-sale integration, automated follow-ups, etc. + diff --git a/addons/account_voucher/account_voucher_data.xml b/addons/account_voucher/account_voucher_data.xml index b497f4ee20e..656d2c2cd91 100644 --- a/addons/account_voucher/account_voucher_data.xml +++ b/addons/account_voucher/account_voucher_data.xml @@ -2,15 +2,16 @@ - - - - OpenERP's electronic invoicing allows to ease and fasten the creation of invoices and collection of customer payments. Invoices are created in a few clicks and your customers receive them by email. They can pay online and/or import them in their own system. + + mail.group + + notification + eInvoicing & Payments application installed! + OpenERP's electronic invoicing accelerates the creation of invoices and collection of customer payments. Invoices are created in a few clicks and your customers receive them by email. They can pay online and/or import them in their own system. -You can track customer payments easily and automate the reminders. You get an overview of the discussion with your customers on each invoice to ensure a full traceability. +You can track customer payments easily and automate follow-ups. You get an overview of the discussion with your customers on each invoice for easier traceability. -If you want to use advanced accounting features, you should install the "Accounting and Finance" module. - Module eInvoicing & Payments has been installed. - +For advanced accounting features, you should install the "Accounting and Finance" module. + diff --git a/addons/crm/crm_data.xml b/addons/crm/crm_data.xml index 52d779f4584..5bf4b5ffbf9 100644 --- a/addons/crm/crm_data.xml +++ b/addons/crm/crm_data.xml @@ -55,21 +55,20 @@ - - - - From the top menu Sales, you can: trace leads and opportunities, get accurate forecast on your sales pipeline, plan meetings and phonecalls, get realtime statistics and efficiently organize the communication with your prospects. + + mail.group + + notification + CRM application installed! + From the top Sales menu you can track leads and opportunities, get accurate forecast on your sales pipeline, plan meetings and phonecalls, get realtime statistics and efficiently organize the communication with your prospects. +To manage quotations and sale orders, install the "Sales Management" application. + -To manage quotations and sale orders, install the module "Sales Management". - Module CRM has been installed - - sales {'type':'lead'} - diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py index 9adab6d3976..d540971c3b4 100644 --- a/addons/email_template/email_template.py +++ b/addons/email_template/email_template.py @@ -28,6 +28,8 @@ from osv import osv from osv import fields import tools from tools.translate import _ +from tools.html_sanitize import html_sanitize +from tools import html2plaintext from urllib import quote as quote _logger = logging.getLogger(__name__) @@ -134,7 +136,7 @@ class email_template(osv.osv): "This is useful for CRM leads for example"), # Overridden mail.message.common fields for technical reasons: - 'model': fields.related('model_id','model', type='char', string='Related Document Model', + 'model': fields.related('model_id', 'model', type='char', string='Related Document Model', size=128, select=True, store=True, readonly=True), # we need a separate m2m table to avoid ID collisions with the original mail.message entries #'attachment_ids': fields.many2many('ir.attachment', 'email_template_attachment_rel', 'email_template_id', @@ -143,7 +145,7 @@ class email_template(osv.osv): # "emails created from this template"), # Overridden mail.message.common fields to make tooltips more appropriate: - #'subject':fields.char('Subject', size=512, translate=True, help="Subject (placeholders may be used here)",), + 'subject': fields.char('Subject', translate=True, help="Subject (placeholders may be used here)",), 'email_from': fields.char('From', size=128, help="Sender address (placeholders may be used here)"), 'email_to': fields.char('To', size=256, help="Comma-separated recipient addresses (placeholders may be used here)"), 'email_cc': fields.char('Cc', size=256, help="Carbon copy recipients (placeholders may be used here)"), @@ -151,13 +153,7 @@ class email_template(osv.osv): 'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing Mail Server', readonly=False, help="Optional preferred server for outgoing mails. If not set, the highest " "priority one will be used."), - #'body': fields.text('Text Contents', translate=True, help="Plaintext version of the message (placeholders may be used here)"), 'body_html': fields.text('Rich-text Contents', translate=True, help="Rich-text/HTML version of the message (placeholders may be used here)"), - #'message_id': fields.char('Message-Id', size=256, help="Message-ID SMTP header to use in outgoing messages based on this template. " - # "Please note that this overrides the 'Resource Tracking' option, " - # "so if you simply need to track replies to outgoing emails, enable " - # "that option instead.\n" - # "Placeholders must be used here, as this value always needs to be unique!"), # Fake fields used to implement the placeholder assistant 'model_object_field': fields.many2one('ir.model.fields', string="Field", @@ -300,7 +296,6 @@ class email_template(osv.osv): context = {} values = { 'subject': False, - 'body': False, 'body_html': False, 'email_from': False, 'email_to': False, @@ -314,7 +309,6 @@ class email_template(osv.osv): 'attachment_ids': False, 'message_id': False, 'state': 'outgoing', - 'content_subtype': 'plain', } if not template_id: return values @@ -322,15 +316,15 @@ class email_template(osv.osv): report_xml_pool = self.pool.get('ir.actions.report.xml') template = self.get_email_template(cr, uid, template_id, res_id, context) - for field in ['subject', 'body', 'body_html', 'email_from', - 'email_to', 'email_cc', 'reply_to', - 'message_id']: + for field in ['subject', 'body_html', 'email_from', + 'email_to', 'email_cc', 'reply_to']: values[field] = self.render_template(cr, uid, getattr(template, field), template.model, res_id, context=context) \ or False if values['body_html']: - values.update(content_subtype='html') + values['body'] = html_sanitize(values['body_html']) + values['body_text'] = html2plaintext(values['body_html']) if template.user_signature: signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature diff --git a/addons/email_template/email_template_view.xml b/addons/email_template/email_template_view.xml index a03bd3351ed..cd75a719197 100644 --- a/addons/email_template/email_template_view.xml +++ b/addons/email_template/email_template_view.xml @@ -22,10 +22,7 @@ - - - - + diff --git a/addons/email_template/tests/__init__.py b/addons/email_template/tests/__init__.py new file mode 100644 index 00000000000..d63c5634cc3 --- /dev/null +++ b/addons/email_template/tests/__init__.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Business Applications +# Copyright (c) 2012-TODAY OpenERP S.A. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +from . import test_mail + +checks = [ + test_mail, +] + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file diff --git a/addons/email_template/tests/test_mail.py b/addons/email_template/tests/test_mail.py new file mode 100644 index 00000000000..db2168a75d5 --- /dev/null +++ b/addons/email_template/tests/test_mail.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Business Applications +# Copyright (c) 2012-TODAY OpenERP S.A. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from openerp.tests import common +import tools + +class test_message_compose(common.TransactionCase): + + def _mock_smtp_gateway(self, *args, **kwargs): + return True + + def _mock_build_email(self, *args, **kwargs): + self._build_email_args = args + self._build_email_kwargs = kwargs + return self.build_email_real(*args, **kwargs) + + def setUp(self): + super(test_message_compose, self).setUp() + self.mail_group = self.registry('mail.group') + self.mail_mail = self.registry('mail.mail') + self.mail_message = self.registry('mail.message') + self.res_users = self.registry('res.users') + self.res_partner = self.registry('res.partner') + + # Install mock SMTP gateway + self.build_email_real = self.registry('ir.mail_server').build_email + self.registry('ir.mail_server').build_email = self._mock_build_email + self.registry('ir.mail_server').send_email = self._mock_smtp_gateway + + # create a 'pigs' group that will be used through the various tests + self.group_pigs_id = self.mail_group.create(self.cr, self.uid, + {'name': 'Pigs', 'description': 'Fans of Pigs, unite !'}) + + def test_00_message_compose_wizard(self): + """ Tests designed for the mail.compose.message wizard updated by email_template. """ + cr, uid = self.cr, self.uid + mail_compose = self.registry('mail.compose.message') + self.res_users.write(cr, uid, [uid], {'signature': 'Admin', 'email': 'a@a'}) + user_admin = self.res_users.browse(cr, uid, uid) + group_model_id = self.registry('ir.model').search(cr, uid, [('model', '=', 'mail.group')])[0] + group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id) + + # Create template on mail.group + email_template = self.registry('email.template') + email_template_id = email_template.create(cr, uid, {'model_id': group_model_id, + 'name': 'Pigs Template', 'subject': '${record.name}', + 'body_html': '${object.description}', 'user_signature': True, + 'email_to': 'b@b c@c', 'email_cc': 'd@d'}) + # import pdb + # pdb.set_trace() + + # Mail data + _subject = 'Pigs' + _body_text = 'Pigs rules' + + # 3 - Create in mass_mail composition mode that should work with or without email_template installed + compose_id = mail_compose.create(cr, uid, + {'subject': _subject, 'body': _body_text}, + {'default_composition_mode': 'mass_mail', 'default_model': 'mail.group', + 'default_res_id': -1, 'default_use_template': True, + 'active_ids': [self.group_pigs_id], 'default_template_id': email_template_id}) + compose = mail_compose.browse(cr, uid, compose_id) + # print compose.subject + + + # Try the 'onchange_template_id' + values = mail_compose.onchange_template_id(cr, uid, [], compose.use_template, compose.template_id, compose.composition_mode, compose.res_id) + # print values + values = mail_compose.onchange_use_template(cr, uid, [], not compose.use_template, compose.template_id, compose.composition_mode, compose.res_id) + # print values + + compose.refresh() + + + # # Post the comment, get created message + # mail_compose.send_mail(cr, uid, [compose_id], {'default_res_id': -1, 'active_ids': [self.group_pigs_id]}) + # group_pigs.refresh() + # msg = group_pigs.message_ids[0] + + # # Test: last message on Pigs = last created message + # test_msg = self.mail_message.browse(cr, uid, self.mail_message.search(cr, uid, [], limit=1))[0] + # self.assertEqual(msg.id, test_msg.id, 'Pigs did not receive its mass mailing message') + # # Test: mail.message: subject, body + # self.assertEqual(msg.subject, _subject, 'mail.message subject is incorrect') + # self.assertEqual(msg.body, group_pigs.description, 'mail.message body is incorrect') \ No newline at end of file diff --git a/addons/email_template/wizard/mail_compose_message.py b/addons/email_template/wizard/mail_compose_message.py index a05aa232453..11e726c537b 100644 --- a/addons/email_template/wizard/mail_compose_message.py +++ b/addons/email_template/wizard/mail_compose_message.py @@ -20,62 +20,62 @@ ############################################################################## import base64 +import tools from osv import osv from osv import fields from tools.translate import _ -import tools - class mail_compose_message(osv.osv_memory): + """ Inherit mail_compose_message to add email template feature in the + message composer. """ + _inherit = 'mail.compose.message' def _get_templates(self, cr, uid, context=None): - """ - Return Email Template of particular Model. - """ + """ Return Email Template of particular Model. """ if context is None: context = {} - record_ids = [] - email_template= self.pool.get('email.template') model = False - if context.get('message_id'): - mail_message = self.pool.get('mail.message') - message_data = mail_message.browse(cr, uid, int(context.get('message_id')), context) - model = message_data.model - elif context.get('mail.compose.target.model') or context.get('active_model'): - model = context.get('mail.compose.target.model', context.get('active_model')) + email_template_obj= self.pool.get('email.template') + + if context.get('default_composition_mode') == 'reply' and context.get('active_id'): + message_data = self.pool.get('mail.message').browse(cr, uid, int(context.get('active_id')), context) + if message_data: + model = message_data.model + else: + model = context.get('default_model', context.get('active_model')) + if model: - record_ids = email_template.search(cr, uid, [('model', '=', model)]) - return email_template.name_get(cr, uid, record_ids, context) + [(False,'')] + record_ids = email_template_obj.search(cr, uid, [('model', '=', model)], context=context) + return email_template_obj.name_get(cr, uid, record_ids, context) + [(False, '')] return [] + def default_get(self, cr, uid, fields, context=None): + """ Override to handle templates. """ + if context is None: + context = {} + result = super(mail_compose_message, self).default_get(cr, uid, fields, context=context) + result['template_id'] = context.get('default_template_id', context.get('mail.compose.template_id', False)) + return result + _columns = { 'use_template': fields.boolean('Use Template'), - 'template_id': fields.selection(_get_templates, 'Template', - size=-1 # means we want an int db column - ), - } - - _defaults = { - 'template_id' : lambda self, cr, uid, context={} : context.get('mail.compose.template_id', False) + # incredible hack of the day: size=-1 means we want an int db column instead of an str one + 'template_id': fields.selection(_get_templates, 'Template', size=-1), } - def on_change_template(self, cr, uid, ids, use_template, template_id, email_from=None, email_to=None, context=None): - if context is None: - context = {} - values = {} - if template_id: - res_id = context.get('active_id', False) - if context.get('mail.compose.message.mode') == 'mass_mail': - # use the original template values - to be rendered when actually sent - # by super.send_mail() - values = self.pool.get('email.template').read(cr, uid, template_id, self.fields_get_keys(cr, uid), context) + def onchange_template_id(self, cr, uid, ids, use_template, template_id, composition_mode, res_id, context=None): + """ onchange_template_id: read or render the template if set, get back + to default values if not. """ + fields = ['body', 'body_html', 'subject', 'partner_ids', 'email_to', 'email_cc'] + if use_template and template_id: + # use the original template values, to be rendered when actually sent + if composition_mode == 'mass_mail': + values = self.pool.get('email.template').read(cr, uid, template_id, fields, context) + # render the mail as one-shot else: - # render the mail as one-shot values = self.pool.get('email.template').generate_email(cr, uid, template_id, res_id, context=context) - # get partner_ids back - values['dest_partner_ids'] = values['partner_ids'] # retrofit generated attachments in the expected field format if values['attachments']: attachment = values.pop('attachments') @@ -92,65 +92,61 @@ class mail_compose_message(osv.osv_memory): } att_ids.append(attachment_obj.create(cr, uid, data_attach)) values['attachment_ids'] = att_ids - else: - # restore defaults - values = self.default_get(cr, uid, self.fields_get_keys(cr, uid), context) - values.update(use_template=use_template, template_id=template_id) + else: # restore defaults + values = self.default_get(cr, uid, fields, context=context) + + if values.get('body_html') and not values.get('body'): + values['body'] = values.get('body_html') + + values.update(use_template=use_template, template_id=template_id) + + print 'returning ', values return {'value': values} - def template_toggle(self, cr, uid, ids, context=None): + def toggle_template(self, cr, uid, ids, context=None): + """ hit toggle template mode button: calls onchange_use_template to + emulate an on_change, then writes the value to update the form. """ for record in self.browse(cr, uid, ids, context=context): - values = {} - use_template = record.use_template - # simulate an on_change on use_template - values.update(self.onchange_use_template(cr, uid, ids, not use_template, context=context)['value']) - record.write(values) - return False + onchange_res = self.onchange_use_template(cr, uid, ids, not record.use_template, + record.template_id, record.composition_mode, record.res_id, context=context)['value'] + record.write(onchange_res) + return True - def onchange_use_template(self, cr, uid, ids, use_template, context=None): - values = {'use_template': use_template} - for record in self.browse(cr, uid, ids, context=context): - if not use_template: - # equivalent to choosing an empty template - onchange_template_values = self.on_change_template(cr, uid, record.id, use_template, - False, email_from=record.email_from, email_to=record.email_to, context=context) - values.update(onchange_template_values['value']) - return {'value': values} + def onchange_use_template(self, cr, uid, ids, use_template, template_id, composition_mode, res_id, context=None): + """ onchange_use_template (values: True or False). If use_template is + False, we do like an onchange with template_id False for values """ + onchange_template_values = self.onchange_template_id(cr, uid, ids, use_template, + template_id, composition_mode, res_id, context=context) + return onchange_template_values def save_as_template(self, cr, uid, ids, context=None): - if context is None: - context = {} + """ hit save as template button: current form value will be a new + template attached to the current document. """ email_template = self.pool.get('email.template') - model_pool = self.pool.get('ir.model') + ir_model_pool = self.pool.get('ir.model') for record in self.browse(cr, uid, ids, context=context): - model = record.model or context.get('active_model') - model_ids = model_pool.search(cr, uid, [('model', '=', model)]) + model = record.model + model_ids = ir_model_pool.search(cr, uid, [('model', '=', model)]) model_id = model_ids and model_ids[0] or False model_name = '' if model_id: - model_name = model_pool.browse(cr, uid, model_id, context=context).name + model_name = ir_model_pool.browse(cr, uid, model_id, context=context).name template_name = "%s: %s" % (model_name, tools.ustr(record.subject)) values = { 'name': template_name, - 'email_from': record.email_from or False, 'subject': record.subject or False, 'body': record.body or False, - 'email_to': record.email_to or False, - 'email_cc': record.email_cc or False, - 'reply_to': record.reply_to or False, 'model_id': model_id or False, 'attachment_ids': [(6, 0, [att.id for att in record.attachment_ids])] } template_id = email_template.create(cr, uid, values, context=context) - record.write({'template_id': template_id, - 'use_template': True}) + record.write({'template_id': template_id, 'use_template': True}) + return True - # _reopen same wizard screen with new template preselected - return False - - # override the basic implementation def render_template(self, cr, uid, template, model, res_id, context=None): + """ Override of mail.compose.message behavior: use the power of + templates ! """ return self.pool.get('email.template').render_template(cr, uid, template, model, res_id, context=context) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/email_template/wizard/mail_compose_message_view.xml b/addons/email_template/wizard/mail_compose_message_view.xml index dff6f4a3ad4..f54ee0d3723 100644 --- a/addons/email_template/wizard/mail_compose_message_view.xml +++ b/addons/email_template/wizard/mail_compose_message_view.xml @@ -10,15 +10,16 @@ -