144 lines
6.6 KiB
Python
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
|