diff --git a/addons/project/tests/test_project_flow.py b/addons/project/tests/test_project_flow.py index f4f539d3ec4..0b88260ac1f 100644 --- a/addons/project/tests/test_project_flow.py +++ b/addons/project/tests/test_project_flow.py @@ -248,7 +248,9 @@ class TestProjectFlow(TestProjectCommon, MockEmail): self.assertEqual(rating_good.parent_res_id, self.project_pigs.id) self.assertEqual(self.project_goats.rating_percentage_satisfaction, -1) + self.assertEqual(self.project_goats.rating_avg, 0, 'Since there is no rating in this project, the Average Rating should be equal to 0.') self.assertEqual(self.project_pigs.rating_percentage_satisfaction, 0) # There is a rating but not a "great" on, just an "okay". + self.assertEqual(self.project_pigs.rating_avg, rating_bad.rating, 'Since there is only one rating the Average Rating should be equal to the rating value of this one.') # Consuming rating_good first_task.rating_apply(5, rating_good.access_token) @@ -257,10 +259,14 @@ class TestProjectFlow(TestProjectCommon, MockEmail): # Our One2Many is linked to a res_id (int) for which the orm doesn't create an inverse first_task.invalidate_cache() + rating_avg = (rating_good.rating + rating_bad.rating) / 2 self.assertEqual(first_task.rating_count, 2, "Task should have two ratings associated with it") + self.assertEqual(first_task.rating_avg_text, 'top') self.assertEqual(rating_good.parent_res_id, self.project_pigs.id) self.assertEqual(self.project_goats.rating_percentage_satisfaction, -1) self.assertEqual(self.project_pigs.rating_percentage_satisfaction, 50) + self.assertEqual(self.project_pigs.rating_avg, rating_avg) + self.assertEqual(self.project_pigs.rating_avg_percentage, rating_avg / 5) # We change the task from project_pigs to project_goats, ratings should be associated with the new project first_task.project_id = self.project_goats.id @@ -271,7 +277,9 @@ class TestProjectFlow(TestProjectCommon, MockEmail): self.assertEqual(rating_good.parent_res_id, self.project_goats.id) self.assertEqual(self.project_goats.rating_percentage_satisfaction, 50) + self.assertEqual(self.project_goats.rating_avg, rating_avg) self.assertEqual(self.project_pigs.rating_percentage_satisfaction, -1) + self.assertEqual(self.project_pigs.rating_avg, 0) def test_task_with_no_project(self): """ diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml index 9e2261a573c..0bb19d55a8a 100644 --- a/addons/project/views/project_views.xml +++ b/addons/project/views/project_views.xml @@ -33,7 +33,10 @@ - + + + + @@ -1121,8 +1124,8 @@ - - + + @@ -1181,10 +1184,10 @@
- - - - + + + +
diff --git a/addons/rating/models/rating_mixin.py b/addons/rating/models/rating_mixin.py index 46b9f43e5f5..ff5f55d1583 100644 --- a/addons/rating/models/rating_mixin.py +++ b/addons/rating/models/rating_mixin.py @@ -1,10 +1,21 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +import operator from datetime import timedelta from odoo import api, fields, models, tools from odoo.addons.rating.models.rating import RATING_LIMIT_SATISFIED, RATING_LIMIT_OK, RATING_LIMIT_MIN from odoo.osv import expression +from odoo.tools.float_utils import float_compare + +OPERATOR_MAPPING = { + '=': operator.eq, + '!=': operator.ne, + '<': operator.lt, + '<=': operator.le, + '>': operator.gt, + '>=': operator.ge, +} class RatingParentMixin(models.AbstractModel): @@ -59,7 +70,8 @@ class RatingMixin(models.AbstractModel): 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') rating_count = fields.Integer('Rating count', compute="_compute_rating_stats", compute_sudo=True) - rating_avg = fields.Float("Rating Average", compute='_compute_rating_stats', compute_sudo=True) + rating_avg = fields.Float("Average Rating", + compute='_compute_rating_stats', compute_sudo=True, search='_search_rating_avg') rating_percentage_satisfaction = fields.Float("Rating Satisfaction", compute='_compute_rating_satisfaction', compute_sudo=True) rating_last_text = fields.Selection(string="Rating Text", groups='base.group_user', related="rating_ids.rating_text") @@ -79,6 +91,19 @@ class RatingMixin(models.AbstractModel): record.rating_count = mapping.get(record.id, {}).get('rating_count', 0) record.rating_avg = mapping.get(record.id, {}).get('rating_avg', 0) + def _search_rating_avg(self, operator, value): + if operator not in OPERATOR_MAPPING: + raise NotImplementedError('This operator %s is not supported in this search method.' % operator) + rating_read_group = self.env['rating.rating'].sudo().read_group( + [('res_model', '=', self._name), ('consumed', '=', True), ('rating', '>=', RATING_LIMIT_MIN)], + ['res_id', 'rating_avg:avg(rating)'], ['res_id']) + res_ids = [ + res['res_id'] + for res in rating_read_group + if OPERATOR_MAPPING[operator](float_compare(res['rating_avg'], value, 2), 0) + ] + return [('id', 'in', res_ids)] + @api.depends('rating_ids.res_id', 'rating_ids.rating') def _compute_rating_satisfaction(self): """ Compute the rating satisfaction percentage, this is done separately from rating_count and rating_avg