196 lines
8.4 KiB
Python
196 lines
8.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import datetime
|
|
import markupsafe
|
|
|
|
from odoo import _, api, fields, models, tools
|
|
|
|
|
|
class MailThread(models.AbstractModel):
|
|
_inherit = 'mail.thread'
|
|
|
|
rating_ids = fields.One2many('rating.rating', 'res_id', string='Ratings', groups='base.group_user',
|
|
domain=lambda self: [('res_model', '=', self._name)], auto_join=True)
|
|
|
|
# MAIL OVERRIDES
|
|
# --------------------------------------------------
|
|
|
|
def unlink(self):
|
|
""" When removing a record, its rating should be deleted too. """
|
|
record_ids = self.ids
|
|
result = super().unlink()
|
|
self.env['rating.rating'].sudo().search([('res_model', '=', self._name), ('res_id', 'in', record_ids)]).unlink()
|
|
return result
|
|
|
|
def _message_create(self, values_list):
|
|
""" Force usage of rating-specific methods and API allowing to delegate
|
|
computation to records. Keep methods optimized and skip rating_ids
|
|
support to simplify MailThrad main API. """
|
|
if not isinstance(values_list, list):
|
|
values_list = [values_list]
|
|
if any(values.get('rating_ids') for values in values_list):
|
|
raise ValueError(_("Posting a rating should be done using message post API."))
|
|
return super()._message_create(values_list)
|
|
|
|
# RATING CONFIGURATION
|
|
# --------------------------------------------------
|
|
|
|
def _rating_apply_get_default_subtype_id(self):
|
|
return self.env['ir.model.data']._xmlid_to_res_id("mail.mt_comment")
|
|
|
|
def _rating_get_operator(self):
|
|
""" Return the operator (partner) that is the person who is rated.
|
|
|
|
:return record: res.partner singleton
|
|
"""
|
|
if 'user_id' in self and self.user_id.partner_id:
|
|
return self.user_id.partner_id
|
|
return self.env['res.partner']
|
|
|
|
def _rating_get_partner(self):
|
|
""" Return the customer (partner) that performs the rating.
|
|
|
|
:return record: res.partner singleton
|
|
"""
|
|
if 'partner_id' in self and self.partner_id:
|
|
return self.partner_id
|
|
return self.env['res.partner']
|
|
|
|
# RATING SUPPORT
|
|
# --------------------------------------------------
|
|
|
|
def _rating_get_access_token(self, partner=None):
|
|
""" Return access token linked to existing ratings, or create a new rating
|
|
that will create the asked token. An explicit call to access rights is
|
|
performed as sudo is used afterwards as this method could be used from
|
|
different sources, notably templates. """
|
|
self.check_access_rights('read')
|
|
self.check_access_rule('read')
|
|
if not partner:
|
|
partner = self._rating_get_partner()
|
|
rated_partner = self._rating_get_operator()
|
|
rating = next(
|
|
(r for r in self.rating_ids.sudo()
|
|
if r.partner_id.id == partner.id and not r.consumed),
|
|
None)
|
|
if not rating:
|
|
rating = self.env['rating.rating'].sudo().create({
|
|
'partner_id': partner.id,
|
|
'rated_partner_id': rated_partner.id,
|
|
'res_model_id': self.env['ir.model']._get_id(self._name),
|
|
'res_id': self.id,
|
|
'is_internal': False,
|
|
})
|
|
return rating.access_token
|
|
|
|
# EXPOSED API
|
|
# --------------------------------------------------
|
|
|
|
def rating_send_request(self, template, lang=False, force_send=True):
|
|
""" This method send rating request by email, using a template given in parameter.
|
|
|
|
:param record template: a mail.template record used to compute the message body;
|
|
:param str lang: optional lang; it can also be specified directly on the template
|
|
itself in the lang field;
|
|
:param bool force_send: whether to send the request directly or use the mail
|
|
queue cron (preferred option);
|
|
"""
|
|
if lang:
|
|
template = template.with_context(lang=lang)
|
|
self.with_context(mail_notify_force_send=force_send).message_post_with_source(
|
|
template,
|
|
email_layout_xmlid='mail.mail_notification_light',
|
|
force_send=force_send,
|
|
subtype_xmlid='mail.mt_note',
|
|
)
|
|
|
|
def rating_apply(self, rate, token=None, rating=None, feedback=None,
|
|
subtype_xmlid=None, notify_delay_send=False):
|
|
""" Apply a rating to the record. This rating can either be linked to a
|
|
token (customer flow) or directly a rating record (code flow).
|
|
|
|
If the current model inherits from mail.thread mixin a message is posted
|
|
on its chatter. User going through this method should have at least
|
|
employee rights as well as rights on the current record because of rating
|
|
manipulation and chatter post (either employee, either sudo-ed in public
|
|
controllers after security check granting access).
|
|
|
|
:param float rate: the rating value to apply (from 0 to 5);
|
|
:param string token: access token to fetch the rating to apply (optional);
|
|
:param record rating: rating.rating to apply (if no token);
|
|
:param string feedback: additional feedback (plaintext);
|
|
:param string subtype_xmlid: xml id of a valid mail.message.subtype used
|
|
to post the message (if it applies). If not given a classic comment is
|
|
posted;
|
|
:param notify_delay_send: Delay the sending by 2 hours of the email so the user
|
|
can still change his feedback. If False, the email will be sent immediately.
|
|
|
|
:returns rating: rating.rating record
|
|
"""
|
|
if rate < 0 or rate > 5:
|
|
raise ValueError(_('Wrong rating value. A rate should be between 0 and 5 (received %d).', rate))
|
|
if token:
|
|
rating = self.env['rating.rating'].search([('access_token', '=', token)], limit=1)
|
|
if not rating:
|
|
raise ValueError(_('Invalid token or rating.'))
|
|
|
|
rating.write({'rating': rate, 'feedback': feedback, 'consumed': True})
|
|
if isinstance(self, self.env.registry['mail.thread']):
|
|
if subtype_xmlid is None:
|
|
subtype_id = self._rating_apply_get_default_subtype_id()
|
|
else:
|
|
subtype_id = False
|
|
feedback = tools.plaintext2html(feedback or '')
|
|
|
|
scheduled_datetime = (
|
|
fields.Datetime.now() + datetime.timedelta(hours=2)
|
|
if notify_delay_send else None
|
|
)
|
|
rating_body = (
|
|
markupsafe.Markup(
|
|
"<img src='%s' alt=':%s/5' style='width:18px;height:18px;float:left;margin-right: 5px;'/>%s"
|
|
) % (rating.rating_image_url, rate, feedback)
|
|
)
|
|
|
|
if rating.message_id:
|
|
self._message_update_content(
|
|
rating.message_id, rating_body,
|
|
scheduled_date=scheduled_datetime,
|
|
strict=False
|
|
)
|
|
else:
|
|
self.message_post(
|
|
author_id=rating.partner_id.id or None, # None will set the default author in mail/mail_thread.py
|
|
body=rating_body,
|
|
rating_id=rating.id,
|
|
scheduled_date=scheduled_datetime,
|
|
subtype_id=subtype_id,
|
|
subtype_xmlid=subtype_xmlid,
|
|
)
|
|
return rating
|
|
|
|
@api.returns('mail.message', lambda value: value.id)
|
|
def message_post(self, **kwargs):
|
|
rating_id = kwargs.pop('rating_id', False)
|
|
rating_value = kwargs.pop('rating_value', False)
|
|
rating_feedback = kwargs.pop('rating_feedback', False)
|
|
message = super(MailThread, self).message_post(**kwargs)
|
|
|
|
# create rating.rating record linked to given rating_value. Using sudo as portal users may have
|
|
# rights to create messages and therefore ratings (security should be checked beforehand)
|
|
if rating_value:
|
|
self.env['rating.rating'].sudo().create({
|
|
'rating': float(rating_value) if rating_value is not None else False,
|
|
'feedback': rating_feedback,
|
|
'res_model_id': self.env['ir.model']._get_id(self._name),
|
|
'res_id': self.id,
|
|
'message_id': message.id,
|
|
'consumed': True,
|
|
'partner_id': self.env.user.partner_id.id,
|
|
})
|
|
elif rating_id:
|
|
self.env['rating.rating'].browse(rating_id).write({'message_id': message.id})
|
|
|
|
return message
|