[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:
Pierre-Yves Dufays
2023-02-23 14:53:50 +01:00
committed by Thibault Delavallée
parent 55a1430016
commit c3873cb1e1
8 changed files with 329 additions and 240 deletions
+1 -1
View File
@@ -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'})
+164 -1
View File
@@ -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)
+4 -142
View File
@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
6 access_mail_test_rating_all mail.test.rating.all model_mail_test_rating 0 0 0 0
7 access_mail_test_rating_portal mail.test.rating.portal model_mail_test_rating base.group_portal 1 0 0 0
8 access_mail_test_rating_user mail.test.rating.user model_mail_test_rating base.group_user 1 1 1 1
9 access_mail_test_rating_thread_all mail.test.rating.thread.all model_mail_test_rating_thread 0 0 0 0
10 access_mail_test_rating_thread_portal mail.test.rating.thread.portal model_mail_test_rating_thread base.group_portal 1 0 0 0
11 access_mail_test_rating_thread_user mail.test.rating.thread.user model_mail_test_rating_thread base.group_user 1 1 1 1
+123 -85
View File
@@ -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',