diff --git a/addons/im_livechat/models/mail_channel.py b/addons/im_livechat/models/mail_channel.py
index 9476b58b8b6..b62a7c570aa 100644
--- a/addons/im_livechat/models/mail_channel.py
+++ b/addons/im_livechat/models/mail_channel.py
@@ -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'})
diff --git a/addons/rating/models/mail_thread.py b/addons/rating/models/mail_thread.py
index c9196018c6c..e33f6e0c334 100644
--- a/addons/rating/models/mail_thread.py
+++ b/addons/rating/models/mail_thread.py
@@ -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(
+ "
%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)
diff --git a/addons/rating/models/rating_mixin.py b/addons/rating/models/rating_mixin.py
index b06797afd2e..f9dabc1fafa 100644
--- a/addons/rating/models/rating_mixin.py
+++ b/addons/rating/models/rating_mixin.py
@@ -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(
- "
%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
diff --git a/addons/test_mail_full/models/test_mail_models_mail.py b/addons/test_mail_full/models/test_mail_models_mail.py
index dba1e9a5137..12db1ac29c8 100644
--- a/addons/test_mail_full/models/test_mail_models_mail.py
+++ b/addons/test_mail_full/models/test_mail_models_mail.py
@@ -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()
diff --git a/addons/test_mail_full/security/ir.model.access.csv b/addons/test_mail_full/security/ir.model.access.csv
index c16510115dd..89a38134b72 100644
--- a/addons/test_mail_full/security/ir.model.access.csv
+++ b/addons/test_mail_full/security/ir.model.access.csv
@@ -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
diff --git a/addons/test_mail_full/tests/test_rating.py b/addons/test_mail_full/tests/test_rating.py
index 546a1aa7ecf..d0a16a30ecb 100644
--- a/addons/test_mail_full/tests/test_rating.py
+++ b/addons/test_mail_full/tests/test_rating.py
@@ -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)
diff --git a/addons/website_sale/models/product_template.py b/addons/website_sale/models/product_template.py
index 7d9946a28b0..88961ad0531 100644
--- a/addons/website_sale/models/product_template.py
+++ b/addons/website_sale/models/product_template.py
@@ -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'
diff --git a/addons/website_slides/models/slide_channel.py b/addons/website_slides/models/slide_channel.py
index 6530d7ef615..2e8e89fd9ba 100644
--- a/addons/website_slides/models/slide_channel.py
+++ b/addons/website_slides/models/slide_channel.py
@@ -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',