mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-21 09:32:08 +02:00
Initial commit: Core packages
This commit is contained in:
commit
12c29a983b
9512 changed files with 8379910 additions and 0 deletions
7
odoo-bringout-oca-ocb-mail/mail/controllers/__init__.py
Normal file
7
odoo-bringout-oca-ocb-mail/mail/controllers/__init__.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# -*- coding: utf-8 -*
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import bus
|
||||
from . import discuss
|
||||
from . import home
|
||||
from . import mail
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
52
odoo-bringout-oca-ocb-mail/mail/controllers/bus.py
Normal file
52
odoo-bringout-oca-ocb-mail/mail/controllers/bus.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import SUPERUSER_ID, tools
|
||||
from odoo.http import request, route
|
||||
from odoo.addons.bus.controllers.main import BusController
|
||||
|
||||
|
||||
class MailChatController(BusController):
|
||||
|
||||
def _default_request_uid(self):
|
||||
""" For Anonymous people, they receive the access right of SUPERUSER_ID since they have NO access (auth=none)
|
||||
!!! Each time a method from this controller is call, there is a check if the user (who can be anonymous and Sudo access)
|
||||
can access to the resource.
|
||||
"""
|
||||
return request.session.uid and request.session.uid or SUPERUSER_ID
|
||||
|
||||
# --------------------------
|
||||
# Anonymous routes (Common Methods)
|
||||
# --------------------------
|
||||
@route('/mail/chat_post', type="json", auth="public", cors="*")
|
||||
def mail_chat_post(self, uuid, message_content, **kwargs):
|
||||
mail_channel = request.env["mail.channel"].sudo().search([('uuid', '=', uuid)], limit=1)
|
||||
if not mail_channel:
|
||||
return False
|
||||
|
||||
# find the author from the user session
|
||||
if request.session.uid:
|
||||
author = request.env['res.users'].sudo().browse(request.session.uid).partner_id
|
||||
author_id = author.id
|
||||
email_from = author.email_formatted
|
||||
else: # If Public User, use catchall email from company
|
||||
author_id = False
|
||||
email_from = mail_channel.anonymous_name or mail_channel.create_uid.company_id.catchall_formatted
|
||||
# post a message without adding followers to the channel. email_from=False avoid to get author from email data
|
||||
body = tools.plaintext2html(message_content)
|
||||
message = mail_channel.with_context(mail_create_nosubscribe=True).message_post(
|
||||
author_id=author_id,
|
||||
email_from=email_from,
|
||||
body=body,
|
||||
message_type='comment',
|
||||
subtype_xmlid='mail.mt_comment'
|
||||
)
|
||||
return message.id if message else False
|
||||
|
||||
@route(['/mail/chat_history'], type="json", auth="public", cors="*")
|
||||
def mail_chat_history(self, uuid, last_id=False, limit=20):
|
||||
channel = request.env["mail.channel"].sudo().search([('uuid', '=', uuid)], limit=1)
|
||||
if not channel:
|
||||
return []
|
||||
else:
|
||||
return channel._channel_fetch_message(last_id, limit)
|
||||
609
odoo-bringout-oca-ocb-mail/mail/controllers/discuss.py
Normal file
609
odoo-bringout-oca-ocb-mail/mail/controllers/discuss.py
Normal file
|
|
@ -0,0 +1,609 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timedelta
|
||||
from psycopg2 import IntegrityError
|
||||
from psycopg2.errorcodes import UNIQUE_VIOLATION
|
||||
|
||||
from odoo import http
|
||||
from odoo.exceptions import AccessError, UserError
|
||||
from odoo.http import request
|
||||
from odoo.tools import consteq, file_open
|
||||
from odoo.tools.misc import get_lang
|
||||
from odoo.tools.translate import _
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
|
||||
class DiscussController(http.Controller):
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Public Pages
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
@http.route([
|
||||
'/chat/<string:create_token>',
|
||||
'/chat/<string:create_token>/<string:channel_name>',
|
||||
], methods=['GET'], type='http', auth='public')
|
||||
def discuss_channel_chat_from_token(self, create_token, channel_name=None, **kwargs):
|
||||
return self._response_discuss_channel_from_token(create_token=create_token, channel_name=channel_name)
|
||||
|
||||
@http.route([
|
||||
'/meet/<string:create_token>',
|
||||
'/meet/<string:create_token>/<string:channel_name>',
|
||||
], methods=['GET'], type='http', auth='public')
|
||||
def discuss_channel_meet_from_token(self, create_token, channel_name=None, **kwargs):
|
||||
return self._response_discuss_channel_from_token(create_token=create_token, channel_name=channel_name, default_display_mode='video_full_screen')
|
||||
|
||||
@http.route('/chat/<int:channel_id>/<string:invitation_token>', methods=['GET'], type='http', auth='public')
|
||||
def discuss_channel_invitation(self, channel_id, invitation_token, **kwargs):
|
||||
channel_sudo = request.env['mail.channel'].browse(channel_id).sudo().exists()
|
||||
if not channel_sudo or not channel_sudo.uuid or not consteq(channel_sudo.uuid, invitation_token):
|
||||
raise NotFound()
|
||||
return self._response_discuss_channel_invitation(channel_sudo=channel_sudo)
|
||||
|
||||
@http.route('/discuss/channel/<int:channel_id>', methods=['GET'], type='http', auth='public')
|
||||
def discuss_channel(self, channel_id, **kwargs):
|
||||
channel_member_sudo = request.env['mail.channel.member']._get_as_sudo_from_request_or_raise(request=request, channel_id=int(channel_id))
|
||||
return self._response_discuss_public_channel_template(channel_sudo=channel_member_sudo.channel_id)
|
||||
|
||||
def _response_discuss_channel_from_token(self, create_token, channel_name=None, default_display_mode=False):
|
||||
if not request.env['ir.config_parameter'].sudo().get_param('mail.chat_from_token'):
|
||||
raise NotFound()
|
||||
channel_sudo = request.env['mail.channel'].sudo().search([('uuid', '=', create_token)])
|
||||
if not channel_sudo:
|
||||
try:
|
||||
channel_sudo = channel_sudo.create({
|
||||
'channel_type': 'channel',
|
||||
'default_display_mode': default_display_mode,
|
||||
'group_public_id': None,
|
||||
'name': channel_name or create_token,
|
||||
'uuid': create_token,
|
||||
})
|
||||
except IntegrityError as e:
|
||||
if e.pgcode != UNIQUE_VIOLATION:
|
||||
raise
|
||||
# concurrent insert attempt: another request created the channel.
|
||||
# commit the current transaction and get the channel.
|
||||
request.env.cr.commit()
|
||||
channel_sudo = channel_sudo.search([('uuid', '=', create_token)])
|
||||
return self._response_discuss_channel_invitation(channel_sudo=channel_sudo, is_channel_token_secret=False)
|
||||
|
||||
def _response_discuss_channel_invitation(self, channel_sudo, is_channel_token_secret=True):
|
||||
if channel_sudo.channel_type == 'chat':
|
||||
raise NotFound()
|
||||
discuss_public_view_data = {
|
||||
'isChannelTokenSecret': is_channel_token_secret,
|
||||
}
|
||||
add_guest_cookie = False
|
||||
channel_member_sudo = channel_sudo.env['mail.channel.member']._get_as_sudo_from_request(request=request, channel_id=channel_sudo.id)
|
||||
if channel_member_sudo:
|
||||
channel_sudo = channel_member_sudo.channel_id # ensure guest is in context
|
||||
else:
|
||||
if not channel_sudo.env.user._is_public():
|
||||
try:
|
||||
channel_sudo.add_members([channel_sudo.env.user.partner_id.id])
|
||||
except UserError:
|
||||
raise NotFound()
|
||||
else:
|
||||
guest = channel_sudo.env['mail.guest']._get_guest_from_request(request)
|
||||
if guest:
|
||||
channel_sudo = channel_sudo.with_context(guest=guest)
|
||||
try:
|
||||
channel_sudo.add_members(guest_ids=[guest.id])
|
||||
except UserError:
|
||||
raise NotFound()
|
||||
else:
|
||||
if channel_sudo.group_public_id:
|
||||
raise NotFound()
|
||||
guest = channel_sudo.env['mail.guest'].create({
|
||||
'country_id': channel_sudo.env['res.country'].search([('code', '=', request.geoip.get('country_code'))], limit=1).id,
|
||||
'lang': get_lang(channel_sudo.env).code,
|
||||
'name': _("Guest"),
|
||||
'timezone': channel_sudo.env['mail.guest']._get_timezone_from_request(request),
|
||||
})
|
||||
add_guest_cookie = True
|
||||
discuss_public_view_data.update({
|
||||
'shouldAddGuestAsMemberOnJoin': True,
|
||||
'shouldDisplayWelcomeViewInitially': True,
|
||||
})
|
||||
channel_sudo = channel_sudo.with_context(guest=guest)
|
||||
response = self._response_discuss_public_channel_template(channel_sudo=channel_sudo, discuss_public_view_data=discuss_public_view_data)
|
||||
if add_guest_cookie:
|
||||
# Discuss Guest ID: every route in this file will make use of it to authenticate
|
||||
# the guest through `_get_as_sudo_from_request` or `_get_as_sudo_from_request_or_raise`.
|
||||
expiration_date = datetime.now() + timedelta(days=365)
|
||||
response.set_cookie(guest._cookie_name, f"{guest.id}{guest._cookie_separator}{guest.access_token}", httponly=True, expires=expiration_date)
|
||||
return response
|
||||
|
||||
def _response_discuss_public_channel_template(self, channel_sudo, discuss_public_view_data=None):
|
||||
discuss_public_view_data = discuss_public_view_data or {}
|
||||
return request.render('mail.discuss_public_channel_template', {
|
||||
'data': {
|
||||
'channelData': channel_sudo.channel_info()[0],
|
||||
'discussPublicViewData': dict({
|
||||
'channel': [('insert', {'id': channel_sudo.id, 'model': 'mail.channel'})],
|
||||
'shouldDisplayWelcomeViewInitially': channel_sudo.default_display_mode == 'video_full_screen',
|
||||
}, **discuss_public_view_data),
|
||||
},
|
||||
'session_info': channel_sudo.env['ir.http'].session_info(),
|
||||
})
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Semi-Static Content (GET requests with possible cache)
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
@http.route('/mail/channel/<int:channel_id>/partner/<int:partner_id>/avatar_128', methods=['GET'], type='http', auth='public')
|
||||
def mail_channel_partner_avatar_128(self, channel_id, partner_id, **kwargs):
|
||||
channel_member_sudo = request.env['mail.channel.member']._get_as_sudo_from_request(request=request, channel_id=channel_id)
|
||||
partner_sudo = channel_member_sudo.env['res.partner'].browse(partner_id).exists()
|
||||
placeholder = partner_sudo._avatar_get_placeholder_path()
|
||||
if channel_member_sudo and channel_member_sudo.env['mail.channel.member'].search([('channel_id', '=', channel_id), ('partner_id', '=', partner_id)], limit=1):
|
||||
return request.env['ir.binary']._get_image_stream_from(partner_sudo, field_name='avatar_128', placeholder=placeholder).get_response()
|
||||
if request.env.user.share:
|
||||
return request.env['ir.binary']._get_placeholder_stream(placeholder).get_response()
|
||||
return request.env['ir.binary']._get_image_stream_from(partner_sudo.sudo(False), field_name='avatar_128', placeholder=placeholder).get_response()
|
||||
|
||||
@http.route('/mail/channel/<int:channel_id>/guest/<int:guest_id>/avatar_128', methods=['GET'], type='http', auth='public')
|
||||
def mail_channel_guest_avatar_128(self, channel_id, guest_id, **kwargs):
|
||||
channel_member_sudo = request.env['mail.channel.member']._get_as_sudo_from_request(request=request, channel_id=channel_id)
|
||||
guest_sudo = channel_member_sudo.env['mail.guest'].browse(guest_id).exists()
|
||||
placeholder = guest_sudo._avatar_get_placeholder_path()
|
||||
if channel_member_sudo and channel_member_sudo.env['mail.channel.member'].search([('channel_id', '=', channel_id), ('guest_id', '=', guest_id)], limit=1):
|
||||
return request.env['ir.binary']._get_image_stream_from(guest_sudo, field_name='avatar_128', placeholder=placeholder).get_response()
|
||||
if request.env.user.share:
|
||||
return request.env['ir.binary']._get_placeholder_stream(placeholder).get_response()
|
||||
return request.env['ir.binary']._get_image_stream_from(guest_sudo.sudo(False), field_name='avatar_128', placeholder=placeholder).get_response()
|
||||
|
||||
@http.route('/mail/channel/<int:channel_id>/attachment/<int:attachment_id>', methods=['GET'], type='http', auth='public')
|
||||
def mail_channel_attachment(self, channel_id, attachment_id, download=None, **kwargs):
|
||||
channel_member_sudo = request.env['mail.channel.member']._get_as_sudo_from_request_or_raise(request=request, channel_id=int(channel_id))
|
||||
attachment_sudo = channel_member_sudo.env['ir.attachment'].search([
|
||||
('id', '=', int(attachment_id)),
|
||||
('res_id', '=', int(channel_id)),
|
||||
('res_model', '=', 'mail.channel')
|
||||
], limit=1)
|
||||
if not attachment_sudo:
|
||||
raise NotFound()
|
||||
return request.env['ir.binary']._get_stream_from(attachment_sudo).get_response(as_attachment=download)
|
||||
|
||||
@http.route([
|
||||
'/mail/channel/<int:channel_id>/image/<int:attachment_id>',
|
||||
'/mail/channel/<int:channel_id>/image/<int:attachment_id>/<int:width>x<int:height>',
|
||||
], methods=['GET'], type='http', auth='public')
|
||||
def fetch_image(self, channel_id, attachment_id, width=0, height=0, **kwargs):
|
||||
channel_member_sudo = request.env['mail.channel.member']._get_as_sudo_from_request_or_raise(request=request, channel_id=int(channel_id))
|
||||
attachment_sudo = channel_member_sudo.env['ir.attachment'].search([
|
||||
('id', '=', int(attachment_id)),
|
||||
('res_id', '=', int(channel_id)),
|
||||
('res_model', '=', 'mail.channel'),
|
||||
], limit=1)
|
||||
|
||||
if not attachment_sudo:
|
||||
raise NotFound()
|
||||
|
||||
return request.env['ir.binary']._get_image_stream_from(
|
||||
attachment_sudo, width=int(width), height=int(height)
|
||||
).get_response(as_attachment=kwargs.get('download'))
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Client Initialization
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
@http.route('/mail/init_messaging', methods=['POST'], type='json', auth='public')
|
||||
def mail_init_messaging(self, **kwargs):
|
||||
if not request.env.user.sudo()._is_public():
|
||||
return request.env.user.sudo(request.env.user.has_group('base.group_portal'))._init_messaging()
|
||||
guest = request.env['mail.guest']._get_guest_from_request(request)
|
||||
if guest:
|
||||
return guest.sudo()._init_messaging()
|
||||
raise NotFound()
|
||||
|
||||
@http.route('/mail/load_message_failures', methods=['POST'], type='json', auth='user')
|
||||
def mail_load_message_failures(self, **kwargs):
|
||||
return request.env.user.partner_id._message_fetch_failed()
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Mailbox
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
@http.route('/mail/inbox/messages', methods=['POST'], type='json', auth='user')
|
||||
def discuss_inbox_messages(self, max_id=None, min_id=None, limit=30, **kwargs):
|
||||
return request.env['mail.message']._message_fetch(domain=[('needaction', '=', True)], max_id=max_id, min_id=min_id, limit=limit).message_format()
|
||||
|
||||
@http.route('/mail/history/messages', methods=['POST'], type='json', auth='user')
|
||||
def discuss_history_messages(self, max_id=None, min_id=None, limit=30, **kwargs):
|
||||
return request.env['mail.message']._message_fetch(domain=[('needaction', '=', False)], max_id=max_id, min_id=min_id, limit=limit).message_format()
|
||||
|
||||
@http.route('/mail/starred/messages', methods=['POST'], type='json', auth='user')
|
||||
def discuss_starred_messages(self, max_id=None, min_id=None, limit=30, **kwargs):
|
||||
return request.env['mail.message']._message_fetch(domain=[('starred_partner_ids', 'in', [request.env.user.partner_id.id])], max_id=max_id, min_id=min_id, limit=limit).message_format()
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Thread API (channel/chatter common)
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
def _get_allowed_message_post_params(self):
|
||||
return {'attachment_ids', 'body', 'message_type', 'partner_ids', 'subtype_xmlid', 'parent_id'}
|
||||
|
||||
@http.route('/mail/message/post', methods=['POST'], type='json', auth='public')
|
||||
def mail_message_post(self, thread_model, thread_id, post_data, **kwargs):
|
||||
guest = request.env['mail.guest']._get_guest_from_request(request)
|
||||
guest.env['ir.attachment'].browse(post_data.get('attachment_ids', []))._check_attachments_access(post_data.get('attachment_tokens'))
|
||||
if thread_model == 'mail.channel':
|
||||
channel_member_sudo = request.env['mail.channel.member']._get_as_sudo_from_request_or_raise(request=request, channel_id=int(thread_id))
|
||||
thread = channel_member_sudo.channel_id
|
||||
else:
|
||||
thread = request.env[thread_model].browse(int(thread_id)).exists()
|
||||
return thread.message_post(**{key: value for key, value in post_data.items() if key in self._get_allowed_message_post_params()}).message_format()[0]
|
||||
|
||||
@http.route('/mail/message/update_content', methods=['POST'], type='json', auth='public')
|
||||
def mail_message_update_content(self, message_id, body, attachment_ids, attachment_tokens=None, **kwargs):
|
||||
guest = request.env['mail.guest']._get_guest_from_request(request)
|
||||
guest.env['ir.attachment'].browse(attachment_ids)._check_attachments_access(attachment_tokens)
|
||||
message_sudo = guest.env['mail.message'].browse(message_id).sudo().exists()
|
||||
if not message_sudo.is_current_user_or_guest_author and not guest.env.user._is_admin():
|
||||
raise NotFound()
|
||||
if not message_sudo.model or not message_sudo.res_id:
|
||||
raise NotFound()
|
||||
guest.env[message_sudo.model].browse([message_sudo.res_id])._message_update_content(
|
||||
message_sudo,
|
||||
body,
|
||||
attachment_ids=attachment_ids
|
||||
)
|
||||
return {
|
||||
'id': message_sudo.id,
|
||||
'body': message_sudo.body,
|
||||
'attachments': message_sudo.attachment_ids.sorted()._attachment_format(),
|
||||
}
|
||||
|
||||
@http.route('/mail/attachment/upload', methods=['POST'], type='http', auth='public')
|
||||
def mail_attachment_upload(self, ufile, thread_id, thread_model, is_pending=False, **kwargs):
|
||||
channel_member = request.env['mail.channel.member']
|
||||
if thread_model == 'mail.channel':
|
||||
channel_member = request.env['mail.channel.member']._get_as_sudo_from_request_or_raise(request=request, channel_id=int(thread_id))
|
||||
vals = {
|
||||
'name': ufile.filename,
|
||||
'raw': ufile.read(),
|
||||
'res_id': int(thread_id),
|
||||
'res_model': thread_model,
|
||||
}
|
||||
if is_pending and is_pending != 'false':
|
||||
# Add this point, the message related to the uploaded file does
|
||||
# not exist yet, so we use those placeholder values instead.
|
||||
vals.update({
|
||||
'res_id': 0,
|
||||
'res_model': 'mail.compose.message',
|
||||
})
|
||||
if channel_member.env.user.share:
|
||||
# Only generate the access token if absolutely necessary (= not for internal user).
|
||||
vals['access_token'] = channel_member.env['ir.attachment']._generate_access_token()
|
||||
try:
|
||||
attachment = channel_member.env['ir.attachment'].create(vals)
|
||||
attachment._post_add_create()
|
||||
attachmentData = {
|
||||
'filename': ufile.filename,
|
||||
'id': attachment.id,
|
||||
'mimetype': attachment.mimetype,
|
||||
'name': attachment.name,
|
||||
'size': attachment.file_size
|
||||
}
|
||||
if attachment.access_token:
|
||||
attachmentData['accessToken'] = attachment.access_token
|
||||
except AccessError:
|
||||
attachmentData = {'error': _("You are not allowed to upload an attachment here.")}
|
||||
return request.make_json_response(attachmentData)
|
||||
|
||||
@http.route('/mail/attachment/delete', methods=['POST'], type='json', auth='public')
|
||||
def mail_attachment_delete(self, attachment_id, access_token=None, **kwargs):
|
||||
attachment_sudo = request.env['ir.attachment'].browse(int(attachment_id)).sudo().exists()
|
||||
if not attachment_sudo:
|
||||
target = request.env.user.partner_id
|
||||
request.env['bus.bus']._sendone(target, 'ir.attachment/delete', {'id': attachment_id})
|
||||
return
|
||||
if not request.env.user.share:
|
||||
# Check through standard access rights/rules for internal users.
|
||||
attachment_sudo.sudo(False)._delete_and_notify()
|
||||
return
|
||||
# For non-internal users 2 cases are supported:
|
||||
# - Either the attachment is linked to a message: verify the request is made by the author of the message (portal user or guest).
|
||||
# - Either a valid access token is given: also verify the message is pending (because unfortunately in portal a token is also provided to guest for viewing others' attachments).
|
||||
guest = request.env['mail.guest']._get_guest_from_request(request)
|
||||
message_sudo = guest.env['mail.message'].sudo().search([('attachment_ids', 'in', attachment_sudo.ids)], limit=1)
|
||||
if message_sudo:
|
||||
if not message_sudo.is_current_user_or_guest_author:
|
||||
raise NotFound()
|
||||
else:
|
||||
if not access_token or not attachment_sudo.access_token or not consteq(access_token, attachment_sudo.access_token):
|
||||
raise NotFound()
|
||||
if attachment_sudo.res_model != 'mail.compose.message' or attachment_sudo.res_id != 0:
|
||||
raise NotFound()
|
||||
attachment_sudo._delete_and_notify()
|
||||
|
||||
@http.route('/mail/message/add_reaction', methods=['POST'], type='json', auth='public')
|
||||
def mail_message_add_reaction(self, message_id, content):
|
||||
guest_sudo = request.env['mail.guest']._get_guest_from_request(request).sudo()
|
||||
message_sudo = guest_sudo.env['mail.message'].browse(int(message_id)).exists()
|
||||
if not message_sudo:
|
||||
raise NotFound()
|
||||
if request.env.user.sudo()._is_public():
|
||||
if not guest_sudo or not message_sudo.model == 'mail.channel' or message_sudo.res_id not in guest_sudo.channel_ids.ids:
|
||||
raise NotFound()
|
||||
message_sudo._message_add_reaction(content=content)
|
||||
guests = [('insert', {'id': guest_sudo.id})]
|
||||
partners = []
|
||||
else:
|
||||
message_sudo.sudo(False)._message_add_reaction(content=content)
|
||||
guests = []
|
||||
partners = [('insert', {'id': request.env.user.partner_id.id})]
|
||||
reactions = message_sudo.env['mail.message.reaction'].search([('message_id', '=', message_sudo.id), ('content', '=', content)])
|
||||
return {
|
||||
'id': message_sudo.id,
|
||||
'messageReactionGroups': [('insert' if len(reactions) > 0 else 'insert-and-unlink', {
|
||||
'content': content,
|
||||
'count': len(reactions),
|
||||
'guests': guests,
|
||||
'message': {'id', message_sudo.id},
|
||||
'partners': partners,
|
||||
})],
|
||||
}
|
||||
|
||||
@http.route('/mail/message/remove_reaction', methods=['POST'], type='json', auth='public')
|
||||
def mail_message_remove_reaction(self, message_id, content):
|
||||
guest_sudo = request.env['mail.guest']._get_guest_from_request(request).sudo()
|
||||
message_sudo = guest_sudo.env['mail.message'].browse(int(message_id)).exists()
|
||||
if not message_sudo:
|
||||
raise NotFound()
|
||||
if request.env.user.sudo()._is_public():
|
||||
if not guest_sudo or not message_sudo.model == 'mail.channel' or message_sudo.res_id not in guest_sudo.channel_ids.ids:
|
||||
raise NotFound()
|
||||
message_sudo._message_remove_reaction(content=content)
|
||||
guests = [('insert-and-unlink', {'id': guest_sudo.id})]
|
||||
partners = []
|
||||
else:
|
||||
message_sudo.sudo(False)._message_remove_reaction(content=content)
|
||||
guests = []
|
||||
partners = [('insert-and-unlink', {'id': request.env.user.partner_id.id})]
|
||||
reactions = message_sudo.env['mail.message.reaction'].search([('message_id', '=', message_sudo.id), ('content', '=', content)])
|
||||
return {
|
||||
'id': message_sudo.id,
|
||||
'messageReactionGroups': [('insert' if len(reactions) > 0 else 'insert-and-unlink', {
|
||||
'content': content,
|
||||
'count': len(reactions),
|
||||
'guests': guests,
|
||||
'message': {'id': message_sudo.id},
|
||||
'partners': partners,
|
||||
})],
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Channel API
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
@http.route('/mail/channel/add_guest_as_member', methods=['POST'], type='json', auth='public')
|
||||
def mail_channel_add_guest_as_member(self, channel_id, channel_uuid, **kwargs):
|
||||
channel_sudo = request.env['mail.channel'].browse(int(channel_id)).sudo().exists()
|
||||
if not channel_sudo or not channel_sudo.uuid or not consteq(channel_sudo.uuid, channel_uuid):
|
||||
raise NotFound()
|
||||
if channel_sudo.channel_type == 'chat':
|
||||
raise NotFound()
|
||||
guest = channel_sudo.env['mail.guest']._get_guest_from_request(request)
|
||||
# Only guests should take this route.
|
||||
if not guest:
|
||||
raise NotFound()
|
||||
channel_member = channel_sudo.env['mail.channel.member']._get_as_sudo_from_request(request=request, channel_id=channel_id)
|
||||
# Do not add the guest to channel members if they are already member.
|
||||
if not channel_member:
|
||||
channel_sudo = channel_sudo.with_context(guest=guest)
|
||||
try:
|
||||
channel_sudo.add_members(guest_ids=[guest.id])
|
||||
except UserError:
|
||||
raise NotFound()
|
||||
|
||||
@http.route('/mail/channel/messages', methods=['POST'], type='json', auth='public')
|
||||
def mail_channel_messages(self, channel_id, max_id=None, min_id=None, limit=30, **kwargs):
|
||||
channel_member_sudo = request.env['mail.channel.member']._get_as_sudo_from_request_or_raise(request=request, channel_id=int(channel_id))
|
||||
messages = channel_member_sudo.env['mail.message']._message_fetch(domain=[
|
||||
('res_id', '=', channel_id),
|
||||
('model', '=', 'mail.channel'),
|
||||
('message_type', '!=', 'user_notification'),
|
||||
], max_id=max_id, min_id=min_id, limit=limit)
|
||||
if not request.env.user._is_public():
|
||||
messages.set_message_done()
|
||||
return messages.message_format()
|
||||
|
||||
@http.route('/mail/channel/set_last_seen_message', methods=['POST'], type='json', auth='public')
|
||||
def mail_channel_mark_as_seen(self, channel_id, last_message_id, **kwargs):
|
||||
channel_member_sudo = request.env['mail.channel.member']._get_as_sudo_from_request_or_raise(request=request, channel_id=int(channel_id))
|
||||
return channel_member_sudo.channel_id._channel_seen(int(last_message_id))
|
||||
|
||||
@http.route('/mail/channel/notify_typing', methods=['POST'], type='json', auth='public')
|
||||
def mail_channel_notify_typing(self, channel_id, is_typing, **kwargs):
|
||||
channel_member_sudo = request.env['mail.channel.member']._get_as_sudo_from_request_or_raise(request=request, channel_id=int(channel_id))
|
||||
channel_member_sudo._notify_typing(is_typing)
|
||||
|
||||
@http.route('/mail/channel/ping', methods=['POST'], type='json', auth='public')
|
||||
def channel_ping(self, channel_id, rtc_session_id=None, check_rtc_session_ids=None):
|
||||
channel_member_sudo = request.env['mail.channel.member']._get_as_sudo_from_request_or_raise(request=request, channel_id=int(channel_id))
|
||||
if rtc_session_id:
|
||||
channel_member_sudo.channel_id.rtc_session_ids.filtered_domain([
|
||||
('id', '=', int(rtc_session_id)),
|
||||
('channel_member_id', '=', channel_member_sudo.id),
|
||||
]).write({}) # update write_date
|
||||
current_rtc_sessions, outdated_rtc_sessions = channel_member_sudo._rtc_sync_sessions(check_rtc_session_ids=check_rtc_session_ids)
|
||||
return {'rtcSessions': [
|
||||
('insert', [rtc_session_sudo._mail_rtc_session_format() for rtc_session_sudo in current_rtc_sessions]),
|
||||
('insert-and-unlink', [{'id': missing_rtc_session_sudo.id} for missing_rtc_session_sudo in outdated_rtc_sessions]),
|
||||
]}
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Chatter API
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
@http.route('/mail/thread/data', methods=['POST'], type='json', auth='user')
|
||||
def mail_thread_data(self, thread_model, thread_id, request_list, **kwargs):
|
||||
thread = request.env[thread_model].with_context(active_test=False).search([('id', '=', thread_id)])
|
||||
return thread._get_mail_thread_data(request_list)
|
||||
|
||||
@http.route('/mail/thread/messages', methods=['POST'], type='json', auth='user')
|
||||
def mail_thread_messages(self, thread_model, thread_id, max_id=None, min_id=None, limit=30, **kwargs):
|
||||
messages = request.env['mail.message']._message_fetch(domain=[
|
||||
('res_id', '=', int(thread_id)),
|
||||
('model', '=', thread_model),
|
||||
('message_type', '!=', 'user_notification'),
|
||||
], max_id=max_id, min_id=min_id, limit=limit)
|
||||
if not request.env.user._is_public():
|
||||
messages.set_message_done()
|
||||
return messages.message_format()
|
||||
|
||||
@http.route('/mail/read_subscription_data', methods=['POST'], type='json', auth='user')
|
||||
def read_subscription_data(self, follower_id):
|
||||
""" Computes:
|
||||
- message_subtype_data: data about document subtypes: which are
|
||||
available, which are followed if any """
|
||||
request.env['mail.followers'].check_access_rights("read")
|
||||
follower = request.env['mail.followers'].sudo().browse(follower_id)
|
||||
follower.ensure_one()
|
||||
request.env[follower.res_model].check_access_rights("read")
|
||||
record = request.env[follower.res_model].browse(follower.res_id)
|
||||
record.check_access_rule("read")
|
||||
|
||||
# find current model subtypes, add them to a dictionary
|
||||
subtypes = record._mail_get_message_subtypes()
|
||||
|
||||
followed_subtypes_ids = set(follower.subtype_ids.ids)
|
||||
subtypes_list = [{
|
||||
'name': subtype.name,
|
||||
'res_model': subtype.res_model,
|
||||
'sequence': subtype.sequence,
|
||||
'default': subtype.default,
|
||||
'internal': subtype.internal,
|
||||
'followed': subtype.id in followed_subtypes_ids,
|
||||
'parent_model': subtype.parent_id.res_model,
|
||||
'id': subtype.id
|
||||
} for subtype in subtypes]
|
||||
return sorted(subtypes_list,
|
||||
key=lambda it: (it['parent_model'] or '', it['res_model'] or '', it['internal'], it['sequence']))
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# RTC API TODO move check logic in routes.
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
@http.route('/mail/rtc/session/notify_call_members', methods=['POST'], type="json", auth="public")
|
||||
def session_call_notify(self, peer_notifications):
|
||||
""" Sends content to other session of the same channel, only works if the user is the user of that session.
|
||||
This is used to send peer to peer information between sessions.
|
||||
|
||||
:param peer_notifications: list of tuple with the following elements:
|
||||
- int sender_session_id: id of the session from which the content is sent
|
||||
- list target_session_ids: list of the ids of the sessions that should receive the content
|
||||
- string content: the content to send to the other sessions
|
||||
"""
|
||||
guest = request.env['mail.guest']._get_guest_from_request(request)
|
||||
notifications_by_session = defaultdict(list)
|
||||
for sender_session_id, target_session_ids, content in peer_notifications:
|
||||
session_sudo = guest.env['mail.channel.rtc.session'].sudo().browse(int(sender_session_id)).exists()
|
||||
if not session_sudo or (session_sudo.guest_id and session_sudo.guest_id != guest) or (session_sudo.partner_id and session_sudo.partner_id != request.env.user.partner_id):
|
||||
continue
|
||||
notifications_by_session[session_sudo].append(([int(sid) for sid in target_session_ids], content))
|
||||
for session_sudo, notifications in notifications_by_session.items():
|
||||
session_sudo._notify_peers(notifications)
|
||||
|
||||
@http.route('/mail/rtc/session/update_and_broadcast', methods=['POST'], type="json", auth="public")
|
||||
def session_update_and_broadcast(self, session_id, values):
|
||||
""" Update a RTC session and broadcasts the changes to the members of its channel,
|
||||
only works of the user is the user of that session.
|
||||
:param int session_id: id of the session to update
|
||||
:param dict values: write dict for the fields to update
|
||||
"""
|
||||
if request.env.user._is_public():
|
||||
guest = request.env['mail.guest']._get_guest_from_request(request)
|
||||
if guest:
|
||||
session = guest.env['mail.channel.rtc.session'].sudo().browse(int(session_id)).exists()
|
||||
if session and session.guest_id == guest:
|
||||
session._update_and_broadcast(values)
|
||||
return
|
||||
return
|
||||
session = request.env['mail.channel.rtc.session'].sudo().browse(int(session_id)).exists()
|
||||
if session and session.partner_id == request.env.user.partner_id:
|
||||
session._update_and_broadcast(values)
|
||||
|
||||
@http.route('/mail/rtc/channel/join_call', methods=['POST'], type="json", auth="public")
|
||||
def channel_call_join(self, channel_id, check_rtc_session_ids=None):
|
||||
""" Joins the RTC call of a channel if the user is a member of that channel
|
||||
:param int channel_id: id of the channel to join
|
||||
"""
|
||||
channel_member_sudo = request.env['mail.channel.member']._get_as_sudo_from_request_or_raise(request=request, channel_id=int(channel_id))
|
||||
return channel_member_sudo._rtc_join_call(check_rtc_session_ids=check_rtc_session_ids)
|
||||
|
||||
@http.route('/mail/rtc/channel/leave_call', methods=['POST'], type="json", auth="public")
|
||||
def channel_call_leave(self, channel_id):
|
||||
""" Disconnects the current user from a rtc call and clears any invitation sent to that user on this channel
|
||||
:param int channel_id: id of the channel from which to disconnect
|
||||
"""
|
||||
channel_member_sudo = request.env['mail.channel.member']._get_as_sudo_from_request_or_raise(request=request, channel_id=int(channel_id))
|
||||
return channel_member_sudo._rtc_leave_call()
|
||||
|
||||
@http.route('/mail/rtc/channel/cancel_call_invitation', methods=['POST'], type="json", auth="public")
|
||||
def channel_call_cancel_invitation(self, channel_id, member_ids=None):
|
||||
""" Sends invitations to join the RTC call to all connected members of the thread who are not already invited,
|
||||
if member_ids is provided, only the specified ids will be invited.
|
||||
|
||||
:param list member_ids: list of member ids to invite
|
||||
"""
|
||||
channel_member_sudo = request.env['mail.channel.member']._get_as_sudo_from_request_or_raise(request=request, channel_id=int(channel_id))
|
||||
return channel_member_sudo.channel_id._rtc_cancel_invitations(member_ids=member_ids)
|
||||
|
||||
@http.route('/mail/rtc/audio_worklet_processor', methods=['GET'], type='http', auth='public')
|
||||
def audio_worklet_processor(self):
|
||||
""" Returns a JS file that declares a WorkletProcessor class in
|
||||
a WorkletGlobalScope, which means that it cannot be added to the
|
||||
bundles like other assets.
|
||||
"""
|
||||
return request.make_response(
|
||||
file_open('mail/static/src/worklets/audio_processor.js', 'rb').read(),
|
||||
headers=[
|
||||
('Content-Type', 'application/javascript'),
|
||||
('Cache-Control', 'max-age=%s' % http.STATIC_CACHE),
|
||||
]
|
||||
)
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Guest API
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
@http.route('/mail/guest/update_name', methods=['POST'], type='json', auth='public')
|
||||
def mail_guest_update_name(self, guest_id, name):
|
||||
guest = request.env['mail.guest']._get_guest_from_request(request)
|
||||
guest_to_rename_sudo = guest.env['mail.guest'].browse(guest_id).sudo().exists()
|
||||
if not guest_to_rename_sudo:
|
||||
raise NotFound()
|
||||
if guest_to_rename_sudo != guest and not request.env.user._is_admin():
|
||||
raise NotFound()
|
||||
guest_to_rename_sudo._update_name(name)
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Link preview API
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
@http.route('/mail/link_preview', methods=['POST'], type='json', auth='public')
|
||||
def mail_link_preview(self, message_id):
|
||||
if not request.env['mail.link.preview'].sudo()._is_link_preview_enabled():
|
||||
return
|
||||
guest = request.env['mail.guest']._get_guest_from_request(request)
|
||||
message = guest.env['mail.message'].search([('id', '=', int(message_id))])
|
||||
if not message:
|
||||
return
|
||||
if not message.is_current_user_or_guest_author and not guest.env.user._is_admin():
|
||||
return
|
||||
guest.env['mail.link.preview'].sudo()._create_link_previews(message)
|
||||
|
||||
@http.route('/mail/link_preview/delete', methods=['POST'], type='json', auth='public')
|
||||
def mail_link_preview_delete(self, link_preview_id):
|
||||
guest = request.env['mail.guest']._get_guest_from_request(request)
|
||||
link_preview_sudo = guest.env['mail.link.preview'].sudo().search([('id', '=', int(link_preview_id))])
|
||||
if not link_preview_sudo:
|
||||
return
|
||||
if not link_preview_sudo.message_id.is_current_user_or_guest_author and not guest.env.user._is_admin():
|
||||
return
|
||||
link_preview_sudo._delete_and_notify()
|
||||
42
odoo-bringout-oca-ocb-mail/mail/controllers/home.py
Normal file
42
odoo-bringout-oca-ocb-mail/mail/controllers/home.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import ipaddress
|
||||
|
||||
from odoo import _, SUPERUSER_ID
|
||||
from odoo.http import request
|
||||
from odoo.addons.web.controllers.home import Home as WebHome
|
||||
|
||||
def _admin_password_warn(uid):
|
||||
""" Admin still has `admin` password, flash a message via chatter.
|
||||
|
||||
Uses a private mail.channel from the system (/ odoobot) to the user, as
|
||||
using a more generic mail.thread could send an email which is undesirable
|
||||
|
||||
Uses mail.channel directly because using mail.thread might send an email instead.
|
||||
"""
|
||||
if request.params['password'] != 'admin':
|
||||
return
|
||||
if ipaddress.ip_address(request.httprequest.remote_addr).is_private:
|
||||
return
|
||||
env = request.env(user=SUPERUSER_ID, su=True)
|
||||
admin = env.ref('base.partner_admin')
|
||||
if uid not in admin.user_ids.ids:
|
||||
return
|
||||
has_demo = bool(env['ir.module.module'].search_count([('demo', '=', True)]))
|
||||
if has_demo:
|
||||
return
|
||||
|
||||
user = request.env(user=uid)['res.users']
|
||||
MailChannel = env(context=user.context_get())['mail.channel']
|
||||
MailChannel.browse(MailChannel.channel_get([admin.id])['id'])\
|
||||
.message_post(
|
||||
body=_("Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!"),
|
||||
message_type='comment',
|
||||
subtype_xmlid='mail.mt_comment'
|
||||
)
|
||||
|
||||
class Home(WebHome):
|
||||
def _login_redirect(self, uid, redirect=None):
|
||||
if request.params.get('login_success'):
|
||||
_admin_password_warn(uid)
|
||||
|
||||
return super()._login_redirect(uid, redirect)
|
||||
206
odoo-bringout-oca-ocb-mail/mail/controllers/mail.py
Normal file
206
odoo-bringout-oca-ocb-mail/mail/controllers/mail.py
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
|
||||
from werkzeug.urls import url_encode
|
||||
|
||||
from odoo import http
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.http import request
|
||||
from odoo.tools import consteq
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MailController(http.Controller):
|
||||
_cp_path = '/mail'
|
||||
|
||||
@classmethod
|
||||
def _redirect_to_generic_fallback(cls, model, res_id, access_token=None, **kwargs):
|
||||
if request.session.uid is None:
|
||||
return cls._redirect_to_login_with_mail_view(
|
||||
model, res_id, access_token=access_token, **kwargs,
|
||||
)
|
||||
return cls._redirect_to_messaging()
|
||||
|
||||
@classmethod
|
||||
def _redirect_to_messaging(cls):
|
||||
url = '/web#%s' % url_encode({'action': 'mail.action_discuss'})
|
||||
return request.redirect(url)
|
||||
|
||||
@classmethod
|
||||
def _redirect_to_login_with_mail_view(cls, model, res_id, access_token=None, **kwargs):
|
||||
url_base = '/mail/view'
|
||||
url_params = request.env['mail.thread']._notify_get_action_link_params(
|
||||
'view', **{
|
||||
'model': model,
|
||||
'res_id': res_id,
|
||||
'access_token': access_token,
|
||||
**kwargs,
|
||||
}
|
||||
)
|
||||
mail_view_url = f'{url_base}?{url_encode(url_params, sort=True)}'
|
||||
return request.redirect(f'/web/login?{url_encode({"redirect": mail_view_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']._notify_encode_link(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_generic_fallback(model, res_id)
|
||||
try:
|
||||
record = request.env[model].browse(res_id).exists()
|
||||
except Exception:
|
||||
record = None
|
||||
redirect = cls._redirect_to_generic_fallback(model, res_id)
|
||||
else:
|
||||
redirect = cls._redirect_to_record(model, res_id)
|
||||
return comparison, record, redirect
|
||||
|
||||
@classmethod
|
||||
def _redirect_to_record(cls, model, res_id, access_token=None, **kwargs):
|
||||
# access_token and kwargs are used in the portal controller override for the Send by email or Share Link
|
||||
# to give access to the record to a recipient that has normally no access.
|
||||
uid = request.session.uid
|
||||
user = request.env['res.users'].sudo().browse(uid)
|
||||
cids = []
|
||||
|
||||
# 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_generic_fallback(
|
||||
model, res_id, access_token=access_token, **kwargs,
|
||||
)
|
||||
|
||||
# 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_generic_fallback(
|
||||
model, res_id, access_token=access_token, **kwargs,
|
||||
)
|
||||
|
||||
suggested_company = record_sudo._get_mail_redirect_suggested_company()
|
||||
# the record has a window redirection: check access rights
|
||||
if uid is not None:
|
||||
if not RecordModel.with_user(uid).check_access_rights('read', raise_exception=False):
|
||||
return cls._redirect_to_generic_fallback(
|
||||
model, res_id, access_token=access_token, **kwargs,
|
||||
)
|
||||
try:
|
||||
# We need here to extend the "allowed_company_ids" to allow a redirection
|
||||
# to any record that the user can access, regardless of currently visible
|
||||
# records based on the "currently allowed companies".
|
||||
cids_str = request.httprequest.cookies.get('cids', str(user.company_id.id))
|
||||
cids = [int(cid) for cid in cids_str.split(',')]
|
||||
try:
|
||||
record_sudo.with_user(uid).with_context(allowed_company_ids=cids).check_access_rule('read')
|
||||
except AccessError:
|
||||
# In case the allowed_company_ids from the cookies (i.e. the last user configuration
|
||||
# on their browser) is not sufficient to avoid an ir.rule access error, try to following
|
||||
# heuristic:
|
||||
# - Guess the supposed necessary company to access the record via the method
|
||||
# _get_mail_redirect_suggested_company
|
||||
# - If no company, then redirect to the messaging
|
||||
# - Merge the suggested company with the companies on the cookie
|
||||
# - Make a new access test if it succeeds, redirect to the record. Otherwise,
|
||||
# redirect to the messaging.
|
||||
if not suggested_company:
|
||||
raise AccessError('')
|
||||
cids = cids + [suggested_company.id]
|
||||
record_sudo.with_user(uid).with_context(allowed_company_ids=cids).check_access_rule('read')
|
||||
except AccessError:
|
||||
return cls._redirect_to_generic_fallback(
|
||||
model, res_id, access_token=access_token, **kwargs,
|
||||
)
|
||||
else:
|
||||
record_action = record_sudo._get_access_action(access_uid=uid)
|
||||
else:
|
||||
record_action = record_sudo._get_access_action()
|
||||
# we have an act_url (probably a portal link): we need to retry being logged to check access
|
||||
if record_action['type'] == 'ir.actions.act_url' and record_action.get('target_type') != 'public':
|
||||
return cls._redirect_to_login_with_mail_view(
|
||||
model, res_id, access_token=access_token, **kwargs,
|
||||
)
|
||||
|
||||
record_action.pop('target_type', None)
|
||||
# the record has an URL redirection: use it directly
|
||||
if record_action['type'] == 'ir.actions.act_url':
|
||||
return request.redirect(record_action['url'])
|
||||
# anything else than an act_window is not supported
|
||||
elif not record_action['type'] == 'ir.actions.act_window':
|
||||
return cls._redirect_to_messaging()
|
||||
|
||||
# backend act_window: when not logged, unless really readable as public,
|
||||
# user is going to be redirected to login -> keep mail/view as redirect
|
||||
# in that case. In case of readable record, we consider this might be
|
||||
# a customization and we do not change the behavior in stable
|
||||
if uid is None or request.env.user._is_public():
|
||||
has_access = record_sudo.with_user(request.env.user).check_access_rights('read', raise_exception=False)
|
||||
if has_access:
|
||||
try:
|
||||
record_sudo.with_user(request.env.user).check_access_rule('read')
|
||||
except AccessError:
|
||||
has_access = False
|
||||
if not has_access:
|
||||
return cls._redirect_to_login_with_mail_view(
|
||||
model, res_id, access_token=access_token, **kwargs,
|
||||
)
|
||||
|
||||
url_params = {
|
||||
'model': model,
|
||||
'id': res_id,
|
||||
'active_id': res_id,
|
||||
'action': record_action.get('id'),
|
||||
}
|
||||
view_id = record_sudo.get_formview_id()
|
||||
if view_id:
|
||||
url_params['view_id'] = view_id
|
||||
|
||||
if cids:
|
||||
url_params['cids'] = ','.join([str(cid) for cid in cids])
|
||||
url = '/web?#%s' % url_encode(url_params, sort=True)
|
||||
return request.redirect(url)
|
||||
|
||||
@http.route('/mail/view', type='http', auth='public')
|
||||
def mail_action_view(self, model=None, res_id=None, access_token=None, **kwargs):
|
||||
""" Generic access point from notification emails. The heuristic to
|
||||
choose where to redirect the user is the following :
|
||||
|
||||
- find a public URL
|
||||
- if none found
|
||||
- users with a read access are redirected to the document
|
||||
- users without read access are redirected to the Messaging
|
||||
- not logged users are redirected to the login page
|
||||
|
||||
models that have an access_token may apply variations on this.
|
||||
"""
|
||||
# ==============================================================================================
|
||||
# This block of code disappeared on saas-11.3 to be reintroduced by TBE.
|
||||
# This is needed because after a migration from an older version to saas-11.3, the link
|
||||
# received by mail with a message_id no longer work.
|
||||
# So this block of code is needed to guarantee the backward compatibility of those links.
|
||||
if kwargs.get('message_id'):
|
||||
try:
|
||||
message = request.env['mail.message'].sudo().browse(int(kwargs['message_id'])).exists()
|
||||
except:
|
||||
message = request.env['mail.message']
|
||||
if message:
|
||||
model, res_id = message.model, message.res_id
|
||||
# ==============================================================================================
|
||||
|
||||
if res_id and isinstance(res_id, str):
|
||||
try:
|
||||
res_id = int(res_id)
|
||||
except ValueError:
|
||||
res_id = False
|
||||
return self._redirect_to_record(model, res_id, access_token, **kwargs)
|
||||
Loading…
Add table
Add a link
Reference in a new issue