mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-20 09:52:02 +02:00
271 lines
13 KiB
Python
271 lines
13 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from datetime import datetime
|
|
from markupsafe import Markup
|
|
from werkzeug.exceptions import NotFound
|
|
|
|
from odoo import http
|
|
from odoo.exceptions import UserError
|
|
from odoo.http import request
|
|
from odoo.tools.misc import verify_limited_field_access_token
|
|
from odoo.addons.mail.tools.discuss import add_guest_to_context, Store
|
|
|
|
|
|
class ThreadController(http.Controller):
|
|
|
|
# access helpers
|
|
# ------------------------------------------------------------
|
|
|
|
@classmethod
|
|
def _get_message_with_access(cls, message_id, mode="read", **kwargs):
|
|
""" Simplified getter that filters access params only, making model methods
|
|
using strong parameters. """
|
|
message_su = request.env['mail.message'].sudo().browse(message_id).exists()
|
|
if not message_su:
|
|
return message_su
|
|
return request.env['mail.message']._get_with_access(
|
|
message_su.id,
|
|
mode=mode,
|
|
**{
|
|
key: value for key, value in kwargs.items()
|
|
if key in request.env[message_su.model or 'mail.thread']._get_allowed_access_params()
|
|
},
|
|
)
|
|
|
|
@classmethod
|
|
def _get_thread_with_access_for_post(cls, thread_model, thread_id, **kwargs):
|
|
""" Helper allowing to fetch thread with access when requesting 'create'
|
|
access on mail.message, aka rights to post on the document. Default
|
|
behavior is to rely on _mail_post_access but it might be customized.
|
|
See '_mail_get_operation_for_mail_message_operation'. """
|
|
thread_su = request.env[thread_model].sudo().browse(int(thread_id))
|
|
access_mode = thread_su._mail_get_operation_for_mail_message_operation('create')[thread_su]
|
|
if not access_mode:
|
|
return request.env[thread_model] # match _get_thread_with_access void result
|
|
return cls._get_thread_with_access(thread_model, thread_id, mode=access_mode, **kwargs)
|
|
|
|
@classmethod
|
|
def _get_thread_with_access(cls, thread_model, thread_id, mode="read", **kwargs):
|
|
""" Simplified getter that filters access params only, making model methods
|
|
using strong parameters. """
|
|
return request.env[thread_model]._get_thread_with_access(
|
|
int(thread_id), mode=mode, **{
|
|
key: value for key, value in kwargs.items()
|
|
if key in request.env[thread_model]._get_allowed_access_params()
|
|
},
|
|
)
|
|
|
|
# main routes
|
|
# ------------------------------------------------------------
|
|
|
|
@http.route("/mail/thread/messages", methods=["POST"], type="jsonrpc", auth="user")
|
|
def mail_thread_messages(self, thread_model, thread_id, fetch_params=None):
|
|
thread = self._get_thread_with_access(thread_model, thread_id, mode="read")
|
|
res = request.env["mail.message"]._message_fetch(domain=None, thread=thread, **(fetch_params or {}))
|
|
messages = res.pop("messages")
|
|
if not request.env.user._is_public():
|
|
messages.set_message_done()
|
|
return {
|
|
**res,
|
|
"data": Store().add(messages).get_result(),
|
|
"messages": messages.ids,
|
|
}
|
|
|
|
@http.route("/mail/thread/recipients", methods=["POST"], type="jsonrpc", auth="user")
|
|
def mail_thread_recipients(self, thread_model, thread_id, message_id=None):
|
|
""" Fetch discussion-based suggested recipients, creating partners on the fly """
|
|
thread = self._get_thread_with_access(thread_model, thread_id, mode='read')
|
|
if message_id:
|
|
message = self._get_message_with_access(message_id, mode="read")
|
|
suggested = thread._message_get_suggested_recipients(
|
|
reply_message=message, no_create=False,
|
|
)
|
|
else:
|
|
suggested = thread._message_get_suggested_recipients(
|
|
reply_discussion=True, no_create=False,
|
|
)
|
|
return [
|
|
{'id': info['partner_id'], 'email': info['email'], 'name': info['name']}
|
|
for info in suggested if info['partner_id']
|
|
]
|
|
|
|
@http.route("/mail/thread/recipients/fields", methods=["POST"], type="jsonrpc", auth="user")
|
|
def mail_thread_recipients_fields(self, thread_model):
|
|
return {
|
|
'partner_fields': request.env[thread_model]._mail_get_partner_fields(),
|
|
'primary_email_field': [request.env[thread_model]._mail_get_primary_email_field()]
|
|
}
|
|
|
|
@http.route("/mail/thread/recipients/get_suggested_recipients", methods=["POST"], type="jsonrpc", auth="user")
|
|
def mail_thread_recipients_get_suggested_recipients(self, thread_model, thread_id, partner_ids=None, main_email=False):
|
|
"""This method returns the suggested recipients with updates coming from the frontend.
|
|
:param thread_model: Model on which we are currently working on.
|
|
:param thread_id: ID of the document we need to compute
|
|
:param partner_ids: IDs of new customers that were edited on the frontend, usually only the customer but could be more.
|
|
:param main_email: New email edited on the frontend linked to the @see _mail_get_primary_email_field
|
|
"""
|
|
thread = self._get_thread_with_access(thread_model, thread_id)
|
|
partner_ids = request.env['res.partner'].search([('id', 'in', partner_ids)])
|
|
recipients = thread._message_get_suggested_recipients(reply_discussion=True, additional_partners=partner_ids, primary_email=main_email)
|
|
if partner_ids:
|
|
old_customer_ids = set(thread._mail_get_partners()[thread.id].ids) - set(partner_ids.ids)
|
|
recipients = list(filter(lambda rec: rec.get('partner_id') not in old_customer_ids, recipients))
|
|
return [{key: recipient[key] for key in recipient if key in ['name', 'email', 'partner_id']} for recipient in recipients]
|
|
|
|
@http.route("/mail/partner/from_email", methods=["POST"], type="jsonrpc", auth="user")
|
|
def mail_thread_partner_from_email(self, thread_model, thread_id, emails):
|
|
partners = [
|
|
{"id": partner.id, "name": partner.name, "email": partner.email}
|
|
for partner in request.env[thread_model].browse(thread_id)._partner_find_from_emails_single(
|
|
emails, no_create=not request.env.user.has_group("base.group_partner_manager")
|
|
)
|
|
]
|
|
return partners
|
|
|
|
@http.route("/mail/read_subscription_data", methods=["POST"], type="jsonrpc", 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"""
|
|
# limited to internal, who can read all followers
|
|
follower = request.env["mail.followers"].browse(follower_id)
|
|
follower.check_access("read")
|
|
record = request.env[follower.res_model].browse(follower.res_id)
|
|
record.check_access("read")
|
|
# find current model subtypes, add them to a dictionary
|
|
subtypes = record._mail_get_message_subtypes()
|
|
store = Store().add(subtypes, ["name"]).add(follower, ["subtype_ids"])
|
|
return {
|
|
"store_data": store.get_result(),
|
|
"subtype_ids": subtypes.sorted(
|
|
key=lambda s: (
|
|
s.parent_id.res_model or "",
|
|
s.res_model or "",
|
|
s.internal,
|
|
s.sequence,
|
|
),
|
|
).ids,
|
|
}
|
|
|
|
def _prepare_message_data(self, post_data, *, thread, **kwargs):
|
|
res = {
|
|
key: value
|
|
for key, value in post_data.items()
|
|
if key in thread._get_allowed_message_params()
|
|
}
|
|
if (attachment_ids := post_data.get("attachment_ids")) is not None:
|
|
attachments = request.env["ir.attachment"].browse(map(int, attachment_ids))
|
|
if not attachments._has_attachments_ownership(post_data.get("attachment_tokens")):
|
|
msg = self.env._(
|
|
"One or more attachments do not exist, or you do not have the rights to access them.",
|
|
)
|
|
raise UserError(msg)
|
|
res["attachment_ids"] = attachments.ids
|
|
if "body" in post_data:
|
|
# User input is HTML string, so it needs to be in a Markup.
|
|
# It will be sanitized by the field itself when writing on it.
|
|
res["body"] = Markup(post_data["body"]) if post_data["body"] else post_data["body"]
|
|
partner_ids = post_data.get("partner_ids")
|
|
partner_emails = post_data.get("partner_emails")
|
|
role_ids = post_data.get("role_ids")
|
|
if partner_ids is not None or partner_emails is not None or role_ids is not None:
|
|
partners = request.env["res.partner"].browse(map(int, partner_ids or []))
|
|
if partner_emails:
|
|
partners |= thread._partner_find_from_emails_single(
|
|
partner_emails,
|
|
no_create=not request.env.user.has_group("base.group_partner_manager"),
|
|
)
|
|
if role_ids:
|
|
# sudo - res.users: getting partners linked to the role is allowed.
|
|
partners |= (
|
|
request.env["res.users"]
|
|
.sudo()
|
|
.search_fetch([("role_ids", "in", role_ids)], ["partner_id"])
|
|
.partner_id
|
|
)
|
|
res["partner_ids"] = partners.filtered(
|
|
lambda p: (not self.env.user.share and p.has_access("read"))
|
|
or (
|
|
verify_limited_field_access_token(
|
|
p,
|
|
"id",
|
|
post_data.get("partner_ids_mention_token", {}).get(str(p.id), ""),
|
|
scope="mail.message_mention",
|
|
)
|
|
),
|
|
).ids
|
|
res.setdefault("message_type", "comment")
|
|
return res
|
|
|
|
@http.route("/mail/message/post", methods=["POST"], type="jsonrpc", auth="public")
|
|
@add_guest_to_context
|
|
def mail_message_post(self, thread_model, thread_id, post_data, context=None, **kwargs):
|
|
store = Store()
|
|
request.update_context(message_post_store=store)
|
|
if context:
|
|
request.update_context(**context)
|
|
canned_response_ids = tuple(cid for cid in kwargs.get('canned_response_ids', []) if isinstance(cid, int))
|
|
if canned_response_ids:
|
|
# Avoid serialization errors since last used update is not
|
|
# essential and should not block message post.
|
|
request.env.cr.execute("""
|
|
UPDATE mail_canned_response SET last_used=%(last_used)s
|
|
WHERE id IN (
|
|
SELECT id from mail_canned_response WHERE id IN %(ids)s
|
|
FOR NO KEY UPDATE SKIP LOCKED
|
|
)
|
|
""", {
|
|
'last_used': datetime.now(),
|
|
'ids': canned_response_ids,
|
|
})
|
|
thread = self._get_thread_with_access_for_post(thread_model, thread_id, **kwargs)
|
|
if not thread:
|
|
raise NotFound()
|
|
if not self._get_thread_with_access(thread_model, thread_id, mode="write"):
|
|
thread = thread.with_context(mail_post_autofollow_author_skip=True, mail_post_autofollow=False)
|
|
# sudo: mail.thread - users can post on accessible threads
|
|
message = thread.sudo().message_post(
|
|
**self._prepare_message_data(post_data, thread=thread, from_create=True, **kwargs),
|
|
)
|
|
return {
|
|
"store_data": store.add(message).get_result(),
|
|
"message_id": message.id,
|
|
}
|
|
|
|
@http.route("/mail/message/update_content", methods=["POST"], type="jsonrpc", auth="public")
|
|
@add_guest_to_context
|
|
def mail_message_update_content(self, message_id, update_data, **kwargs):
|
|
message = self._get_message_with_access(message_id, mode="create", **kwargs)
|
|
if not message or not self._can_edit_message(message, **kwargs):
|
|
raise NotFound()
|
|
# sudo: mail.message - access is checked in _get_with_access and _can_edit_message
|
|
message = message.sudo()
|
|
thread = request.env[message.model].browse(message.res_id)
|
|
thread._message_update_content(
|
|
message,
|
|
**self._prepare_message_data(update_data, thread=thread, from_create=False, **kwargs),
|
|
)
|
|
return Store().add(message).get_result()
|
|
|
|
# side check for access
|
|
# ------------------------------------------------------------
|
|
|
|
@classmethod
|
|
def _can_edit_message(cls, message, **kwargs):
|
|
return message.sudo().is_current_user_or_guest_author or request.env.user._is_admin()
|
|
|
|
@http.route("/mail/thread/unsubscribe", methods=["POST"], type="jsonrpc", auth="user")
|
|
def mail_thread_unsubscribe(self, res_model, res_id, partner_ids):
|
|
thread = self.env[res_model].browse(res_id)
|
|
thread.message_unsubscribe(partner_ids)
|
|
return Store().add(
|
|
thread, [], as_thread=True, request_list=["followers", "suggestedRecipients"]
|
|
).get_result()
|
|
|
|
@http.route("/mail/thread/subscribe", methods=["POST"], type="jsonrpc", auth="user")
|
|
def mail_thread_subscribe(self, res_model, res_id, partner_ids):
|
|
thread = self.env[res_model].browse(res_id)
|
|
thread.message_subscribe(partner_ids)
|
|
return Store().add(
|
|
thread, [], as_thread=True, request_list=["followers", "suggestedRecipients"]
|
|
).get_result()
|