[IMP] mail: separate management of mail routes from redirection

Move code related to redirection in its own class method. This way all
controllers perform their own actions and checks before calling the
redirection itself. Moreover it is necessary for some controllers to be
able to call the redirection independently outside of the mail/view
controller scope.

This commit also fixes some links generated for mail notification email
buttons. Assign and (un)follow buttons now effectively use the link helper
when generating the associated URL instead of manually crafting it.

Custom routes for notification buttons used in HR and CRM are added.
Indeed in order to be able to fine-tune the behavior called by those
buttons, using dedicated controllers is simpler than trying to use
a generic one. As there are few custom buttons in notification emails
using dedicated controllers does not lead to a code overhead.
This commit is contained in:
Thibault Delavallée
2016-10-03 19:03:50 +02:00
committed by Olivier Dony
parent 0ea7842c5e
commit 0b87f76628
10 changed files with 211 additions and 119 deletions
+1
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import controllers
import res_users
import crm_stage
import crm_lead
+2
View File
@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*
import main
+38
View File
@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.addons.mail.controllers.main import MailController
from openerp import http
class CrmController(http.Controller):
@http.route('/lead/case_mark_won', type='http', auth='user', methods=['GET'])
def crm_lead_case_mark_won(self, res_id, token):
comparison, record, redirect = MailController._check_token_and_record_or_redirect('crm.lead', int(res_id), token)
if comparison and record:
try:
record.case_mark_won()
except Exception:
return MailController._redirect_to_messaging()
return redirect
@http.route('/lead/case_mark_lost', type='http', auth='user', methods=['GET'])
def crm_lead_case_mark_lost(self, res_id, token):
comparison, record, redirect = MailController._check_token_and_record_or_redirect('crm.lead', int(res_id), token)
if comparison and record:
try:
record.case_mark_lost()
except Exception:
return MailController._redirect_to_messaging()
return redirect
@http.route('/lead/convert', type='http', auth='user', methods=['GET'])
def crm_lead_convert(self, res_id, token):
comparison, record, redirect = MailController._check_token_and_record_or_redirect('crm.lead', int(res_id), token)
if comparison and record:
try:
record.convert_opportunity(record.partner_id.id)
except Exception:
return MailController._redirect_to_messaging()
return redirect
+3 -3
View File
@@ -976,9 +976,9 @@ Update your business card, phone book, social media,... Send an email right now
lead = self.browse(cr, uid, ids[0], context=context)
won_action = self._notification_link_helper(cr, uid, ids, 'method', context=context, method='case_mark_won')
lost_action = self._notification_link_helper(cr, uid, ids, 'method', context=context, method='case_mark_lost')
convert_action = self._notification_link_helper(cr, uid, ids, 'method', context=context, method='convert_opportunity', partner_id=lead.partner_id.id)
won_action = self._notification_link_helper(cr, uid, ids, 'controller', controller='/lead/case_mark_won', context=context)
lost_action = self._notification_link_helper(cr, uid, ids, 'controller', controller='/lead/case_mark_lost', context=context)
convert_action = self._notification_link_helper(cr, uid, ids, 'controller', controller='/lead/convert', context=context)
if lead.type == 'lead':
res['group_sale_salesman'] = {
+1
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import controllers
import hr_department
import hr_holidays
import report
@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*
import main
+28
View File
@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.addons.mail.controllers.main import MailController
from openerp import http
class HrHolidaysController(http.Controller):
@http.route('/hr_holidays/validate', type='http', auth='user', methods=['GET'])
def hr_holidays_validate(self, res_id, token):
comparison, record, redirect = MailController._check_token_and_record_or_redirect('hr.holidays', int(res_id), token)
if comparison and record:
try:
record.signal_workflow('validate')
except Exception:
return MailController._redirect_to_messaging()
return redirect
@http.route('/hr_holidays/refuse', type='http', auth='user', methods=['GET'])
def hr_holidays_refuse(self, res_id, token):
comparison, record, redirect = MailController._check_token_and_record_or_redirect('hr.holidays', int(res_id), token)
if comparison and record:
try:
record.signal_workflow('refuse')
except Exception:
return MailController._redirect_to_messaging()
return redirect
+2 -2
View File
@@ -523,8 +523,8 @@ class hr_holidays(osv.osv):
def _notification_get_recipient_groups(self, cr, uid, ids, message, recipients, context=None):
res = super(hr_holidays, self)._notification_get_recipient_groups(cr, uid, ids, message, recipients, context=context)
app_action = '/mail/workflow?%s' % url_encode({'model': self._name, 'res_id': ids[0], 'signal': 'validate'})
ref_action = '/mail/workflow?%s' % url_encode({'model': self._name, 'res_id': ids[0], 'signal': 'refuse'})
app_action = self._notification_link_helper(cr, uid, ids, 'controller', controller='/hr_holidays/validate', context=context)
ref_action = self._notification_link_helper(cr, uid, ids, 'controller', controller='/hr_holidays/refuse', context=context)
holiday = self.browse(cr, uid, ids[0], context=context)
actions = []
+105 -98
View File
@@ -1,5 +1,6 @@
import base64
import json
import logging
from operator import itemgetter
import psycopg2
import werkzeug
@@ -8,20 +9,90 @@ from werkzeug import url_encode
import openerp
from openerp import SUPERUSER_ID
from openerp import http
from openerp import _
from openerp.exceptions import AccessError
from openerp.http import request
from openerp.tools import consteq
from openerp.addons.web.controllers.main import binary_content
_logger = logging.getLogger(__name__)
class MailController(http.Controller):
_cp_path = '/mail'
def _redirect_to_messaging(self):
@classmethod
def _redirect_to_messaging(cls):
messaging_action = request.env['mail.thread']._get_inbox_action_xml_id()
url = '/web#%s' % url_encode({'action': messaging_action})
return werkzeug.utils.redirect(url)
@classmethod
def _check_token(cls, token):
base_link = request.httprequest.path
params = dict(request.params)
params.pop('token', '')
valid_token = request.env['mail.thread']._generate_notification_token(base_link, params)
return consteq(valid_token, str(token))
@classmethod
def _check_token_and_record_or_redirect(cls, model, res_id, token):
comparison = cls._check_token(token)
if not comparison:
_logger.warning(_('Invalid token in route %s') % request.httprequest.url)
return comparison, None, cls._redirect_to_messaging()
try:
record = request.env[model].browse(res_id).exists()
except Exception:
record = None
redirect = cls._redirect_to_messaging()
else:
redirect = cls._redirect_to_record(model, res_id)
return comparison, record, redirect
@classmethod
def _redirect_to_record(cls, model, res_id):
uid = request.session.uid
# no model / res_id, meaning no possible record -> redirect to login
if not model or not res_id or model not in request.env:
return cls._redirect_to_messaging()
# find the access action using sudo to have the details about the access link
RecordModel = request.env[model]
record_sudo = RecordModel.sudo().browse(res_id).exists()
if not record_sudo:
# record does not seem to exist -> redirect to login
return cls._redirect_to_messaging()
record_action = record_sudo.get_access_action()
# the record has an URL redirection: use it directly
if record_action['type'] == 'ir.actions.act_url':
return werkzeug.utils.redirect(record_action['url'])
# other choice: act_window (no support of anything else currently)
elif not record_action['type'] == 'ir.actions.act_window':
return cls._redirect_to_messaging()
# the record has a window redirection: check access rights
if not RecordModel.sudo(uid).check_access_rights('read', raise_exception=False):
return cls._redirect_to_messaging()
try:
record_sudo.sudo(uid).check_access_rule('read')
except AccessError:
return cls._redirect_to_messaging()
url_params = {
'view_type': record_action['view_type'],
'model': model,
'id': res_id,
'active_id': res_id,
'view_id': record_sudo.get_formview_id(),
'action': record_action.get('id'),
}
url = '/web?#%s' % url_encode(url_params)
return werkzeug.utils.redirect(url)
@http.route('/mail/receive', type='json', auth='none')
def receive(self, req):
""" End-point to receive mail from an external SMTP server. """
@@ -95,8 +166,6 @@ class MailController(http.Controller):
- users without read access are redirected to the Messaging
- not logged users are redirected to the login page
"""
uid = request.session.uid
if message_id:
try:
message = request.env['mail.message'].sudo().browse(int(message_id)).exists()
@@ -110,110 +179,48 @@ class MailController(http.Controller):
elif res_id and isinstance(res_id, basestring):
res_id = int(res_id)
# no model / res_id, meaning no possible record -> redirect to login
if not model or not res_id or model not in request.env:
return self._redirect_to_messaging()
return self._redirect_to_record(model, res_id)
# find the access action using sudo to have the details about the access link
RecordModel = request.env[model]
record_sudo = RecordModel.sudo().browse(res_id).exists()
if not record_sudo:
# record does not seem to exist -> redirect to login
return self._redirect_to_messaging()
record_action = record_sudo.get_access_action()
@http.route('/mail/follow', type='http', auth='user', methods=['GET'])
def mail_action_follow(self, model, res_id, token=None):
comparison, record, redirect = self._check_token_and_record_or_redirect(model, int(res_id), token)
if comparison and record:
try:
record.sudo().message_subscribe_users()
except Exception:
return self._redirect_to_messaging()
return redirect
# the record has an URL redirection: use it directly
if record_action['type'] == 'ir.actions.act_url':
return werkzeug.utils.redirect(record_action['url'])
# other choice: act_window (no support of anything else currently)
elif not record_action['type'] == 'ir.actions.act_window':
return self._redirect_to_messaging()
# the record has a window redirection: check access rights
if not RecordModel.sudo(uid).check_access_rights('read', raise_exception=False):
return self._redirect_to_messaging()
try:
RecordModel.sudo(uid).browse(res_id).exists().check_access_rule('read')
except AccessError:
return self._redirect_to_messaging()
query = {}
url_params = {
'view_type': record_action['view_type'],
'model': model,
'id': res_id,
'active_id': res_id,
'view_id': record_sudo.get_formview_id(),
'action': record_action.get('id'),
}
url = '/web?%s#%s' % (url_encode(query), url_encode(url_params))
return werkzeug.utils.redirect(url)
@http.route('/mail/follow', type='http', auth='user')
def mail_action_follow(self, model, res_id):
if model not in request.env:
return self._redirect_to_messaging()
Model = request.env[model]
try:
Model.browse(res_id).message_subscribe_users()
except:
return self._redirect_to_messaging()
return werkzeug.utils.redirect('/mail/view?%s' % url_encode({'model': model, 'res_id': res_id}))
@http.route('/mail/unfollow', type='http', auth='user')
def mail_action_unfollow(self, model, res_id):
if model not in request.env:
return self._redirect_to_messaging()
Model = request.env[model]
try:
Model.browse(res_id).sudo().message_unsubscribe_users([request.uid])
except:
return self._redirect_to_messaging()
return werkzeug.utils.redirect('/mail/view?%s' % url_encode({'model': model, 'res_id': res_id}))
@http.route('/mail/unfollow', type='http', auth='user', methods=['GET'])
def mail_action_unfollow(self, model, res_id, token=None):
comparison, record, redirect = self._check_token_and_record_or_redirect(model, int(res_id), token)
if comparison and record:
try:
# TDE CHECKME: is sudo really necessary ?
record.sudo().message_unsubscribe_users([request.uid])
except Exception:
return self._redirect_to_messaging()
return redirect
@http.route('/mail/new', type='http', auth='user')
def mail_action_new(self, model, res_id, **kwargs):
def mail_action_new(self, model, res_id, action_id):
if model not in request.env:
return self._redirect_to_messaging()
params = {'view_type': 'form', 'model': model}
if kwargs.get('action_id'):
params['action'] = kwargs['action_id']
if action_id:
# Probably something to do
params['action'] = action_id
return werkzeug.utils.redirect('/web?#%s' % url_encode(params))
@http.route('/mail/method', type='http', auth='user')
def mail_action_method(self, model, res_id, method, **kwargs):
# only public methods / check exists
if method.strip().startswith('_') or model not in request.env:
return self._redirect_to_messaging()
Model = request.env[model]
try:
record = Model.browse(int(res_id)).exists()
getattr(record, method)(**json.loads(kwargs.get('params', {})))
except:
return self._redirect_to_messaging()
return werkzeug.utils.redirect('/mail/view?%s' % url_encode({'model': model, 'res_id': res_id}))
@http.route('/mail/assign', type='http', auth='user')
def mail_action_assign(self, model, res_id, **kwargs):
if model not in request.env:
return self._redirect_to_messaging()
Model = request.env[model]
try:
Model.browse(int(res_id)).exists().write({'user_id': request.uid})
except:
return self._redirect_to_messaging()
return werkzeug.utils.redirect('/mail/view?%s' % url_encode({'model': model, 'res_id': res_id}))
@http.route('/mail/workflow', type='http', auth='user')
def mail_action_workflow(self, model, res_id, signal, **kwargs):
if model not in request.env:
return self._redirect_to_messaging()
Model = request.env[model]
try:
Model.browse(int(res_id)).exists().signal_workflow(signal)
except:
return self._redirect_to_messaging()
return werkzeug.utils.redirect('/mail/view?%s' % url_encode({'model': model, 'res_id': res_id}))
@http.route('/mail/assign', type='http', auth='user', methods=['GET'])
def mail_action_assign(self, model, res_id, token=None):
comparison, record, redirect = self._check_token_and_record_or_redirect(model, int(res_id), token)
if comparison and record:
try:
record.write({'user_id': request.uid})
except Exception:
return self._redirect_to_messaging()
return redirect
@http.route('/mail/<string:res_model>/<int:res_id>/avatar/<int:partner_id>', type='http', auth='public')
def avatar(self, res_model, res_id, partner_id):
+29 -16
View File
@@ -4,6 +4,8 @@ import base64
import datetime
import dateutil
import email
import hashlib
import hmac
import json
import lxml
from lxml import etree
@@ -539,6 +541,13 @@ class MailThread(models.AbstractModel):
because portal users have a different Inbox action than classic users. """
return 'mail.mail_channel_action_client_chat'
@api.model
def _generate_notification_token(self, base_link, params):
secret = self.env['ir.config_parameter'].sudo().get_param('database.secret')
token = '%s?%s' % (base_link, ' '.join('%s=%s' % (key, params[key]) for key in sorted(params.keys())))
hm = hmac.new(str(secret), token, hashlib.sha1).hexdigest()
return hm
@api.multi
def _notification_link_helper(self, link_type, **kwargs):
if kwargs.get('message_id'):
@@ -546,26 +555,30 @@ class MailThread(models.AbstractModel):
'message_id': kwargs.pop('message_id')
}
else:
self.ensure_one()
base_params = {
'model': self._name,
'res_id': self.ids[0],
'model': kwargs.get('model', self._name),
'res_id': kwargs.get('res_id', self.ids and self.ids[0] or False),
}
link = False
if link_type in ['view', 'assign', 'follow', 'unfollow']:
params = dict(base_params)
link = '/mail/view?%s' % url_encode(params)
elif link_type == 'workflow':
params = dict(base_params, signal=kwargs['signal'])
link = '/mail/workflow?%s' % url_encode(params)
elif link_type == 'method':
method = kwargs.pop('method')
params = dict(base_params, method=method, params=json.dumps(kwargs))
link = '/mail/method?%s' % url_encode(params)
base_link = '/mail/%s' % link_type
elif link_type == 'new':
params = dict(base_params, action_id=kwargs.get('action_id'))
link = '/mail/new?%s' % url_encode(params)
params = dict(base_params, action_id=kwargs.get('action_id', ''))
base_link = '/mail/new'
elif link_type == 'controller':
controller = kwargs.pop('controller')
params = dict(base_params)
params.pop('model')
base_link = '%s' % controller
else:
return ''
if link_type not in ['view', 'new']:
token = self._generate_notification_token(base_link, params)
params['token'] = token
link = '%s?%s' % (base_link, url_encode(params))
return link
@api.multi
@@ -639,8 +652,8 @@ class MailThread(models.AbstractModel):
'followers': self.env['res.partner'],
'not_followers': self.env['res.partner'],
'button_access': {'url': access_link, 'title': view_title},
'button_follow': {'url': '/mail/follow?%s' % url_encode({'model': message.model, 'res_id': message.res_id}), 'title': _('Follow')},
'button_unfollow': {'url': '/mail/unfollow?%s' % url_encode({'model': message.model, 'res_id': message.res_id}), 'title': _('Unfollow')},
'button_follow': {'url': self._notification_link_helper('follow', model=message.model, res_id=message.res_id), 'title': _('Follow')},
'button_unfollow': {'url': self._notification_link_helper('unfollow', model=message.model, res_id=message.res_id), 'title': _('Unfollow')},
'actions': list(),
}
group_data[category] = self.env['res.partner']