From c04e0fa10a61a034ec319fb379fa5a21ad049c9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 22 Feb 2022 14:43:30 +0000 Subject: [PATCH] [MOV] rating: split rating mixin It currently holds two different mixins. Let us split it into two files in order to have a file per model. Also a quick reordering of rating model is performed just to sort a bit things Task-2728564 Part-of: odoo/odoo#82792 --- addons/rating/models/__init__.py | 1 + addons/rating/models/rating.py | 19 ++--- addons/rating/models/rating_mixin.py | 71 +----------------- addons/rating/models/rating_parent_mixin.py | 81 +++++++++++++++++++++ 4 files changed, 93 insertions(+), 79 deletions(-) create mode 100644 addons/rating/models/rating_parent_mixin.py diff --git a/addons/rating/models/__init__.py b/addons/rating/models/__init__.py index 44c09740ff3..4492fc6fc0f 100644 --- a/addons/rating/models/__init__.py +++ b/addons/rating/models/__init__.py @@ -2,5 +2,6 @@ from . import rating from . import rating_mixin +from . import rating_parent_mixin from . import mail_thread from . import mail_message diff --git a/addons/rating/models/rating.py b/addons/rating/models/rating.py index a10c839595d..78d412531b2 100644 --- a/addons/rating/models/rating.py +++ b/addons/rating/models/rating.py @@ -23,15 +23,6 @@ class Rating(models.Model): _description = "Rating" _order = 'write_date desc' _rec_name = 'res_name' - _sql_constraints = [ - ('rating_range', 'check(rating >= 0 and rating <= 5)', 'Rating should be between 0 and 5'), - ] - - @api.depends('res_model', 'res_id') - def _compute_res_name(self): - for rating in self: - name = self.env[rating.res_model].sudo().browse(rating.res_id).name_get() - rating.res_name = name and name[0][1] or ('%s/%s') % (rating.res_model, rating.res_id) @api.model def _default_access_token(self): @@ -71,6 +62,16 @@ class Rating(models.Model): access_token = fields.Char('Security Token', default=_default_access_token, help="Access token to set the rating of the value") consumed = fields.Boolean(string="Filled Rating", help="Enabled if the rating has been filled.") + _sql_constraints = [ + ('rating_range', 'check(rating >= 0 and rating <= 5)', 'Rating should be between 0 and 5'), + ] + + @api.depends('res_model', 'res_id') + def _compute_res_name(self): + for rating in self: + name = self.env[rating.res_model].sudo().browse(rating.res_id).name_get() + rating.res_name = name and name[0][1] or ('%s/%s') % (rating.res_model, rating.res_id) + @api.depends('res_model', 'res_id') def _compute_resource_ref(self): for rating in self: diff --git a/addons/rating/models/rating_mixin.py b/addons/rating/models/rating_mixin.py index 13cbe90e000..4b6aad4668e 100644 --- a/addons/rating/models/rating_mixin.py +++ b/addons/rating/models/rating_mixin.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. + import operator -from collections import defaultdict -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, RATING_TEXT @@ -22,74 +21,6 @@ RATING_AVG_OK = 2.33 RATING_AVG_MIN = RATING_LIMIT_MIN -class RatingParentMixin(models.AbstractModel): - _name = 'rating.parent.mixin' - _description = "Rating Parent Mixin" - _rating_satisfaction_days = False # Number of last days used to compute parent satisfaction. Set to False to include all existing rating. - - rating_ids = fields.One2many( - 'rating.rating', 'parent_res_id', string='Ratings', - auto_join=True, groups='base.group_user', - domain=lambda self: [('parent_res_model', '=', self._name)]) - rating_percentage_satisfaction = fields.Integer( - "Rating Satisfaction", - compute="_compute_rating_percentage_satisfaction", compute_sudo=True, - store=False, help="Percentage of happy ratings") - rating_count = fields.Integer(string='# Ratings', compute="_compute_rating_percentage_satisfaction", compute_sudo=True) - rating_avg = fields.Float('Average Rating', groups='base.group_user', - compute='_compute_rating_percentage_satisfaction', compute_sudo=True, search='_search_rating_avg') - rating_avg_percentage = fields.Float('Average Rating (%)', groups='base.group_user', - compute='_compute_rating_percentage_satisfaction', compute_sudo=True) - rating_last_value = fields.Float('Rating Last Value', groups='base.group_user', related='rating_ids.rating') - - @api.depends('rating_ids.rating', 'rating_ids.consumed') - def _compute_rating_percentage_satisfaction(self): - # build domain and fetch data - domain = [('parent_res_model', '=', self._name), ('parent_res_id', 'in', self.ids), ('rating', '>=', RATING_LIMIT_MIN), ('consumed', '=', True)] - if self._rating_satisfaction_days: - domain += [('write_date', '>=', fields.Datetime.to_string(fields.datetime.now() - timedelta(days=self._rating_satisfaction_days)))] - data = self.env['rating.rating'].read_group(domain, ['parent_res_id', 'rating'], ['parent_res_id', 'rating'], lazy=False) - - # get repartition of grades per parent id - default_grades = {'great': 0, 'okay': 0, 'bad': 0} - grades_per_parent = dict((parent_id, dict(default_grades)) for parent_id in self.ids) # map: {parent_id: {'great': 0, 'bad': 0, 'ok': 0}} - rating_scores_per_parent = defaultdict(int) # contains the total of the rating values per record - for item in data: - parent_id = item['parent_res_id'] - rating = item['rating'] - if rating > RATING_LIMIT_OK: - grades_per_parent[parent_id]['great'] += item['__count'] - elif rating > RATING_LIMIT_MIN: - grades_per_parent[parent_id]['okay'] += item['__count'] - else: - grades_per_parent[parent_id]['bad'] += item['__count'] - rating_scores_per_parent[parent_id] += rating * item['__count'] - - # compute percentage per parent - for record in self: - repartition = grades_per_parent.get(record.id, default_grades) - rating_count = sum(repartition.values()) - record.rating_count = rating_count - record.rating_percentage_satisfaction = repartition['great'] * 100 / rating_count if rating_count else -1 - record.rating_avg = rating_scores_per_parent[record.id] / rating_count if rating_count else 0 - record.rating_avg_percentage = record.rating_avg / 5 - - 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) - domain = [('parent_res_model', '=', self._name), ('consumed', '=', True), ('rating', '>=', RATING_LIMIT_MIN)] - if self._rating_satisfaction_days: - min_date = fields.datetime.now() - timedelta(days=self._rating_satisfaction_days) - domain = expression.AND([domain, [('write_date', '>=', fields.Datetime.to_string(min_date))]]) - rating_read_group = self.env['rating.rating'].sudo().read_group(domain, ['parent_res_id', 'rating_avg:avg(rating)'], ['parent_res_id']) - parent_res_ids = [ - res['parent_res_id'] - for res in rating_read_group - if OPERATOR_MAPPING[operator](float_compare(res['rating_avg'], value, 2), 0) - ] - return [('id', 'in', parent_res_ids)] - - class RatingMixin(models.AbstractModel): _name = 'rating.mixin' _description = "Rating Mixin" diff --git a/addons/rating/models/rating_parent_mixin.py b/addons/rating/models/rating_parent_mixin.py new file mode 100644 index 00000000000..62c040c4ba1 --- /dev/null +++ b/addons/rating/models/rating_parent_mixin.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import operator + +from collections import defaultdict +from datetime import timedelta + +from odoo import api, fields, models +from odoo.addons.rating.models.rating import RATING_LIMIT_OK, RATING_LIMIT_MIN +from odoo.addons.rating.models.rating_mixin import OPERATOR_MAPPING +from odoo.osv import expression +from odoo.tools.float_utils import float_compare + + +class RatingParentMixin(models.AbstractModel): + _name = 'rating.parent.mixin' + _description = "Rating Parent Mixin" + _rating_satisfaction_days = False # Number of last days used to compute parent satisfaction. Set to False to include all existing rating. + + rating_ids = fields.One2many( + 'rating.rating', 'parent_res_id', string='Ratings', + auto_join=True, groups='base.group_user', + domain=lambda self: [('parent_res_model', '=', self._name)]) + rating_percentage_satisfaction = fields.Integer( + "Rating Satisfaction", + compute="_compute_rating_percentage_satisfaction", compute_sudo=True, + store=False, help="Percentage of happy ratings") + rating_count = fields.Integer(string='# Ratings', compute="_compute_rating_percentage_satisfaction", compute_sudo=True) + rating_avg = fields.Float('Average Rating', groups='base.group_user', + compute='_compute_rating_percentage_satisfaction', compute_sudo=True, search='_search_rating_avg') + rating_avg_percentage = fields.Float('Average Rating (%)', groups='base.group_user', + compute='_compute_rating_percentage_satisfaction', compute_sudo=True) + rating_last_value = fields.Float('Rating Last Value', groups='base.group_user', related='rating_ids.rating') + + @api.depends('rating_ids.rating', 'rating_ids.consumed') + def _compute_rating_percentage_satisfaction(self): + # build domain and fetch data + domain = [('parent_res_model', '=', self._name), ('parent_res_id', 'in', self.ids), ('rating', '>=', RATING_LIMIT_MIN), ('consumed', '=', True)] + if self._rating_satisfaction_days: + domain += [('write_date', '>=', fields.Datetime.to_string(fields.datetime.now() - timedelta(days=self._rating_satisfaction_days)))] + data = self.env['rating.rating'].read_group(domain, ['parent_res_id', 'rating'], ['parent_res_id', 'rating'], lazy=False) + + # get repartition of grades per parent id + default_grades = {'great': 0, 'okay': 0, 'bad': 0} + grades_per_parent = dict((parent_id, dict(default_grades)) for parent_id in self.ids) # map: {parent_id: {'great': 0, 'bad': 0, 'ok': 0}} + rating_scores_per_parent = defaultdict(int) # contains the total of the rating values per record + for item in data: + parent_id = item['parent_res_id'] + rating = item['rating'] + if rating > RATING_LIMIT_OK: + grades_per_parent[parent_id]['great'] += item['__count'] + elif rating > RATING_LIMIT_MIN: + grades_per_parent[parent_id]['okay'] += item['__count'] + else: + grades_per_parent[parent_id]['bad'] += item['__count'] + rating_scores_per_parent[parent_id] += rating * item['__count'] + + # compute percentage per parent + for record in self: + repartition = grades_per_parent.get(record.id, default_grades) + rating_count = sum(repartition.values()) + record.rating_count = rating_count + record.rating_percentage_satisfaction = repartition['great'] * 100 / rating_count if rating_count else -1 + record.rating_avg = rating_scores_per_parent[record.id] / rating_count if rating_count else 0 + record.rating_avg_percentage = record.rating_avg / 5 + + 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) + domain = [('parent_res_model', '=', self._name), ('consumed', '=', True), ('rating', '>=', RATING_LIMIT_MIN)] + if self._rating_satisfaction_days: + min_date = fields.datetime.now() - timedelta(days=self._rating_satisfaction_days) + domain = expression.AND([domain, [('write_date', '>=', fields.Datetime.to_string(min_date))]]) + rating_read_group = self.env['rating.rating'].sudo().read_group(domain, ['parent_res_id', 'rating_avg:avg(rating)'], ['parent_res_id']) + parent_res_ids = [ + res['parent_res_id'] + for res in rating_read_group + if OPERATOR_MAPPING[operator](float_compare(res['rating_avg'], value, 2), 0) + ] + return [('id', 'in', parent_res_ids)]