# create a mail_mail based on values, without attachments
values = self.generate_email(cr, uid, template_id, res_id, context=context)
assert values.get('email_from'), 'email_from is missing or empty after template rendering, send_mail() cannot proceed'
- del values['partner_to'] # TODO Properly use them.
+
- # process email_recipients field that is a comma separated list of partner_ids -> recipient_ids
++ # process partner_to field that is a comma separated list of partner_ids -> recipient_ids
+ # NOTE: only usable if force_send is True, because otherwise the value is
+ # not stored on the mail_mail, and therefore lost -> fixed in v8
- recipient_ids = []
- email_recipients = values.pop('email_recipients', '')
- if email_recipients:
- for partner_id in email_recipients.split(','):
- if partner_id: # placeholders could generate '', 3, 2 due to some empty field values
- recipient_ids.append(int(partner_id))
++ values['recipient_ids'] = []
++ partner_to = values.pop('partner_to', '')
++ if partner_to:
++ # placeholders could generate '', 3, 2 due to some empty field values
++ tpl_partner_ids = [pid for pid in partner_to.split(',') if pid]
++ values['recipient_ids'] += [(4, pid) for pid in self.pool['res.partner'].exists(cr, SUPERUSER_ID, tpl_partner_ids, context=context)]
+
attachment_ids = values.pop('attachment_ids', [])
attachments = values.pop('attachments', [])
msg_id = mail_mail.create(cr, uid, values, context=context)
--- /dev/null
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Business Applications
+# Copyright (c) 2013-TODAY OpenERP S.A. <http://www.openerp.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from openerp.addons.base.tests.test_ir_actions import TestServerActionsBase
+
+
+class TestServerActionsEmail(TestServerActionsBase):
+
+ def test_00_state_email(self):
+ """ Test ir.actions.server email type """
+ cr, uid = self.cr, self.uid
+
+ # create email_template
+ template_id = self.registry('email.template').create(cr, uid, {
+ 'name': 'TestTemplate',
+ 'email_from': 'myself@example.com',
+ 'email_to': 'brigitte@example.com',
- 'partner_to': '[%s]' % self.test_partner_id,
++ 'partner_to': '%s' % self.test_partner_id,
+ 'model_id': self.res_partner_model_id,
+ 'subject': 'About ${object.name}',
+ 'body_html': '<p>Dear ${object.name}, your parent is ${object.parent_id and object.parent_id.name or "False"}</p>',
+ })
+
+ self.ir_actions_server.write(cr, uid, self.act_id, {
+ 'state': 'email',
+ 'template_id': template_id,
+ })
+ run_res = self.ir_actions_server.run(cr, uid, [self.act_id], context=self.context)
+ self.assertFalse(run_res, 'ir_actions_server: email server action correctly finished should return False')
+
+ # check an email is waiting for sending
+ mail_ids = self.registry('mail.mail').search(cr, uid, [('subject', '=', 'About TestingPartner')])
+ self.assertEqual(len(mail_ids), 1, 'ir_actions_server: TODO')
+ # check email content
+ mail = self.registry('mail.mail').browse(cr, uid, mail_ids[0])
+ self.assertEqual(mail.body, '<p>Dear TestingPartner, your parent is False</p>',
+ 'ir_actions_server: TODO')
##############################################################################
import base64
-from openerp.addons.mail.tests.test_mail_base import TestMailBase
+from openerp.addons.mail.tests.common import TestMail
+ from openerp.tools import mute_logger
-class test_message_compose(TestMailBase):
+class test_message_compose(TestMail):
def setUp(self):
super(test_message_compose, self).setUp()
# Generate messsage with default email and partner on template
mail_value = mail_compose.generate_email_for_composer(cr, uid, email_template_id, uid)
self.assertEqual(set(mail_value['partner_ids']), set(send_to), 'mail.message partner_ids list created by template is incorrect')
+
+ @mute_logger('openerp.osv.orm', 'openerp.osv.orm')
+ def test_10_email_templating(self):
+ """ Tests designed for the mail.compose.message wizard updated by email_template. """
+ cr, uid, context = self.cr, self.uid, {}
+
+ # create the email.template on mail.group model
+ group_model_id = self.registry('ir.model').search(cr, uid, [('model', '=', 'mail.group')])[0]
+ email_template = self.registry('email.template')
+ email_template_id = email_template.create(cr, uid, {
+ 'model_id': group_model_id,
+ 'name': 'Pigs Template',
+ 'email_from': 'Raoul Grosbedon <raoul@example.com>',
+ 'subject': '${object.name}',
+ 'body_html': '${object.description}',
+ 'user_signature': True,
+ 'email_to': 'b@b.b c@c.c',
+ 'email_cc': 'd@d.d',
- 'email_recipients': '${user.partner_id.id},%s,%s,-1' % (self.user_raoul.partner_id.id, self.user_bert.partner_id.id)
++ 'partner_to': '${user.partner_id.id},%s,%s,-1' % (self.user_raoul.partner_id.id, self.user_bert.partner_id.id)
+ })
+
+ # not force send: email_recipients is not taken into account
+ msg_id = email_template.send_mail(cr, uid, email_template_id, self.group_pigs_id, context=context)
+ mail = self.mail_mail.browse(cr, uid, msg_id, context=context)
+ self.assertEqual(mail.subject, 'Pigs', 'email_template: send_mail: wrong subject')
+ self.assertEqual(mail.email_to, 'b@b.b c@c.c', 'email_template: send_mail: wrong email_to')
+ self.assertEqual(mail.email_cc, 'd@d.d', 'email_template: send_mail: wrong email_cc')
++ self.assertEqual(
++ set([partner.id for partner in mail.recipient_ids]),
++ set((self.partner_admin_id, self.user_raoul.partner_id.id, self.user_bert.partner_id.id)),
++ 'email_template: send_mail: wrong management of partner_to')
+
+ # force send: take email_recipients into account
+ email_template.send_mail(cr, uid, email_template_id, self.group_pigs_id, force_send=True, context=context)
+ sent_emails = self._build_email_kwargs_list
- email_to_lst = ['"Followers of Pigs" <admin@example.com>', '"Followers of Pigs" <raoul@raoul.fr>', '"Followers of Pigs" <bert@bert.fr>']
- self.assertEqual(len(sent_emails), 3, 'email_template: send_mail: 3 valid email recipients -> should send 3 emails')
++ email_to_lst = [
++ ['b@b.b', 'c@c.c'], ['"Followers of Pigs" <admin@example.com>'],
++ ['"Followers of Pigs" <raoul@raoul.fr>'], ['"Followers of Pigs" <bert@bert.fr>']]
++ self.assertEqual(len(sent_emails), 4, 'email_template: send_mail: 3 valid email recipients + email_to -> should send 4 emails')
+ for email in sent_emails:
- self.assertEqual(len(email['email_to']), 1, 'email_template: send_mail: email_recipient should send email to one recipient at a time')
- self.assertIn(email['email_to'][0], email_to_lst, 'email_template: send_mail: wrong email_recipients')
++ self.assertIn(email['email_to'], email_to_lst, 'email_template: send_mail: wrong email_recipients')
#
##############################################################################
--from openerp import tools
++from openerp import tools, SUPERUSER_ID
from openerp.osv import osv, fields
+
def _reopen(self, res_id, model):
return {'type': 'ir.actions.act_window',
'view_mode': 'form',
# Wizard validation and send
#------------------------------------------------------
- def generate_email_for_composer(self, cr, uid, template_id, res_id, context=None):
+ def _get_or_create_partners_from_values(self, cr, uid, rendered_values, context=None):
+ """ Check for email_to, email_cc, partner_to """
+ partner_ids = []
+ mails = tools.email_split(rendered_values.pop('email_to', '') + ' ' + rendered_values.pop('email_cc', ''))
+ for mail in mails:
+ partner_id = self.pool.get('res.partner').find_or_create(cr, uid, mail, context=context)
+ partner_ids.append(partner_id)
+ partner_to = rendered_values.pop('partner_to', '')
+ if partner_to:
- for partner_id in partner_to.split(','):
- if partner_id: # placeholders could generate '', 3, 2 due to some empty field values
- partner_ids.append(int(partner_id))
++ # placeholders could generate '', 3, 2 due to some empty field values
++ tpl_partner_ids = [pid for pid in partner_to.split(',') if pid]
++ partner_ids += self.pool['res.partner'].exists(cr, SUPERUSER_ID, tpl_partner_ids, context=context)
+ return partner_ids
+
+ def generate_email_for_composer_batch(self, cr, uid, template_id, res_ids, context=None):
""" Call email_template.generate_email(), get fields relevant for
mail.compose.message, transform email_cc and email_to into partner_ids """
- template_values = self.pool.get('email.template').generate_email(cr, uid, template_id, res_id, context=context)
# filter template values
- fields = ['body_html', 'subject', 'email_to', 'email_recipients', 'email_cc', 'attachment_ids', 'attachments']
- values = dict((field, template_values[field]) for field in fields if template_values.get(field))
- values['body'] = values.pop('body_html', '')
-
- # transform email_to, email_cc into partner_ids
- partner_ids = set()
- mails = tools.email_split(values.pop('email_to', '') + ' ' + values.pop('email_cc', ''))
- ctx = dict((k, v) for k, v in (context or {}).items() if not k.startswith('default_'))
- for mail in mails:
- partner_id = self.pool.get('res.partner').find_or_create(cr, uid, mail, context=ctx)
- partner_ids.add(partner_id)
- email_recipients = values.pop('email_recipients', '')
- if email_recipients:
- for partner_id in email_recipients.split(','):
- if partner_id: # placeholders could generate '', 3, 2 due to some empty field values
- partner_ids.add(int(partner_id))
- # legacy template behavior: void values do not erase existing values and the
- # related key is removed from the values dict
- if partner_ids:
- values['partner_ids'] = list(partner_ids)
-
+ fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids', 'attachments', 'mail_server_id']
+ values = dict.fromkeys(res_ids, False)
+
+ template_values = self.pool.get('email.template').generate_email_batch(cr, uid, template_id, res_ids, context=context)
+ for res_id in res_ids:
+ res_id_values = dict((field, template_values[res_id][field]) for field in fields if template_values[res_id].get(field))
+ res_id_values['body'] = res_id_values.pop('body_html', '')
+
+ # transform email_to, email_cc into partner_ids
+ ctx = dict((k, v) for k, v in (context or {}).items() if not k.startswith('default_'))
+ partner_ids = self._get_or_create_partners_from_values(cr, uid, res_id_values, context=ctx)
+ # legacy template behavior: void values do not erase existing values and the
+ # related key is removed from the values dict
+ if partner_ids:
+ res_id_values['partner_ids'] = list(partner_ids)
+
+ values[res_id] = res_id_values
return values
- def render_message(self, cr, uid, wizard, res_id, context=None):
+ def render_message_batch(self, cr, uid, wizard, res_ids, context=None):
""" Override to handle templates. """
- # generate the composer email
+ # generate template-based values
if wizard.template_id:
- values = self.generate_email_for_composer(cr, uid, wizard.template_id, res_id, context=context)
+ template_values = self.generate_email_for_composer_batch(cr, uid, wizard.template_id.id, res_ids, context=context)
else:
- values = {}
- # remove attachments as they should not be rendered
- values.pop('attachment_ids', None)
- # get values to return
- email_dict = super(mail_compose_message, self).render_message(cr, uid, wizard, res_id, context)
- values.update(email_dict)
- return values
-
- def render_template(self, cr, uid, template, model, res_id, context=None):
- return self.pool.get('email.template').render_template(cr, uid, template, model, res_id, context=context)
+ template_values = dict.fromkeys(res_ids, dict())
+ # generate composer values
+ composer_values = super(mail_compose_message, self).render_message_batch(cr, uid, wizard, res_ids, context)
+
+ for res_id in res_ids:
+ # remove attachments from template values as they should not be rendered
+ template_values[res_id].pop('attachment_ids', None)
+ # remove some keys from composer that are readonly
+ composer_values[res_id].pop('email_to', None)
+ composer_values[res_id].pop('email_cc', None)
+ composer_values[res_id].pop('partner_to', None)
+ # update template values by composer values
+ template_values[res_id].update(composer_values[res_id])
+ return template_values
+
+ def render_template_batch(self, cr, uid, template, model, res_ids, context=None):
+ return self.pool.get('email.template').render_template_batch(cr, uid, template, model, res_ids, context=context)
+
+ # Compatibility methods
+ def generate_email_for_composer(self, cr, uid, template_id, res_id, context=None):
+ return self.generate_email_for_composer_batch(cr, uid, template_id, [res_id], context)[res_id]
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@param contract_ids: list of contracts
@return: the structures linked to the given contracts, ordered by hierachy (parent=False first, then first level children and so on) and without duplicata
"""
- all_structures = []
- structure_ids = [contract.struct_id.id for contract in self.browse(cr, uid, contract_ids, context=context)]
+ structure_ids = [contract.struct_id.id for contract in self.browse(cr, uid, contract_ids, context=context) if contract.struct_id]
+ if not structure_ids:
+ return []
return list(set(self.pool.get('hr.payroll.structure')._get_parent_structure(cr, uid, structure_ids, context=context)))
-hr_contract()
class contrib_register(osv.osv):
'''
<date>%s</date>
<company>%s</company>
</header>
- ''' % (str(rml_obj.formatLang(time.strftime("%Y-%m-%d"),date=True))+' ' + str(time.strftime("%H:%M")),registry['res.users'].browse(cr,uid,uid).company_id.name)
- ''' % (str(rml_obj.formatLang(time.strftime("%Y-%m-%d"),date=True))+' ' + str(time.strftime("%H:%M")),toxml(pooler.get_pool(cr.dbname).get('res.users').browse(cr,uid,uid).company_id.name))
++ ''' % (str(rml_obj.formatLang(time.strftime("%Y-%m-%d"),date=True))+' ' + str(time.strftime("%H:%M")),toxml(registry['res.users'].browse(cr,uid,uid).company_id.name))
xml='''<?xml version="1.0" encoding="UTF-8" ?>
<report>
'project.task': (lambda self, cr, uid, ids, c={}: ids, ['work_ids', 'remaining_hours', 'planned_hours'], 10),
'project.task.work': (_get_task, ['hours'], 10),
}),
- 'progress': fields.function(_hours_get, string='Progress (%)', multi='hours', group_operator="avg", help="If the task has a progress of 99.99% you should close the task if it's finished or reevaluate the time",
+ 'progress': fields.function(_hours_get, string='Working Time Progress (%)', multi='hours', group_operator="avg", help="If the task has a progress of 99.99% you should close the task if it's finished or reevaluate the time",
store = {
- 'project.task': (lambda self, cr, uid, ids, c={}: ids, ['work_ids', 'remaining_hours', 'planned_hours','state'], 10),
+ 'project.task': (lambda self, cr, uid, ids, c={}: ids, ['work_ids', 'remaining_hours', 'planned_hours', 'state', 'stage_id'], 10),
'project.task.work': (_get_task, ['hours'], 10),
}),
'delay_hours': fields.function(_hours_get, string='Delay Hours', multi='hours', help="Computed as difference between planned hours by the project manager and the total hours of the task.",