[MERGE] Forward-port of latest 7.0 bugfixes, up to rev. 9718 rev-id: dle@openerp...
authorDenis Ledoux <dle@openerp.com>
Tue, 24 Dec 2013 11:24:07 +0000 (12:24 +0100)
committerDenis Ledoux <dle@openerp.com>
Tue, 24 Dec 2013 11:24:07 +0000 (12:24 +0100)
bzr revid: tde@openerp.com-20131220162158-hbgktly09dv37958
bzr revid: dle@openerp.com-20131223155041-8zp8e08fihbr0ie2
bzr revid: dle@openerp.com-20131224112407-aw6dq2q92312kjbk

1  2 
addons/account_check_writing/account_voucher.py
addons/account_payment/account_payment_view.xml
addons/account_voucher/account_voucher.py
addons/email_template/email_template.py
addons/email_template/tests/test_ir_actions.py
addons/email_template/tests/test_mail.py
addons/email_template/wizard/mail_compose_message.py
addons/hr_holidays/report/holidays_summary_report.py
addons/hr_payroll/hr_payroll.py
addons/hr_timesheet/report/users_timesheet.py
addons/project/project.py

@@@ -411,7 -377,17 +411,17 @@@ class email_template(osv.osv)
          # 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)
index d323ee3,0000000..9b408a4
mode 100644,000000..100644
--- /dev/null
@@@ -1,55 -1,0 +1,55 @@@
 +# -*- 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',
@@@ -134,69 -135,49 +134,69 @@@ class mail_compose_message(osv.Transien
      # 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:
@@@ -143,10 -145,12 +143,11 @@@ class hr_contract(osv.osv)
          @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):
      '''
@@@ -106,7 -104,7 +106,7 @@@ class report_custom(report_rml)
          <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>
@@@ -786,9 -806,9 +786,9 @@@ class task(osv.osv)
                  '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.",