odoo18/addons_extensions/knowledge/models/knowledge_article_thread.py

144 lines
6.6 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo.tools import html2plaintext
class KnowledgeArticleThread(models.Model):
"""
This is the model for a comment thread linked to a `knowledge.article`. Each thread inherits
the `mail.thread` mixin.
These threads allow end-users to discuss specific parts of the body of a knowledge article.
Which enables reviews, taking notes, pinging a colleague to get more information on a topic, ...
Each initial comment starts its own thread, which will then accumulate replies, reactions, etc.
It is also possible to mark a thread as closed so that it no longer appears inside the editor
of the article if the conversation does not need to be continued.
"""
_name = "knowledge.article.thread"
_description = "Article Discussion Thread"
_inherit = ['mail.thread']
_mail_post_access = 'read' # if you can read, you can post a message on an article thread
_order = 'write_date desc, id desc'
_rec_name = 'display_name'
_ANCHOR_TEXT_MAX_LENGTH = 1200
article_anchor_text = fields.Text("Anchor Text",
help="The original highlighted anchor text, giving initial context if that text is modified or removed afterwards."
)
article_id = fields.Many2one('knowledge.article', ondelete="cascade", readonly=True, required=True)
is_resolved = fields.Boolean("Thread Closed", tracking=True)
@api.depends('article_id')
def _compute_display_name(self):
for record in self:
record.display_name = record.article_id.display_name
# ===========================================================================
# CRUD METHODS
# ===========================================================================
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if 'article_anchor_text' in vals:
article_anchor_text = html2plaintext(vals['article_anchor_text'])
vals['article_anchor_text'] = (article_anchor_text[:self._ANCHOR_TEXT_MAX_LENGTH] + '...') \
if len(article_anchor_text) > self._ANCHOR_TEXT_MAX_LENGTH else article_anchor_text
return super(KnowledgeArticleThread, self.with_context(mail_create_nolog=True)).create(vals_list)
def write(self, vals):
if 'is_resolved' in vals:
self.ensure_one()
if 'article_anchor_text' in vals:
article_anchor_text = html2plaintext(vals['article_anchor_text'])
vals['article_anchor_text'] = (article_anchor_text[:self._ANCHOR_TEXT_MAX_LENGTH] + '...') \
if len(article_anchor_text) > self._ANCHOR_TEXT_MAX_LENGTH else article_anchor_text
return super().write(vals)
# ==========================================================================
# THREAD OVERRIDES
# ==========================================================================
def message_post(self, **kwargs):
"""This function overrides the 'mail.thread' message_post in order to control what portal
users that have access to an article can post through a thread message.
Before posting as a portal we filter what's being sent to lessen security risks. Notably
partner_ids should be a list of ids (not the records themselves) so that we don't allow command
executions.
"""
self.ensure_one()
if self.env.user._is_portal() and self.article_id.user_has_access:
authorized_keys = {'body', 'partner_ids', 'author_id', 'attachment_ids'}
return super().message_post(
**{key: kwargs.get(key) for key in authorized_keys},
message_type='comment', subtype_xmlid='mail.mt_comment'
)
kwargs.update({'message_type': 'comment', 'subtype_xmlid': 'mail.mt_comment'})
return super().message_post(**kwargs)
def _get_access_action(self, access_uid=None, force_website=False):
self.ensure_one()
user = self.env['res.users'].sudo().browse(access_uid) if access_uid else self.env.user
action = {
'type': 'ir.actions.act_url',
'url': f'/knowledge/article/{self.article_id.id}',
}
if access_uid is None:
action['target_type'] = 'public'
if self.article_id.with_user(user).user_has_access or access_uid is None:
return action
return super()._get_access_action(access_uid=access_uid, force_website=force_website)
def _notify_thread_by_email(self, message, recipients_data, **kwargs):
"""We need to override this method to set our own mail template to be sent to users that
have been tagged inside a comment. We are using the template 'knowledge.knowledge_mail_notification_layout'
which is a simple template comprised of the comment sent and the person that tagged the notified user.
"""
if not kwargs.get('msg_vals', {}).get('partner_ids', []):
return
kwargs['msg_vals'] = {**kwargs.get('msg_vals', {}), 'email_layout_xmlid': 'knowledge.knowledge_mail_notification_layout'}
return super()._notify_thread_by_email(message, recipients_data, **kwargs)
def _message_compute_subject(self):
self.ensure_one()
return _('New Mention in %s') % self.display_name
def _notify_get_recipients(self, message, msg_vals, **kwargs):
recipients_data = super()._notify_get_recipients(message, msg_vals, **kwargs)
recipients_data = [data for data in recipients_data if data['id'] in msg_vals.get('partner_ids', [])]
return recipients_data
def _notify_get_recipients_groups(self, message, model_description, msg_vals=None):
groups = super()._notify_get_recipients_groups(
message, model_description, msg_vals=msg_vals
)
if message.model != 'knowledge.article.thread':
return groups
self.ensure_one()
action = self._notify_get_action_link('controller', controller='/knowledge/thread/resolve', **msg_vals)
user_actions = [{'url': action, 'title': _('Mark Comment as Closed')}]
new_groups = [(
'group_knowledge_article_thread_portal_and_users',
lambda pdata:
pdata['uid'] and self.article_id.with_user(pdata['uid']).user_has_access,
{
'actions': user_actions,
'active': True,
'has_button_access': True,
}
)]
return new_groups + groups