[IMP] mail, rating, various: support base rating feature in mail.thread
Various: im_livechat, rating, test_mail_full, website_{sale|slides}
Allows to rate any record extending mail_thread. This change was needed because
rating template could be created for any models, even those not inheriting from
rating.mixin. If sent on a record of such model, it was crashing when the
controllers using rating mixin features were used. This is no longer the case.
This commit moves the code from the mixin rating.mixin to mail.thread and
adapts the test for testing rating submission with and without the mixin.
Base behavior accepts rating and provides an access to ratings through the
'rating_ids' field. Rating.mixin inherits now from mail.thread to ease
computation, and adds statistics and some advanced capabilities.
Inheritance of some model have been reordered now that rating is build on
top of mail.thread.
Task-2674649
Part-of: odoo/odoo#103966
This commit is contained in:
committed by
Thibault Delavallée
parent
55a1430016
commit
c3873cb1e1
@@ -14,7 +14,7 @@ class MailChannel(models.Model):
|
||||
"""
|
||||
|
||||
_name = 'mail.channel'
|
||||
_inherit = ['mail.channel', 'rating.mixin']
|
||||
_inherit = ['rating.mixin', 'mail.channel']
|
||||
|
||||
anonymous_name = fields.Char('Anonymous Name')
|
||||
channel_type = fields.Selection(selection_add=[('livechat', 'Livechat Conversation')], ondelete={'livechat': 'cascade'})
|
||||
|
||||
@@ -1,12 +1,175 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, models, _
|
||||
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 issubclass(type(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)
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
# -*- 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
|
||||
from odoo import api, fields, models
|
||||
from odoo.addons.rating.models import rating_data
|
||||
from odoo.osv import expression
|
||||
from odoo.tools.float_utils import float_compare, float_round
|
||||
|
||||
|
||||
class RatingMixin(models.AbstractModel):
|
||||
"""This mixin adds rating statistics to mail.thread that already support ratings."""
|
||||
_name = 'rating.mixin'
|
||||
_description = "Rating Mixin"
|
||||
_inherit = 'mail.thread'
|
||||
|
||||
rating_ids = fields.One2many('rating.rating', 'res_id', string='Rating', groups='base.group_user', domain=lambda self: [('res_model', '=', self._name)], auto_join=True)
|
||||
rating_last_value = fields.Float('Rating Last Value', groups='base.group_user', compute='_compute_rating_last_value', compute_sudo=True, store=True)
|
||||
rating_last_feedback = fields.Text('Rating Last Feedback', groups='base.group_user', related='rating_ids.feedback')
|
||||
rating_last_image = fields.Binary('Rating Last Image', groups='base.group_user', related='rating_ids.rating_image')
|
||||
@@ -110,16 +108,8 @@ class RatingMixin(models.AbstractModel):
|
||||
|
||||
return result
|
||||
|
||||
def unlink(self):
|
||||
""" When removing a record, its rating should be deleted too. """
|
||||
record_ids = self.ids
|
||||
result = super(RatingMixin, self).unlink()
|
||||
self.env['rating.rating'].sudo().search([('res_model', '=', self._name), ('res_id', 'in', record_ids)]).unlink()
|
||||
return result
|
||||
|
||||
def _rating_get_parent_field_name(self):
|
||||
"""Return the parent relation field name
|
||||
Should return a Many2One"""
|
||||
"""Return the parent relation field name. Should return a Many2One"""
|
||||
return None
|
||||
|
||||
def _rating_domain(self):
|
||||
@@ -128,134 +118,6 @@ class RatingMixin(models.AbstractModel):
|
||||
"""
|
||||
return ['&', '&', ('res_model', '=', self._name), ('res_id', 'in', self.ids), ('consumed', '=', True)]
|
||||
|
||||
def _rating_get_partner(self):
|
||||
""" Return the customer (partner) that performs the rating.
|
||||
|
||||
:return record: res.partner singleton
|
||||
"""
|
||||
if hasattr(self, 'partner_id') and self.partner_id:
|
||||
return self.partner_id
|
||||
return self.env['res.partner']
|
||||
|
||||
def _rating_get_operator(self):
|
||||
""" Return the operator (partner) that is the person who is rated.
|
||||
|
||||
:return record: res.partner singleton
|
||||
"""
|
||||
if hasattr(self, 'user_id') and self.user_id.partner_id:
|
||||
return self.user_id.partner_id
|
||||
return self.env['res.partner']
|
||||
|
||||
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()
|
||||
ratings = self.rating_ids.sudo().filtered(lambda x: x.partner_id.id == partner.id and not x.consumed)
|
||||
if not ratings:
|
||||
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,
|
||||
})
|
||||
else:
|
||||
rating = ratings[0]
|
||||
return rating.access_token
|
||||
|
||||
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 issubclass(type(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_thread.py
|
||||
body=rating_body,
|
||||
rating_id=rating.id,
|
||||
scheduled_date=scheduled_datetime,
|
||||
subtype_id=subtype_id,
|
||||
subtype_xmlid=subtype_xmlid,
|
||||
)
|
||||
return rating
|
||||
|
||||
def _rating_apply_get_default_subtype_id(self):
|
||||
return self.env['ir.model.data']._xmlid_to_res_id("mail.mt_comment")
|
||||
|
||||
def _rating_get_repartition(self, add_stats=False, domain=None):
|
||||
""" get the repatition of rating grade for the given res_ids.
|
||||
:param add_stats : flag to add stat to the result
|
||||
|
||||
@@ -14,7 +14,7 @@ class MailTestPortal(models.Model):
|
||||
'mail.thread',
|
||||
]
|
||||
|
||||
name = fields.Char()
|
||||
name = fields.Char('Name')
|
||||
partner_id = fields.Many2one('res.partner', 'Customer')
|
||||
user_id = fields.Many2one(comodel_name='res.users', string="Salesperson")
|
||||
|
||||
@@ -42,26 +42,25 @@ class MailTestPortalNoPartner(models.Model):
|
||||
|
||||
|
||||
class MailTestRating(models.Model):
|
||||
""" A model inheriting from mail.thread with some fields used for SMS
|
||||
""" A model inheriting from rating.mixin (which inherits from mail.thread) with some fields used for SMS
|
||||
gateway, like a partner, a specific mobile phone, ... """
|
||||
_description = 'Rating Model (ticket-like)'
|
||||
_name = 'mail.test.rating'
|
||||
_inherit = [
|
||||
'mail.thread',
|
||||
'mail.activity.mixin',
|
||||
'rating.mixin',
|
||||
'mail.activity.mixin',
|
||||
'portal.mixin',
|
||||
]
|
||||
_mailing_enabled = True
|
||||
_order = 'name asc, id asc'
|
||||
|
||||
name = fields.Char()
|
||||
subject = fields.Char()
|
||||
name = fields.Char('Name')
|
||||
subject = fields.Char('Subject')
|
||||
company_id = fields.Many2one('res.company', 'Company')
|
||||
customer_id = fields.Many2one('res.partner', 'Customer')
|
||||
email_from = fields.Char(compute='_compute_email_from', precompute=True, readonly=False, store=True)
|
||||
mobile_nbr = fields.Char(compute='_compute_mobile_nbr', precompute=True, readonly=False, store=True)
|
||||
phone_nbr = fields.Char(compute='_compute_phone_nbr', precompute=True, readonly=False, store=True)
|
||||
email_from = fields.Char('From', compute='_compute_email_from', precompute=True, readonly=False, store=True)
|
||||
mobile_nbr = fields.Char('Mobile', compute='_compute_mobile_nbr', precompute=True, readonly=False, store=True)
|
||||
phone_nbr = fields.Char('Phone Number', compute='_compute_phone_nbr', precompute=True, readonly=False, store=True)
|
||||
user_id = fields.Many2one('res.users', 'Responsible', tracking=1)
|
||||
|
||||
@api.depends('customer_id')
|
||||
@@ -96,3 +95,27 @@ class MailTestRating(models.Model):
|
||||
|
||||
def _rating_get_partner(self):
|
||||
return self.customer_id
|
||||
|
||||
|
||||
class MailTestRatingThread(models.Model):
|
||||
"""A model inheriting from mail.thread with minimal fields for testing
|
||||
rating submission without the rating mixin but with the same test code:
|
||||
|
||||
- partner_id: value returned by the base _rating_get_partner method
|
||||
- user_id: value returned by the base _rating_get_operator method
|
||||
"""
|
||||
|
||||
_description = 'Model for testing rating without the rating mixin'
|
||||
_name = 'mail.test.rating.thread'
|
||||
_inherit = 'mail.thread'
|
||||
_order = 'name asc, id asc'
|
||||
|
||||
name = fields.Char('Name')
|
||||
customer_id = fields.Many2one('res.partner', 'Customer')
|
||||
user_id = fields.Many2one('res.users', 'Responsible', tracking=1)
|
||||
|
||||
def _mail_get_partner_fields(self):
|
||||
return ['customer_id']
|
||||
|
||||
def _rating_get_partner(self):
|
||||
return self.customer_id or super()._rating_get_partner()
|
||||
|
||||
@@ -6,3 +6,6 @@ access_mail_test_portal_no_partner_user,mail.test.portal.no.partner.user,model_m
|
||||
access_mail_test_rating_all,mail.test.rating.all,model_mail_test_rating,,0,0,0,0
|
||||
access_mail_test_rating_portal,mail.test.rating.portal,model_mail_test_rating,base.group_portal,1,0,0,0
|
||||
access_mail_test_rating_user,mail.test.rating.user,model_mail_test_rating,base.group_user,1,1,1,1
|
||||
access_mail_test_rating_thread_all,mail.test.rating.thread.all,model_mail_test_rating_thread,,0,0,0,0
|
||||
access_mail_test_rating_thread_portal,mail.test.rating.thread.portal,model_mail_test_rating_thread,base.group_portal,1,0,0,0
|
||||
access_mail_test_rating_thread_user,mail.test.rating.thread.user,model_mail_test_rating_thread,base.group_user,1,1,1,1
|
||||
|
||||
|
@@ -20,88 +20,104 @@ class TestRatingCommon(TestMailFullCommon, TestSMSRecipients):
|
||||
'name': 'Test Rating',
|
||||
'user_id': cls.user_admin.id,
|
||||
})
|
||||
cls.record_rating_thread = cls.env['mail.test.rating.thread'].create({
|
||||
'customer_id': cls.partner_1.id,
|
||||
'name': 'Test rating without rating mixin',
|
||||
'user_id': cls.user_admin.id,
|
||||
})
|
||||
|
||||
|
||||
@tagged('rating')
|
||||
class TestRatingFlow(TestRatingCommon):
|
||||
|
||||
def test_initial_values(self):
|
||||
record_rating = self.record_rating.with_env(self.env)
|
||||
self.assertFalse(record_rating.rating_ids)
|
||||
self.assertEqual(record_rating.message_partner_ids, self.partner_admin)
|
||||
self.assertEqual(len(record_rating.message_ids), 1)
|
||||
for record_rating in [self.record_rating, self.record_rating_thread]:
|
||||
record_rating = record_rating.with_env(self.env)
|
||||
self.assertFalse(record_rating.rating_ids)
|
||||
self.assertEqual(record_rating.message_partner_ids, self.partner_admin)
|
||||
self.assertEqual(len(record_rating.message_ids), 1)
|
||||
|
||||
@users('employee')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_rating_prepare(self):
|
||||
record_rating = self.record_rating.with_env(self.env)
|
||||
for record_rating, desc in ((self.record_rating, 'With rating mixin'),
|
||||
(self.record_rating_thread, 'Without rating mixin')):
|
||||
with self.subTest(desc):
|
||||
record_rating = record_rating.with_env(self.env)
|
||||
|
||||
# prepare rating token
|
||||
access_token = record_rating._rating_get_access_token()
|
||||
# prepare rating token
|
||||
access_token = record_rating._rating_get_access_token()
|
||||
|
||||
# check rating creation
|
||||
rating = record_rating.rating_ids
|
||||
self.assertEqual(rating.access_token, access_token)
|
||||
self.assertFalse(rating.consumed)
|
||||
self.assertFalse(rating.is_internal)
|
||||
self.assertEqual(rating.partner_id, self.partner_1)
|
||||
self.assertEqual(rating.rated_partner_id, self.user_admin.partner_id)
|
||||
self.assertFalse(rating.rating)
|
||||
# check rating creation
|
||||
rating = record_rating.rating_ids
|
||||
self.assertEqual(rating.access_token, access_token)
|
||||
self.assertFalse(rating.consumed)
|
||||
self.assertFalse(rating.is_internal)
|
||||
self.assertEqual(rating.partner_id, self.partner_1)
|
||||
self.assertEqual(rating.rated_partner_id, self.user_admin.partner_id)
|
||||
self.assertFalse(rating.rating)
|
||||
|
||||
@users('employee')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_rating_rating_apply(self):
|
||||
record_rating = self.record_rating.with_env(self.env)
|
||||
record_messages = record_rating.message_ids
|
||||
for record_rating, expected_subtype, is_rating_mixin_test in (
|
||||
(self.record_rating_thread, self.env.ref('mail.mt_comment'), False),
|
||||
(self.record_rating, self.env.ref('test_mail_full.mt_mail_test_rating_rating_done'), True),
|
||||
):
|
||||
with self.subTest('With rating mixin' if is_rating_mixin_test else 'Without rating mixin'):
|
||||
record_rating = record_rating.with_env(self.env)
|
||||
record_messages = record_rating.message_ids
|
||||
|
||||
# prepare rating token
|
||||
access_token = record_rating._rating_get_access_token()
|
||||
# prepare rating token
|
||||
access_token = record_rating._rating_get_access_token()
|
||||
|
||||
# simulate an email click: notification should be delayed
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False), self.mock_mail_app():
|
||||
record_rating.rating_apply(5, token=access_token, feedback='Top Feedback', notify_delay_send=True)
|
||||
message = record_rating.message_ids[0]
|
||||
rating = record_rating.rating_ids
|
||||
# simulate an email click: notification should be delayed
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False), self.mock_mail_app():
|
||||
record_rating.rating_apply(5, token=access_token, feedback='Top Feedback', notify_delay_send=True)
|
||||
message = record_rating.message_ids[0]
|
||||
rating = record_rating.rating_ids
|
||||
|
||||
# check posted message
|
||||
self.assertEqual(record_rating.message_ids, record_messages + message)
|
||||
self.assertIn('Top Feedback', message.body)
|
||||
self.assertIn('/rating/static/src/img/rating_5.png', message.body)
|
||||
self.assertEqual(message.author_id, self.partner_1)
|
||||
self.assertEqual(message.rating_ids, rating)
|
||||
self.assertFalse(message.notified_partner_ids)
|
||||
self.assertEqual(message.subtype_id, self.env.ref('test_mail_full.mt_mail_test_rating_rating_done'))
|
||||
# check posted message
|
||||
self.assertEqual(record_rating.message_ids, record_messages + message)
|
||||
self.assertIn('Top Feedback', message.body)
|
||||
self.assertIn('/rating/static/src/img/rating_5.png', message.body)
|
||||
self.assertEqual(message.author_id, self.partner_1)
|
||||
self.assertEqual(message.rating_ids, rating)
|
||||
self.assertFalse(message.notified_partner_ids)
|
||||
self.assertEqual(message.subtype_id, expected_subtype)
|
||||
|
||||
# check rating update
|
||||
self.assertTrue(rating.consumed)
|
||||
self.assertEqual(rating.feedback, 'Top Feedback')
|
||||
self.assertEqual(rating.message_id, message)
|
||||
self.assertEqual(rating.rating, 5)
|
||||
self.assertEqual(record_rating.rating_last_value, 5)
|
||||
# check rating update
|
||||
self.assertTrue(rating.consumed)
|
||||
self.assertEqual(rating.feedback, 'Top Feedback')
|
||||
self.assertEqual(rating.message_id, message)
|
||||
self.assertEqual(rating.rating, 5)
|
||||
if is_rating_mixin_test:
|
||||
self.assertEqual(record_rating.rating_last_value, 5)
|
||||
|
||||
# give a feedback: send notifications (notify_delay_send set to False)
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False), self.mock_mail_app():
|
||||
record_rating.rating_apply(1, token=access_token, feedback='Bad Feedback')
|
||||
# give a feedback: send notifications (notify_delay_send set to False)
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False), self.mock_mail_app():
|
||||
record_rating.rating_apply(1, token=access_token, feedback='Bad Feedback')
|
||||
|
||||
# check posted message: message is updated
|
||||
update_message = record_rating.message_ids[0]
|
||||
self.assertEqual(update_message, message, 'Should update first message')
|
||||
self.assertEqual(record_rating.message_ids, record_messages + update_message)
|
||||
self.assertIn('Bad Feedback', update_message.body)
|
||||
self.assertIn('/rating/static/src/img/rating_1.png', update_message.body)
|
||||
self.assertEqual(update_message.author_id, self.partner_1)
|
||||
self.assertEqual(update_message.rating_ids, rating)
|
||||
self.assertEqual(update_message.notified_partner_ids, self.partner_admin)
|
||||
self.assertEqual(update_message.subtype_id, self.env.ref("test_mail_full.mt_mail_test_rating_rating_done"))
|
||||
# check posted message: message is updated
|
||||
update_message = record_rating.message_ids[0]
|
||||
self.assertEqual(update_message, message, 'Should update first message')
|
||||
self.assertEqual(record_rating.message_ids, record_messages + update_message)
|
||||
self.assertIn('Bad Feedback', update_message.body)
|
||||
self.assertIn('/rating/static/src/img/rating_1.png', update_message.body)
|
||||
self.assertEqual(update_message.author_id, self.partner_1)
|
||||
self.assertEqual(update_message.rating_ids, rating)
|
||||
self.assertEqual(update_message.notified_partner_ids, self.partner_admin)
|
||||
self.assertEqual(update_message.subtype_id, expected_subtype)
|
||||
|
||||
# check rating update
|
||||
new_rating = record_rating.rating_ids
|
||||
self.assertEqual(new_rating, rating, 'Should update first rating')
|
||||
self.assertTrue(new_rating.consumed)
|
||||
self.assertEqual(new_rating.feedback, 'Bad Feedback')
|
||||
self.assertEqual(new_rating.message_id, update_message)
|
||||
self.assertEqual(new_rating.rating, 1)
|
||||
self.assertEqual(record_rating.rating_last_value, 1)
|
||||
# check rating update
|
||||
new_rating = record_rating.rating_ids
|
||||
self.assertEqual(new_rating, rating, 'Should update first rating')
|
||||
self.assertTrue(new_rating.consumed)
|
||||
self.assertEqual(new_rating.feedback, 'Bad Feedback')
|
||||
self.assertEqual(new_rating.message_id, update_message)
|
||||
self.assertEqual(new_rating.rating, 1)
|
||||
if is_rating_mixin_test:
|
||||
self.assertEqual(record_rating.rating_last_value, 1)
|
||||
|
||||
|
||||
@tagged('rating')
|
||||
@@ -131,37 +147,55 @@ class TestRatingMixin(TestRatingCommon):
|
||||
|
||||
@tagged('rating', 'mail_performance', 'post_install', '-at_install')
|
||||
class TestRatingPerformance(TestRatingCommon):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.RECORD_COUNT = 100
|
||||
cls.partners = cls.env['res.partner'].sudo().create([
|
||||
{'name': 'Jean-Luc %s' % (idx), 'email': 'jean-luc-%s@opoo.com' % (idx)}
|
||||
for idx in range(cls.RECORD_COUNT)])
|
||||
|
||||
def apply_ratings(self, rate):
|
||||
for record in self.record_ratings:
|
||||
access_token = record._rating_get_access_token()
|
||||
record.rating_apply(rate, token=access_token)
|
||||
self.flush_tracking()
|
||||
|
||||
def create_ratings(self, model):
|
||||
self.record_ratings = self.env[model].create([{
|
||||
'customer_id': self.partners[idx].id,
|
||||
'name': 'Test Rating',
|
||||
'user_id': self.user_admin.id,
|
||||
} for idx in range(self.RECORD_COUNT)])
|
||||
self.flush_tracking()
|
||||
|
||||
@users('employee')
|
||||
@warmup
|
||||
def test_rating_last_value_perfs(self):
|
||||
RECORD_COUNT = 100
|
||||
partners = self.env['res.partner'].sudo().create([
|
||||
{'name': 'Jean-Luc %s' % (idx), 'email': 'jean-luc-%s@opoo.com' % (idx)} for idx in range(RECORD_COUNT)])
|
||||
with self.assertQueryCount(employee=1213): # tmf 1213 / com 1217
|
||||
self.create_ratings('mail.test.rating.thread')
|
||||
|
||||
with self.assertQueryCount(employee=1514): # tmf 1514 / com 1514
|
||||
record_ratings = self.env['mail.test.rating'].create([{
|
||||
'customer_id': partners[idx].id,
|
||||
'name': 'Test Rating',
|
||||
'user_id': self.user_admin.id,
|
||||
} for idx in range(RECORD_COUNT)])
|
||||
self.flush_tracking()
|
||||
with self.assertQueryCount(employee=1801): # tmf 1801
|
||||
self.apply_ratings(1)
|
||||
|
||||
with self.assertQueryCount(employee=1700): # tmf 1700
|
||||
self.apply_ratings(5)
|
||||
|
||||
@users('employee')
|
||||
@warmup
|
||||
def test_rating_last_value_perfs_with_rating_mixin(self):
|
||||
with self.assertQueryCount(employee=1312): # tmf 1312 / com 1312
|
||||
self.create_ratings('mail.test.rating')
|
||||
|
||||
with self.assertQueryCount(employee=2004): # tmf 2004
|
||||
for record in record_ratings:
|
||||
access_token = record._rating_get_access_token()
|
||||
record.rating_apply(1, token=access_token)
|
||||
self.flush_tracking()
|
||||
self.apply_ratings(1)
|
||||
|
||||
with self.assertQueryCount(employee=2003): # tmf 2003
|
||||
for record in record_ratings:
|
||||
access_token = record._rating_get_access_token()
|
||||
record.rating_apply(5, token=access_token)
|
||||
self.flush_tracking()
|
||||
self.apply_ratings(5)
|
||||
|
||||
with self.assertQueryCount(employee=1):
|
||||
record_ratings._compute_rating_last_value()
|
||||
vals = [val == 5 for val in record_ratings.mapped('rating_last_value')]
|
||||
self.record_ratings._compute_rating_last_value()
|
||||
vals = (val == 5 for val in self.record_ratings.mapped('rating_last_value'))
|
||||
self.assertTrue(all(vals), "The last rating is kept.")
|
||||
|
||||
|
||||
@@ -169,10 +203,14 @@ class TestRatingPerformance(TestRatingCommon):
|
||||
class TestRatingRoutes(HttpCase, TestRatingCommon):
|
||||
|
||||
def test_open_rating_route(self):
|
||||
access_token = self.record_rating._rating_get_access_token()
|
||||
self.url_open(f"/rate/{access_token}/5")
|
||||
for record_rating, is_rating_mixin_test in ((self.record_rating_thread, False),
|
||||
(self.record_rating, True)):
|
||||
with self.subTest('With rating mixin' if is_rating_mixin_test else 'Without rating mixin'):
|
||||
access_token = record_rating._rating_get_access_token()
|
||||
self.url_open(f"/rate/{access_token}/5")
|
||||
|
||||
rating = self.record_rating.rating_ids
|
||||
self.assertTrue(rating.consumed)
|
||||
self.assertEqual(rating.rating, 5)
|
||||
self.assertEqual(self.record_rating.rating_last_value, 5)
|
||||
rating = record_rating.rating_ids
|
||||
self.assertTrue(rating.consumed)
|
||||
self.assertEqual(rating.rating, 5)
|
||||
if is_rating_mixin_test:
|
||||
self.assertEqual(record_rating.rating_last_value, 5)
|
||||
|
||||
@@ -10,11 +10,11 @@ from odoo.osv import expression
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = [
|
||||
'rating.mixin',
|
||||
"product.template",
|
||||
"website.seo.metadata",
|
||||
'website.published.multi.mixin',
|
||||
'website.searchable.mixin',
|
||||
'rating.mixin',
|
||||
]
|
||||
_name = 'product.template'
|
||||
_mail_post_access = 'read'
|
||||
|
||||
@@ -187,7 +187,7 @@ class Channel(models.Model):
|
||||
_name = 'slide.channel'
|
||||
_description = 'Course'
|
||||
_inherit = [
|
||||
'mail.thread', 'rating.mixin',
|
||||
'rating.mixin',
|
||||
'mail.activity.mixin',
|
||||
'image.mixin',
|
||||
'website.cover_properties.mixin',
|
||||
|
||||
Reference in New Issue
Block a user