[MERGE] Sync with trunk
authorThibault Delavallée <tde@openerp.com>
Wed, 22 May 2013 11:42:36 +0000 (13:42 +0200)
committerThibault Delavallée <tde@openerp.com>
Wed, 22 May 2013 11:42:36 +0000 (13:42 +0200)
bzr revid: tde@openerp.com-20130522103305-yi5ql1chdx9qxxlx
bzr revid: tde@openerp.com-20130522114236-qi2s3i2dl2lemdvh

1  2 
addons/account/account_invoice.py
addons/crm/crm_lead.py
addons/mail/mail_mail.py
addons/mail/mail_thread.py
addons/mail/mail_thread_view.xml
addons/mail/static/src/js/mail.js
addons/mail/tests/test_mail_features.py
addons/mail/tests/test_mail_gateway.py
addons/portal/__init__.py
addons/portal/mail_mail.py
addons/portal/tests/test_portal.py

Simple merge
@@@ -980,25 -981,18 +981,28 @@@ class crm_lead(base_stage, format_addre
      def message_get_reply_to(self, cr, uid, ids, context=None):
          """ Override to get the reply_to of the parent project. """
          return [lead.section_id.message_get_reply_to()[0] if lead.section_id else False
-                     for lead in self.browse(cr, uid, ids, context=context)]
+                     for lead in self.browse(cr, SUPERUSER_ID, ids, context=context)]
  
 +    def _get_formview_action(self, cr, uid, id, context=None):
 +        action = super(crm_lead, self)._get_formview_action(cr, uid, id, context=context)
 +        obj = self.browse(cr, uid, id, context=context)
 +        if obj.type == 'opportunity':
 +            model, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'crm', 'crm_case_form_view_oppor')
 +            action.update({
 +                'views': [(view_id, 'form')],
 +                })
 +        return action
 +
      def message_get_suggested_recipients(self, cr, uid, ids, context=None):
          recipients = super(crm_lead, self).message_get_suggested_recipients(cr, uid, ids, context=context)
-         for lead in self.browse(cr, uid, ids, context=context):
-             if lead.partner_id:
-                 self._message_add_suggested_recipient(cr, uid, recipients, lead, partner=lead.partner_id, reason=_('Customer'))
-             elif lead.email_from:
-                 self._message_add_suggested_recipient(cr, uid, recipients, lead, email=lead.email_from, reason=_('Customer Email'))
+         try:
+             for lead in self.browse(cr, uid, ids, context=context):
+                 if lead.partner_id:
+                     self._message_add_suggested_recipient(cr, uid, recipients, lead, partner=lead.partner_id, reason=_('Customer'))
+                 elif lead.email_from:
+                     self._message_add_suggested_recipient(cr, uid, recipients, lead, email=lead.email_from, reason=_('Customer Email'))
+         except (osv.except_osv, orm.except_orm):  # no read access rights -> just ignore suggested recipients because this imply modifying followers
+             pass
          return recipients
  
      def message_new(self, cr, uid, msg, custom_values=None, context=None):
@@@ -231,10 -212,38 +241,12 @@@ class mail_mail(osv.Model)
              :param browse_record mail: mail.mail browse_record
              :param browse_record partner: specific recipient partner
          """
-         if force or (not mail.subject and mail.model and mail.res_id):
+         if (force or not mail.subject) and mail.record_name:
              return 'Re: %s' % (mail.record_name)
+         elif (force or not mail.subject) and mail.parent_id and mail.parent_id.subject:
+             return 'Re: %s' % (mail.parent_id.subject)
          return mail.subject
  
 -    def send_get_mail_body_footer(self, cr, uid, mail, partner=None, context=None):
 -        """ Return a specific footer for the ir_email body.  The main purpose of this method
 -            is to be inherited by Portal, to add modify the link for signing in, in
 -            each notification email a partner receives.
 -        """
 -        body_footer = ""
 -        # partner is a user, link to a related document (incentive to install portal)
 -        if partner and partner.user_ids and mail.model and mail.res_id \
 -                and self.check_access_rights(cr, partner.user_ids[0].id, 'read', raise_exception=False):
 -            related_user = partner.user_ids[0]
 -            try:
 -                self.pool[mail.model].check_access_rule(cr, related_user.id, [mail.res_id], 'read', context=context)
 -                base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
 -                # the parameters to encode for the query and fragment part of url
 -                query = {'db': cr.dbname}
 -                fragment = {
 -                    'login': related_user.login,
 -                    'model': mail.model,
 -                    'id': mail.res_id,
 -                }
 -                url = urljoin(base_url, "?%s#%s" % (urlencode(query), urlencode(fragment)))
 -                body_footer = _("""<small>Access this document <a style='color:inherit' href="%s">directly in OpenERP</a></small>""") % url
 -            except except_orm, e:
 -                pass
 -        return body_footer
 -
      def send_get_mail_body(self, cr, uid, mail, partner=None, context=None):
          """ Return a specific ir_email body. The main purpose of this method
              is to be inherited to add custom content depending on some module.
Simple merge
Simple merge
Simple merge
@@@ -124,7 -123,106 +124,107 @@@ class TestMailgateway(TestMailBase)
          self.assertEqual(partner_info['partner_id'], p_b_id,
                          'mail_thread: message_find_partner_from_emails wrong partner found')
  
+     def test_05_mail_message_mail_mail(self):
+         """ Tests designed for testing email values based on mail.message, aliases, ... """
+         cr, uid = self.cr, self.uid
+         # Data: clean catchall domain
+         param_ids = self.registry('ir.config_parameter').search(cr, uid, [('key', '=', 'mail.catchall.domain')])
+         self.registry('ir.config_parameter').unlink(cr, uid, param_ids)
+         # Do: create a mail_message with a reply_to, without message-id
+         msg_id = self.mail_message.create(cr, uid, {'subject': 'Subject', 'body': 'Body', 'reply_to': 'custom@example.com'})
+         msg = self.mail_message.browse(cr, uid, msg_id)
+         # Test: message content
+         self.assertIn('reply_to', msg.message_id,
+                         'mail_message: message_id should be specific to a mail_message with a given reply_to')
+         self.assertEqual('custom@example.com', msg.reply_to,
+                         'mail_message: incorrect reply_to')
+         # Do: create a mail_mail with the previous mail_message and specified reply_to
+         mail_id = self.mail_mail.create(cr, uid, {'mail_message_id': msg_id, 'reply_to': 'other@example.com', 'state': 'cancel'})
+         mail = self.mail_mail.browse(cr, uid, mail_id)
+         # Test: mail_mail content
+         self.assertEqual(mail.reply_to, 'other@example.com',
+                         'mail_mail: reply_to should be equal to the one coming from creation values')
+         # Do: create a mail_mail with the previous mail_message
+         mail_id = self.mail_mail.create(cr, uid, {'mail_message_id': msg_id, 'state': 'cancel'})
+         mail = self.mail_mail.browse(cr, uid, mail_id)
+         # Test: mail_mail content
+         self.assertEqual(mail.reply_to, msg.reply_to,
+                         'mail_mail: reply_to should be equal to the one coming from the mail_message')
+         # Do: create a mail_message without a reply_to
+         msg_id = self.mail_message.create(cr, uid, {'subject': 'Subject', 'body': 'Body', 'model': 'mail.group', 'res_id': self.group_pigs_id, 'email_from': False})
+         msg = self.mail_message.browse(cr, uid, msg_id)
+         # Test: message content
+         self.assertIn('mail.group', msg.message_id,
+                         'mail_message: message_id should contain model')
+         self.assertIn('%s' % self.group_pigs_id, msg.message_id,
+                         'mail_message: message_id should contain res_id')
+         self.assertFalse(msg.reply_to,
+                         'mail_message: should not generate a reply_to address when not specified')
+         # Do: create a mail_mail based on the previous mail_message
+         mail_id = self.mail_mail.create(cr, uid, {'mail_message_id': msg_id, 'state': 'cancel'})
+         mail = self.mail_mail.browse(cr, uid, mail_id)
+         # Test: mail_mail content
+         self.assertFalse(mail.reply_to,
+                         'mail_mail: reply_to should not have been guessed')
+         # Update message
+         self.mail_message.write(cr, uid, [msg_id], {'email_from': 'someone@example.com'})
+         msg.refresh()
+         # Do: create a mail_mail based on the previous mail_message
+         mail_id = self.mail_mail.create(cr, uid, {'mail_message_id': msg_id, 'state': 'cancel'})
+         mail = self.mail_mail.browse(cr, uid, mail_id)
+         # Test: mail_mail content
+         self.assertEqual(mail.reply_to, msg.email_from,
+                         'mail_mail: reply_to should equal to mail_message.email_from when having no document or default alias')
+         # Data: set catchall domain
+         self.registry('ir.config_parameter').set_param(cr, uid, 'mail.catchall.domain', 'schlouby.fr')
+         self.registry('ir.config_parameter').unlink(cr, uid, self.registry('ir.config_parameter').search(cr, uid, [('key', '=', 'mail.catchall.alias')]))
+         # Do: create a mail_mail based on the previous mail_message
+         mail_id = self.mail_mail.create(cr, uid, {'mail_message_id': msg_id, 'state': 'cancel'})
+         mail = self.mail_mail.browse(cr, uid, mail_id)
+         # Test: mail_mail content
+         self.assertEqual(mail.reply_to, '"Followers of Pigs" <group+pigs@schlouby.fr>',
+                         'mail_mail: reply_to should equal the mail.group alias')
+         # Update message
+         self.mail_message.write(cr, uid, [msg_id], {'res_id': False, 'email_from': 'someone@schlouby.fr'})
+         msg.refresh()
+         # Do: create a mail_mail based on the previous mail_message
+         mail_id = self.mail_mail.create(cr, uid, {'mail_message_id': msg_id, 'state': 'cancel'})
+         mail = self.mail_mail.browse(cr, uid, mail_id)
+         # Test: mail_mail content
+         self.assertEqual(mail.reply_to, msg.email_from,
+                         'mail_mail: reply_to should equal the mail_message email_from')
+         # Data: set catchall alias
+         self.registry('ir.config_parameter').set_param(self.cr, self.uid, 'mail.catchall.alias', 'gateway')
+         # Do: create a mail_mail based on the previous mail_message
+         mail_id = self.mail_mail.create(cr, uid, {'mail_message_id': msg_id, 'state': 'cancel'})
+         mail = self.mail_mail.browse(cr, uid, mail_id)
+         # Test: mail_mail content
+         self.assertEqual(mail.reply_to, 'gateway@schlouby.fr',
+                         'mail_mail: reply_to should equal the catchall email alias')
+         # Do: create a mail_mail
+         mail_id = self.mail_mail.create(cr, uid, {'state': 'cancel'})
+         mail = self.mail_mail.browse(cr, uid, mail_id)
+         # Test: mail_mail content
+         self.assertEqual(mail.reply_to, 'gateway@schlouby.fr',
+                         'mail_mail: reply_to should equal the catchall email alias')
+         # Do: create a mail_mail
+         mail_id = self.mail_mail.create(cr, uid, {'state': 'cancel', 'reply_to': 'someone@example.com'})
+         mail = self.mail_mail.browse(cr, uid, mail_id)
+         # Test: mail_mail content
+         self.assertEqual(mail.reply_to, 'someone@example.com',
+                         'mail_mail: reply_to should equal the rpely_to given to create')
 +    @mute_logger('openerp.addons.mail.mail_thread', 'openerp.osv.orm')
      def test_10_message_process(self):
          """ Testing incoming emails processing. """
          cr, uid, user_raoul = self.cr, self.uid, self.user_raoul
@@@ -20,8 -20,8 +20,9 @@@
  ##############################################################################
  
  import portal
 +import mail_thread
  import mail_mail
+ import mail_message
  import wizard
  import acquirer
  
@@@ -28,14 -28,20 +28,19 @@@ class mail_mail(osv.Model)
      """ Update of mail_mail class, to add the signin URL to notifications. """
      _inherit = 'mail.mail'
  
 -    def send_get_mail_body_footer(self, cr, uid, mail, partner=None, context=None):
 -        """ add a signin link inside the body of a mail.mail
 -            :param mail: mail.mail browse_record
 -            :param partner: browse_record of the specific recipient partner
 -            :return: the resulting body_html
 +    def _get_partner_access_link(self, cr, uid, mail, partner=None, context=None):
 +        """ Generate URLs for links in mails:
 +            - partner is not an user: signup_url
 +            - partner is an user: fallback on classic URL
          """
+         if context is None:
+             context = {}
+         partner_obj = self.pool.get('res.partner')
          if partner and not partner.user_ids:
-             contex_signup = dict(context or {}, signup_valid=True)
-             partner = self.pool.get('res.partner').browse(cr, SUPERUSER_ID, partner.id, context=contex_signup)
-             return _("""<small>Access your messages and documents through <a style='color:inherit' href="%s">our Customer Portal</a></small>""") % partner.signup_url
+             contex_signup = dict(context, signup_valid=True)
+             signup_url = partner_obj._get_signup_url_for_action(cr, SUPERUSER_ID, [partner.id],
+                                                                     action='login', model=mail.model, res_id=mail.res_id,
+                                                                     context=contex_signup)[partner.id]
+             return _("""<small>Access your messages and documents through <a style='color:inherit' href="%s">our Customer Portal</a></small>""") % signup_url
          else:
 -            return super(mail_mail, self).send_get_mail_body_footer(cr, uid, mail, partner=partner, context=context)
 +            return super(mail_mail, self)._get_partner_access_link(cr, uid, mail, partner=partner, context=context)
@@@ -131,69 -133,27 +133,93 @@@ class test_portal(TestMailBase)
              self.assertTrue(partner_carine.signup_url in sent_email.get('body'),
                              'invite: body of invitation email does not contain signup url')
  
 -    def test_20_message_read(self):
 +    def test_20_notification_url(self):
 +        """ Tests designed to test the URL added in notification emails. """
 +        cr, uid, group_pigs = self.cr, self.uid, self.group_pigs
 +
 +        # Partner data
 +        partner_raoul = self.res_partner.browse(cr, uid, self.partner_raoul_id)
 +        partner_bert_id = self.res_partner.create(cr, uid, {'name': 'bert'})
 +        partner_bert = self.res_partner.browse(cr, uid, partner_bert_id)
 +        # Mail data
 +        mail_mail_id = self.mail_mail.create(cr, uid, {'state': 'exception'})
 +        mail = self.mail_mail.browse(cr, uid, mail_mail_id)
 +
 +        # Test: link for nobody -> None
 +        url = self.mail_mail._get_partner_access_link(cr, uid, mail)
 +        self.assertEqual(url, None,
 +                        'notification email: mails not send to a specific partner should not have any URL')
 +
 +        # Test: link for partner -> signup URL
 +        url = self.mail_mail._get_partner_access_link(cr, uid, mail, partner=partner_bert)
 +        self.assertIn(partner_bert.signup_url, url,
 +                        'notification email: mails send to a not-user partner should contain the signup URL')
 +
 +        # Test: link for user -> signin
 +        url = self.mail_mail._get_partner_access_link(cr, uid, mail, partner=partner_raoul)
 +        self.assertIn('action=mail.action_mail_redirect', url,
 +                        'notification email: link should contain the redirect action')
 +        self.assertIn('login=%s' % partner_raoul.user_ids[0].login, url,
 +                        'notification email: link should contain the user login')
 +
 +    @mute_logger('openerp.addons.mail.mail_thread', 'openerp.osv.orm')
 +    def test_21_inbox_redirection(self):
 +        """ Tests designed to test the inbox redirection of emails notification URLs. """
 +        cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
 +        model, act_id = self.ir_model_data.get_object_reference(cr, uid, 'mail', 'action_mail_inbox_feeds')
 +        model, port_act_id = self.ir_model_data.get_object_reference(cr, uid, 'portal', 'action_mail_inbox_feeds_portal')
 +        # Data: post a message on pigs
 +        msg_id = self.group_pigs.message_post(body='My body', partner_ids=[self.partner_bert_id, self.partner_chell_id], type='comment', subtype='mail.mt_comment')
 +
 +        # No specific parameters -> should redirect to Inbox
 +        action = self.mail_thread.message_redirect_action(cr, self.user_raoul_id, {'params': {}})
 +        self.assertEqual(action.get('type'), 'ir.actions.client',
 +                        'URL redirection: action without parameters should redirect to client action Inbox')
 +        self.assertEqual(action.get('id'), act_id,
 +                        'URL redirection: action without parameters should redirect to client action Inbox')
 +
 +        # Bert has read access to Pigs -> should redirect to form view of Pigs
 +        action = self.mail_thread.message_redirect_action(cr, self.user_raoul_id, {'params': {'message_id': msg_id}})
 +        self.assertEqual(action.get('type'), 'ir.actions.act_window',
 +                        'URL redirection: action with message_id for read-accredited user should redirect to Pigs')
 +        self.assertEqual(action.get('res_id'), group_pigs.id,
 +                        'URL redirection: action with message_id for read-accredited user should redirect to Pigs')
 +
 +        # Bert has no read access to Pigs -> should redirect to Inbox
 +        action = self.mail_thread.message_redirect_action(cr, self.user_bert_id, {'params': {'message_id': msg_id}})
 +        self.assertEqual(action.get('type'), 'ir.actions.client',
 +                        'URL redirection: action without parameters should redirect to client action Inbox')
 +        self.assertEqual(action.get('id'), act_id,
 +                        'URL redirection: action without parameters should redirect to client action Inbox')
 +
 +        # Chell has no read access to pigs -> should redirect to Portal Inbox
 +        action = self.mail_thread.message_redirect_action(cr, self.user_chell_id, {'params': {'message_id': msg_id}})
 +        self.assertEqual(action.get('type'), 'ir.actions.client',
 +                        'URL redirection: action without parameters should redirect to client action Inbox')
 +        self.assertEqual(action.get('id'), port_act_id,
 +                        'URL redirection: action without parameters should redirect to client action Inbox')
 +
++    def test_30_message_read(self):
+         cr, uid, group_port_id = self.cr, self.uid, self.group_port_id
+         # Data: custom subtypes
+         mt_group_public_id = self.mail_message_subtype.create(cr, uid, {'name': 'group_public', 'description': 'Group changed'})
+         self.ir_model_data.create(cr, uid, {'name': 'mt_group_public', 'model': 'mail.message.subtype', 'module': 'mail', 'res_id': mt_group_public_id})
+         # Data: post messages with various subtypes
+         msg1_id = self.mail_group.message_post(cr, uid, group_port_id, body='Body1', type='comment', subtype='mail.mt_comment')
+         msg2_id = self.mail_group.message_post(cr, uid, group_port_id, body='Body2', type='comment', subtype='mail.mt_group_public')
+         msg3_id = self.mail_group.message_post(cr, uid, group_port_id, body='Body3', type='comment', subtype='mail.mt_comment')
+         msg4_id = self.mail_group.message_post(cr, uid, group_port_id, body='Body4', type='comment')
+         msg5_id = self.mail_group.message_post(cr, uid, group_port_id, body='Body5', type='notification')
+         # Do: Chell search messages: should not see internal notes (comment without subtype)
+         msg_ids = self.mail_message.search(cr, self.user_chell_id, [('model', '=', 'mail.group'), ('res_id', '=', group_port_id)])
+         self.assertEqual(set(msg_ids), set([msg1_id, msg2_id, msg3_id, msg5_id]),
+                         'mail_message: portal user has access to messages he should not read')
+         # Do: Chell read messages she can read
+         self.mail_message.read(cr, self.user_chell_id, msg_ids, ['body', 'type', 'subtype_id'])
+         # Do: Chell read a message she should not be able to read
+         with self.assertRaises(except_orm):
+             self.mail_message.read(cr, self.user_chell_id, [msg4_id], ['body', 'type', 'subtype_id'])