From 8c67d4150b257f3338f3538fb874bcca6a00e287 Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Wed, 3 Apr 2013 16:22:06 +0200 Subject: [PATCH 1/8] [PROGRESS] bzr revid: chm@openerp.com-20130403142206-i4oirpl41usx4qg0 --- addons/mail/mail_thread.py | 30 ++++++++++++++++++++++++++++++ addons/mail/mail_thread_view.xml | 2 ++ addons/mail/static/src/js/mail.js | 1 + 3 files changed, 33 insertions(+) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index cb8da42ab47..23341ac148f 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -1078,6 +1078,36 @@ class mail_thread(osv.AbstractModel): self.message_subscribe(cr, uid, [thread_id], [message.author_id.id], context=context) return msg_id + def get_message_action_from_link(self, cr, uid, message_id, context=None): + action = None + msg_obj = self.pool.get('mail.message') + msg = msg_obj.browse(cr, uid, message_id, context=context) + if msg.model: + try: + self.pool.get(msg.model).check_access_rights(cr, uid, 'read') + action = { + 'type': 'ir.actions.act_window', + 'res_model': msg.model, + 'view_type': 'form', + 'view_mode': 'form', + 'views': [(msg.res_id, 'form')], + 'target': 'current', + 'res_id': msg.res_id, + } + except openerp.exceptions.AccessDenied: + mod_obj = self.pool.get('ir.model.data') + act_model, act_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mail', 'action_mail_inbox_feeds') + action = self.pool.get(act_model).read(cr, uid, act_id, []) + action.update({ + 'context': { + 'default_model': 'res.users', + 'default_res_id': uid, + 'search_default_res_id': 1, + 'search_default_model': 1, + } + }) + return action + #------------------------------------------------------ # Compatibility methods: do not use # TDE TODO: remove me in 8.0 diff --git a/addons/mail/mail_thread_view.xml b/addons/mail/mail_thread_view.xml index 75c86e4645d..62fde0cf026 100644 --- a/addons/mail/mail_thread_view.xml +++ b/addons/mail/mail_thread_view.xml @@ -8,6 +8,8 @@ { 'default_model': 'res.users', 'default_res_id': uid, + 'search_default_model': context.get('params') and context.params.get('model'), + 'search_default_res_id': context.get('params') and context.params.get('res_id') } Access this document directly in OpenERP""") % url + else: + return None + def send_get_mail_subject(self, cr, uid, mail, force=False, partner=None, context=None): """ If subject is void and record_name defined: ' posted on ' @@ -170,25 +194,10 @@ class mail_mail(osv.Model): :param browse_record partner: specific recipient partner """ body = mail.body_html - # 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.get(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))) - text = _("""

Access this document directly in OpenERP

""") % url - body = tools.append_content_to_html(body, ("

%s

" % text), plaintext=False) - except except_orm, e: - pass + # generate footer + link = self._get_partner_access_link(cr, uid, mail, partner, context=context) + if link: + body = tools.append_content_to_html(body, link, plaintext=False, container_tag='div') return body def send_get_mail_reply_to(self, cr, uid, mail, partner=None, context=None): diff --git a/addons/mail/mail_message_view.xml b/addons/mail/mail_message_view.xml index 8f2fb3f1020..02f4ef7fdbe 100644 --- a/addons/mail/mail_message_view.xml +++ b/addons/mail/mail_message_view.xml @@ -59,6 +59,9 @@ + + + diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 23341ac148f..08cf1f1495f 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -389,6 +389,54 @@ class mail_thread(osv.AbstractModel): return [('message_unread', '=', True)] return [] + def message_redirect_get_inbox_action_xml_id(self, cr, uid, context=None): + """ When redirecting towards the Inbox, choose which action xml_id has + to be fetched. This method is meant to be inherited, at least in portal + because portal users have a different Inbox action than classic users. """ + return ('mail', 'action_mail_inbox_feeds') + + def message_redirect_action(self, cr, uid, context=None): + """ For a given message, return an action that either + - opens the form view of the related document if model, res_id, and + read access to the document + - opens the Inbox with a default search on the conversation if model, + res_id + - opens the Inbox with context propagated + """ + if context is None: + context = {} + + # default action is the Inbox action + self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context=context) + act_model, act_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, *self.message_redirect_get_inbox_action_xml_id(cr, uid, context=context)) + action = self.pool.get(act_model).read(cr, uid, act_id, []) + + # if msg_id specified: try to redirect to the document or fallback on the Inbox + msg_id = context.get('params', {}).get('message_id') + if not msg_id: + return action + msg = self.pool.get('mail.message').browse(cr, uid, msg_id, context=context) + if msg.model and msg.res_id and self.pool.get(msg.model).check_access_rights(cr, uid, 'read', raise_exception=False): + try: + self.pool.get(msg.model).check_access_rule(cr, uid, [msg.res_id], 'read', context=context) + action = { + 'type': 'ir.actions.act_window', + 'res_model': msg.model, + 'view_type': 'form', + 'view_mode': 'form', + 'views': [(msg.res_id, 'form')], + 'target': 'current', + 'res_id': msg.res_id, + } + except osv.except_osv: + action.update({ + 'context': { + 'search_default_model': msg.model, + 'search_default_res_id': msg.res_id, + } + }) + return action + #------------------------------------------------------ # Email specific #------------------------------------------------------ @@ -1078,36 +1126,6 @@ class mail_thread(osv.AbstractModel): self.message_subscribe(cr, uid, [thread_id], [message.author_id.id], context=context) return msg_id - def get_message_action_from_link(self, cr, uid, message_id, context=None): - action = None - msg_obj = self.pool.get('mail.message') - msg = msg_obj.browse(cr, uid, message_id, context=context) - if msg.model: - try: - self.pool.get(msg.model).check_access_rights(cr, uid, 'read') - action = { - 'type': 'ir.actions.act_window', - 'res_model': msg.model, - 'view_type': 'form', - 'view_mode': 'form', - 'views': [(msg.res_id, 'form')], - 'target': 'current', - 'res_id': msg.res_id, - } - except openerp.exceptions.AccessDenied: - mod_obj = self.pool.get('ir.model.data') - act_model, act_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mail', 'action_mail_inbox_feeds') - action = self.pool.get(act_model).read(cr, uid, act_id, []) - action.update({ - 'context': { - 'default_model': 'res.users', - 'default_res_id': uid, - 'search_default_res_id': 1, - 'search_default_model': 1, - } - }) - return action - #------------------------------------------------------ # Compatibility methods: do not use # TDE TODO: remove me in 8.0 diff --git a/addons/mail/mail_thread_view.xml b/addons/mail/mail_thread_view.xml index 62fde0cf026..6c7bdc2da72 100644 --- a/addons/mail/mail_thread_view.xml +++ b/addons/mail/mail_thread_view.xml @@ -8,8 +8,6 @@ { 'default_model': 'res.users', 'default_res_id': uid, - 'search_default_model': context.get('params') and context.params.get('model'), - 'search_default_res_id': context.get('params') and context.params.get('res_id') } + Mail Redirection (Document / Inbox) + True + + action = pool.get('mail.thread').message_redirect_action(cr, uid, context) + code + ir.actions.server + + diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js index f3306b40da7..580cca91113 100644 --- a/addons/mail/static/src/js/mail.js +++ b/addons/mail/static/src/js/mail.js @@ -1834,10 +1834,19 @@ openerp.mail = function (session) { this._super(parent, action); this.action = _.clone(action); + // debugger this.domain = this.action.params.domain || this.action.domain || []; this.context = _.extend(this.action.params.context || {}, this.action.context || {}); + // filter some parameters that we will propagate as search_default this.defaults = {}; + for (var key in this.action.context.params) { + if (_.indexOf(['model', 'res_id'], key) == -1) { + continue; + } + this.context['search_default_' + key] = this.action.context.params[key]; + console.log(this.context); + } for (var key in this.context) { if (key.match(/^search_default_/)) { this.defaults[key.replace(/^search_default_/, '')] = this.context[key]; @@ -1995,5 +2004,4 @@ openerp.mail = function (session) { }); }, }); - }; diff --git a/addons/mail/tests/test_mail_gateway.py b/addons/mail/tests/test_mail_gateway.py index a68011298c7..738945eec35 100644 --- a/addons/mail/tests/test_mail_gateway.py +++ b/addons/mail/tests/test_mail_gateway.py @@ -20,6 +20,7 @@ ############################################################################## from openerp.addons.mail.tests.test_mail_base import TestMailBase +from openerp.tools import mute_logger MAIL_TEMPLATE = """Return-Path: To: {to} @@ -83,6 +84,7 @@ Sylvie class TestMailgateway(TestMailBase): + @mute_logger('openerp.addons.mail.mail_thread', 'openerp.osv.orm') def test_00_message_process(self): """ Testing incoming emails processing. """ cr, uid, user_raoul = self.cr, self.uid, self.user_raoul @@ -325,6 +327,7 @@ class TestMailgateway(TestMailBase): self.assertEqual(msg.body, '
\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n
', 'message_process: plaintext incoming email incorrectly parsed') + @mute_logger('openerp.addons.mail.mail_thread', 'openerp.osv.orm') def test_10_thread_parent_resolution(self): """ Testing parent/child relationships are correctly established when processing incoming mails """ cr, uid = self.cr, self.uid diff --git a/addons/portal/__init__.py b/addons/portal/__init__.py index 0045a557a49..3a9b5ef80d0 100644 --- a/addons/portal/__init__.py +++ b/addons/portal/__init__.py @@ -20,6 +20,7 @@ ############################################################################## import portal +import mail_thread import mail_mail import wizard import acquirer diff --git a/addons/portal/mail_mail.py b/addons/portal/mail_mail.py index 5d2a91e691f..35658456c40 100644 --- a/addons/portal/mail_mail.py +++ b/addons/portal/mail_mail.py @@ -21,8 +21,6 @@ from openerp import SUPERUSER_ID from openerp.osv import osv -from openerp.osv.orm import except_orm -from openerp.tools import append_content_to_html from openerp.tools.translate import _ @@ -30,27 +28,18 @@ 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(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 """ - partner_obj = self.pool.get('res.partner') - body = mail.body_html - if partner: + if partner and not partner.user_ids: contex_signup = dict(context or {}, signup_valid=True) - partner = partner_obj.browse(cr, SUPERUSER_ID, partner.id, context=contex_signup) - text = _("""

Access your messages and personal documents through our Customer Portal

""") % partner.signup_url - # partner is an user: add a link to the document if read access - if 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.get(mail.model).check_access_rule(cr, related_user.id, [mail.res_id], 'read', context=context) - url = partner_obj._get_signup_url_for_action(cr, related_user.id, [partner.id], action='', res_id=mail.res_id, model=mail.model, context=context)[partner.id] - text = _("""

Access this document directly in OpenERP

""") % url - except except_orm, e: - pass - body = append_content_to_html(body, ("

%s

" % text), plaintext=False) - return body + partner = self.pool.get('res.partner').browse(cr, SUPERUSER_ID, partner.id, context=contex_signup) + return _("""Access your messages and documents through our Customer Portal""") % partner.signup_url + else: + return super(mail_mail, self)._get_partner_access_link(cr, uid, mail, partner=partner, context=context) + + def send_get_mail_body(self, cr, uid, mail, partner=None, context=None): + """ TODO: remove me in 8.0 """ + return super(mail_mail, self).send_get_mail_body(cr, uid, mail, partner=partner, context=context) From b61c12d28ea18587fc3effd7d57ac655894dbd1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 26 Apr 2013 15:28:47 +0200 Subject: [PATCH 3/8] [MERGE] Revisions 9000 ... 9002 of 7.0 branch bzr revid: tde@openerp.com-20130426132847-mr8e0yuxao582bs6 --- addons/crm/crm_lead.py | 10 ++++ addons/mail/mail_mail.py | 10 ++++ addons/mail/mail_thread.py | 41 +++++++++------ addons/mail/tests/test_mail_features.py | 64 ++++++++++++++++++++++- addons/portal/mail_thread.py | 44 ++++++++++++++++ addons/portal/tests/test_portal.py | 67 +++++++++++++++++++++++++ 6 files changed, 220 insertions(+), 16 deletions(-) create mode 100644 addons/portal/mail_thread.py diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 61e8c75cea8..8724b73396f 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -982,6 +982,16 @@ class crm_lead(base_stage, format_address, osv.osv): 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)] + def _get_document_action(self, cr, uid, id, context=None): + action = super(crm_lead, self)._get_document_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): diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py index 352a6aa61f5..990830fab18 100644 --- a/addons/mail/mail_mail.py +++ b/addons/mail/mail_mail.py @@ -198,6 +198,16 @@ class mail_mail(osv.Model): # mail_mail formatting, tools and send mechanism #------------------------------------------------------ + # TDE NOTE: as for 7.0 link generation is located in mail.mail because it grew + # based on a simple URL generation that moved towards something more complicated + # Moreover a bug in hierarchy generation prevents from putting link generation + # inside mail_thread and override it in modules; the overrides will not be + # taken into account + # TODO in 8.0: maybe factorize this to enable in modules link generation + # independently of mail_mail model + # TODO in 8.0: factorize doc name sanitized and 'Followers of ...' formatting + # because it begins to appear everywhere + def _get_partner_access_link(self, cr, uid, mail, partner=None, context=None): """ Generate URLs for links in mails: - partner is an user and has read access to the document: direct link to document with model, res_id diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 4f9116a73a0..38a2f86698e 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -33,7 +33,7 @@ from email.message import Message from openerp import tools from openerp import SUPERUSER_ID from openerp.addons.mail.mail_message import decode -from openerp.osv import fields, osv +from openerp.osv import fields, osv, orm from openerp.tools.safe_eval import safe_eval as eval from openerp.tools.translate import _ @@ -449,7 +449,24 @@ class mail_thread(osv.AbstractModel): ir_attachment_obj.unlink(cr, uid, attach_ids, context=context) return True - def message_redirect_get_inbox_action_xml_id(self, cr, uid, context=None): + def _get_document_action(self, cr, uid, id, model=None, context=None): + """ Return an action to open the document. This method is meant to be + overridden in addons that want to give specific view ids for example. + + :param int id: id of the document to open + :param string model: specific model that overrides self._name + """ + return { + 'type': 'ir.actions.act_window', + 'res_model': model or self._name, + 'view_type': 'form', + 'view_mode': 'form', + 'views': [(False, 'form')], + 'target': 'current', + 'res_id': id, + } + + def _get_inbox_action_xml_id(self, cr, uid, context=None): """ When redirecting towards the Inbox, choose which action xml_id has to be fetched. This method is meant to be inherited, at least in portal because portal users have a different Inbox action than classic users. """ @@ -469,7 +486,7 @@ class mail_thread(osv.AbstractModel): # default action is the Inbox action self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context=context) - act_model, act_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, *self.message_redirect_get_inbox_action_xml_id(cr, uid, context=context)) + act_model, act_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, *self._get_inbox_action_xml_id(cr, uid, context=context)) action = self.pool.get(act_model).read(cr, uid, act_id, []) # if msg_id specified: try to redirect to the document or fallback on the Inbox @@ -479,17 +496,13 @@ class mail_thread(osv.AbstractModel): msg = self.pool.get('mail.message').browse(cr, uid, msg_id, context=context) if msg.model and msg.res_id and self.pool.get(msg.model).check_access_rights(cr, uid, 'read', raise_exception=False): try: - self.pool.get(msg.model).check_access_rule(cr, uid, [msg.res_id], 'read', context=context) - action = { - 'type': 'ir.actions.act_window', - 'res_model': msg.model, - 'view_type': 'form', - 'view_mode': 'form', - 'views': [(msg.res_id, 'form')], - 'target': 'current', - 'res_id': msg.res_id, - } - except osv.except_osv: + model_obj = self.pool.get(msg.model) + model_obj.check_access_rule(cr, uid, [msg.res_id], 'read', context=context) + if not hasattr(model_obj, '_get_document_action'): + action = self.pool.get('mail.thread')._get_document_action(cr, uid, msg.res_id, model=msg.model, context=context) + else: + action = model_obj._get_document_action(cr, uid, msg.res_id, context=context) + except (osv.except_osv, orm.except_orm): action.update({ 'context': { 'search_default_model': msg.model, diff --git a/addons/mail/tests/test_mail_features.py b/addons/mail/tests/test_mail_features.py index 7eea66b3b90..c8f83188b85 100644 --- a/addons/mail/tests/test_mail_features.py +++ b/addons/mail/tests/test_mail_features.py @@ -19,12 +19,15 @@ # ############################################################################## +from openerp.addons.mail.mail_mail import mail_mail +from openerp.addons.mail.mail_thread import mail_thread from openerp.addons.mail.tests.test_mail_base import TestMailBase +from openerp.tools import mute_logger from openerp.tools.mail import html_sanitize class test_mail(TestMailBase): - + def test_000_alias_setup(self): """ Test basic mail.alias setup works, before trying to use them for routing """ cr, uid = self.cr, self.uid @@ -43,7 +46,6 @@ class test_mail(TestMailBase): self.user_barty = self.res_users.browse(cr, uid, self.user_barty_id) self.assertEquals(self.user_barty.alias_name, 'b4r+_-_r3wl-', 'Disallowed chars should be replaced by hyphens') - def test_00_followers_function_field(self): """ Tests designed for the many2many function field 'follower_ids'. We will test to perform writes using the many2many commands 0, 3, 4, @@ -171,6 +173,64 @@ class test_mail(TestMailBase): self.assertNotIn('First answer, should not be displayed', result, 'Old answer should not be in quote.') self.assertNotIn('My answer I am propagating', result, 'Thread header content should be in quote.') + def test_11_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 = mail_mail._get_partner_access_link(self.mail_mail, 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 -> None + url = mail_mail._get_partner_access_link(self.mail_mail, cr, uid, mail, partner=partner_bert) + self.assertEqual(url, None, + 'notification email: mails send to a not-user partner should not have any URL') + + # Test: link for user -> signin + url = mail_mail._get_partner_access_link(self.mail_mail, 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_12_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') + # Data: post a message on pigs + msg_id = self.group_pigs.message_post(body='My body', partner_ids=[self.partner_bert_id], type='comment', subtype='mail.mt_comment') + + # No specific parameters -> should redirect to Inbox + action = mail_thread.message_redirect_action(self.mail_thread, 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 = mail_thread.message_redirect_action(self.mail_thread, 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 = mail_thread.message_redirect_action(self.mail_thread, 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') + def test_20_message_post(self): """ Tests designed for message_post. """ cr, uid, user_raoul, group_pigs = self.cr, self.uid, self.user_raoul, self.group_pigs diff --git a/addons/portal/mail_thread.py b/addons/portal/mail_thread.py new file mode 100644 index 00000000000..54fb0d26279 --- /dev/null +++ b/addons/portal/mail_thread.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2013-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 import SUPERUSER_ID +from openerp.osv import osv + + +class mail_thread(osv.AbstractModel): + """ Update of mail_mail class, to add the signin URL to notifications. """ + _inherit = 'mail.thread' + + def _get_inbox_action_xml_id(self, cr, uid, context=None): + """ For a given message, return an action that either + - opens the form view of the related document if model, res_id, and + read access to the document + - opens the Inbox with a default search on the conversation if model, + res_id + - opens the Inbox with context propagated + """ + # if uid is a portal user -> action is different + portal_model, group_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'portal', 'group_portal') + cur_user = self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context=context) + if group_id in [g.id for g in cur_user.groups_id]: + return ('portal', 'action_mail_inbox_feeds_portal') + else: + return super(mail_thread, self)._get_inbox_action_xml_id(cr, uid, context=context) diff --git a/addons/portal/tests/test_portal.py b/addons/portal/tests/test_portal.py index 07becf5cdd9..8021a0e371c 100644 --- a/addons/portal/tests/test_portal.py +++ b/addons/portal/tests/test_portal.py @@ -130,3 +130,70 @@ class test_portal(TestMailBase): 'invite: body of invitation email is incorrect') self.assertTrue(partner_carine.signup_url in sent_email.get('body'), 'invite: body of invitation email does not contain signup url') + + 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') + From 10176982c45ac40f15273fa173da666f28f6ed69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 26 Apr 2013 15:46:56 +0200 Subject: [PATCH 4/8] [CLEAN] Remove dead code left when merging 7.0 branch inside trunk branch bzr revid: tde@openerp.com-20130426134656-hruyetp4x10g1t0h --- addons/mail/mail_mail.py | 30 ------------------------------ addons/portal/mail_mail.py | 4 ---- 2 files changed, 34 deletions(-) diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py index 990830fab18..6f02c31ecf6 100644 --- a/addons/mail/mail_mail.py +++ b/addons/mail/mail_mail.py @@ -240,32 +240,6 @@ class mail_mail(osv.Model): return 'Re: %s' % (mail.record_name) 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 = _("""Access this document directly in OpenERP""") % 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. @@ -275,10 +249,6 @@ class mail_mail(osv.Model): """ body = mail.body_html - # add footer - body_footer = self.send_get_mail_body_footer(cr, uid, mail, partner=partner, context=context) - body = tools.append_content_to_html(body, body_footer, plaintext=False, container_tag='div') - # generate footer link = self._get_partner_access_link(cr, uid, mail, partner, context=context) if link: diff --git a/addons/portal/mail_mail.py b/addons/portal/mail_mail.py index 35658456c40..faaf21e5028 100644 --- a/addons/portal/mail_mail.py +++ b/addons/portal/mail_mail.py @@ -39,7 +39,3 @@ class mail_mail(osv.Model): return _("""Access your messages and documents through our Customer Portal""") % partner.signup_url else: return super(mail_mail, self)._get_partner_access_link(cr, uid, mail, partner=partner, context=context) - - def send_get_mail_body(self, cr, uid, mail, partner=None, context=None): - """ TODO: remove me in 8.0 """ - return super(mail_mail, self).send_get_mail_body(cr, uid, mail, partner=partner, context=context) From 0691000b61b43a80a636316d2b6285ddcc3c50e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 26 Apr 2013 16:40:19 +0200 Subject: [PATCH 5/8] [IMP] account_invoice: now have its _get_formview_action method. _get_formview_action is the renaming of _get_document_action, by the way. bzr revid: tde@openerp.com-20130426144019-w4tq4of7rdkyqbah --- addons/account/account_invoice.py | 20 ++++++++++++++++++++ addons/crm/crm_lead.py | 4 ++-- addons/mail/mail_thread.py | 8 ++++---- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py index c47cf70b673..701ede7e799 100644 --- a/addons/account/account_invoice.py +++ b/addons/account/account_invoice.py @@ -635,6 +635,26 @@ class account_invoice(osv.osv): self.create_workflow(cr, uid, ids) return True + # ---------------------------------------- + # Mail related methods + # ---------------------------------------- + + def _get_formview_action(self, cr, uid, id, context=None): + """ Update form view id of action to open the invoice """ + action = super(account_invoice, self)._get_formview_action(cr, uid, id, context=context) + obj = self.browse(cr, uid, id, context=context) + if obj.type == 'in_invoice': + model, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'invoice_supplier_form') + action.update({ + 'views': [(view_id, 'form')], + }) + else: + model, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'invoice_form') + action.update({ + 'views': [(view_id, 'form')], + }) + return action + # Workflow stuff ################# diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 8724b73396f..c2ea6f0d6d2 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -982,8 +982,8 @@ class crm_lead(base_stage, format_address, osv.osv): 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)] - def _get_document_action(self, cr, uid, id, context=None): - action = super(crm_lead, self)._get_document_action(cr, uid, id, 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') diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 38a2f86698e..4af604891ac 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -449,7 +449,7 @@ class mail_thread(osv.AbstractModel): ir_attachment_obj.unlink(cr, uid, attach_ids, context=context) return True - def _get_document_action(self, cr, uid, id, model=None, context=None): + def _get_formview_action(self, cr, uid, id, model=None, context=None): """ Return an action to open the document. This method is meant to be overridden in addons that want to give specific view ids for example. @@ -498,10 +498,10 @@ class mail_thread(osv.AbstractModel): try: model_obj = self.pool.get(msg.model) model_obj.check_access_rule(cr, uid, [msg.res_id], 'read', context=context) - if not hasattr(model_obj, '_get_document_action'): - action = self.pool.get('mail.thread')._get_document_action(cr, uid, msg.res_id, model=msg.model, context=context) + if not hasattr(model_obj, '_get_formview_action'): + action = self.pool.get('mail.thread')._get_formview_action(cr, uid, msg.res_id, model=msg.model, context=context) else: - action = model_obj._get_document_action(cr, uid, msg.res_id, context=context) + action = model_obj._get_formview_action(cr, uid, msg.res_id, context=context) except (osv.except_osv, orm.except_orm): action.update({ 'context': { From c08b877370b24410bd33fad6d56899aa2eb6a7a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 29 Apr 2013 11:33:29 +0200 Subject: [PATCH 6/8] [FIX] mail: fixed redirection action that was not redirecting to the discussion when failing to open the document. bzr revid: tde@openerp.com-20130429093329-w0qxiiipx1mkz6qt --- addons/mail/mail_mail.py | 11 ++------ addons/mail/mail_thread.py | 32 ++++++++++++---------- addons/portal/security/ir.model.access.csv | 2 +- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py index 6f02c31ecf6..9083c940027 100644 --- a/addons/mail/mail_mail.py +++ b/addons/mail/mail_mail.py @@ -198,14 +198,9 @@ class mail_mail(osv.Model): # mail_mail formatting, tools and send mechanism #------------------------------------------------------ - # TDE NOTE: as for 7.0 link generation is located in mail.mail because it grew - # based on a simple URL generation that moved towards something more complicated - # Moreover a bug in hierarchy generation prevents from putting link generation - # inside mail_thread and override it in modules; the overrides will not be - # taken into account - # TODO in 8.0: maybe factorize this to enable in modules link generation + # TODO in 8.0(+): maybe factorize this to enable in modules link generation # independently of mail_mail model - # TODO in 8.0: factorize doc name sanitized and 'Followers of ...' formatting + # TODO in 8.0(+): factorize doc name sanitized and 'Followers of ...' formatting # because it begins to appear everywhere def _get_partner_access_link(self, cr, uid, mail, partner=None, context=None): @@ -225,7 +220,7 @@ class mail_mail(osv.Model): 'message_id': mail.mail_message_id.id, }) url = urljoin(base_url, "?%s#%s" % (urlencode(query), urlencode(fragment))) - return _("""Access this document directly in OpenERP""") % url + return _("""Access your messages and documents in OpenERP""") % url else: return None diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 4af604891ac..1164e4ea273 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -494,21 +494,23 @@ class mail_thread(osv.AbstractModel): if not msg_id: return action msg = self.pool.get('mail.message').browse(cr, uid, msg_id, context=context) - if msg.model and msg.res_id and self.pool.get(msg.model).check_access_rights(cr, uid, 'read', raise_exception=False): - try: - model_obj = self.pool.get(msg.model) - model_obj.check_access_rule(cr, uid, [msg.res_id], 'read', context=context) - if not hasattr(model_obj, '_get_formview_action'): - action = self.pool.get('mail.thread')._get_formview_action(cr, uid, msg.res_id, model=msg.model, context=context) - else: - action = model_obj._get_formview_action(cr, uid, msg.res_id, context=context) - except (osv.except_osv, orm.except_orm): - action.update({ - 'context': { - 'search_default_model': msg.model, - 'search_default_res_id': msg.res_id, - } - }) + if msg.model and msg.res_id: + action.update({ + 'context': { + 'search_default_model': msg.model, + 'search_default_res_id': msg.res_id, + } + }) + if self.pool.get(msg.model).check_access_rights(cr, uid, 'read', raise_exception=False): + try: + model_obj = self.pool.get(msg.model) + model_obj.check_access_rule(cr, uid, [msg.res_id], 'read', context=context) + if not hasattr(model_obj, '_get_formview_action'): + action = self.pool.get('mail.thread')._get_formview_action(cr, uid, msg.res_id, model=msg.model, context=context) + else: + action = model_obj._get_formview_action(cr, uid, msg.res_id, context=context) + except (osv.except_osv, orm.except_orm): + pass return action #------------------------------------------------------ diff --git a/addons/portal/security/ir.model.access.csv b/addons/portal/security/ir.model.access.csv index 31bd7332c2f..26b0b4895e8 100644 --- a/addons/portal/security/ir.model.access.csv +++ b/addons/portal/security/ir.model.access.csv @@ -3,7 +3,7 @@ access_mail_message_portal,mail.message.portal,mail.model_mail_message,group_por access_mail_mail_portal,mail.mail.portal,mail.model_mail_mail,group_portal,1,1,1,0 access_mail_notification_portal,mail.notification.portal,mail.model_mail_notification,group_portal,1,1,1,0 access_mail_followers_portal,mail.followers.portal,mail.model_mail_followers,group_portal,1,1,0,0 -access_res_partner,res.partner,base.model_res_partner,portal.group_portal,1,0,0,0 +access_res_partner_portal,res.partner.portal,base.model_res_partner,portal.group_portal,1,0,0,0 access_acquirer,portal.payment.acquirer,portal.model_portal_payment_acquirer,,1,0,0,0 access_acquirer_all,portal.payment.acquirer,portal.model_portal_payment_acquirer,base.group_system,1,1,1,1 access_ir_attachment_group_portal,ir.attachment group_portal,base.model_ir_attachment,portal.group_portal,1,0,1,0 \ No newline at end of file From c1cb8bf7653d98153c43e0ee35eb3558438e0b78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 21 May 2013 15:29:24 +0200 Subject: [PATCH 7/8] [CLEAN] Removed debug code. bzr revid: tde@openerp.com-20130521132924-cyb9y03tbkgvv6fl --- addons/mail/static/src/js/mail.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js index 6f1e75d5136..5b5ae4fd26a 100644 --- a/addons/mail/static/src/js/mail.js +++ b/addons/mail/static/src/js/mail.js @@ -1841,7 +1841,6 @@ openerp.mail = function (session) { this._super(parent, action); this.action = _.clone(action); - // debugger this.domain = this.action.params.domain || this.action.domain || []; this.context = _.extend(this.action.params.context || {}, this.action.context || {}); @@ -1852,7 +1851,6 @@ openerp.mail = function (session) { continue; } this.context['search_default_' + key] = this.action.context.params[key]; - console.log(this.context); } for (var key in this.context) { if (key.match(/^search_default_/)) { From 6b572b8d9089403a7ea4c54ff31a72dccfd78a8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 22 May 2013 13:55:49 +0200 Subject: [PATCH 8/8] [CLEAN] portal/mail_thread: cleaned a bit new code, using is_portal. bzr revid: tde@openerp.com-20130522115549-ot3f31c5oty0zqya --- addons/portal/mail_thread.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/addons/portal/mail_thread.py b/addons/portal/mail_thread.py index 54fb0d26279..44bc4d0bde8 100644 --- a/addons/portal/mail_thread.py +++ b/addons/portal/mail_thread.py @@ -35,10 +35,9 @@ class mail_thread(osv.AbstractModel): res_id - opens the Inbox with context propagated """ - # if uid is a portal user -> action is different - portal_model, group_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'portal', 'group_portal') cur_user = self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context=context) - if group_id in [g.id for g in cur_user.groups_id]: + # if uid is a portal user -> action is different + if any(group.is_portal for group in cur_user.groups_id): return ('portal', 'action_mail_inbox_feeds_portal') else: return super(mail_thread, self)._get_inbox_action_xml_id(cr, uid, context=context)