summaryrefslogtreecommitdiff
path: root/addons/rating/models
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/rating/models
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/rating/models')
-rw-r--r--addons/rating/models/__init__.py6
-rw-r--r--addons/rating/models/mail_message.py27
-rw-r--r--addons/rating/models/mail_thread.py29
-rw-r--r--addons/rating/models/rating.py176
-rw-r--r--addons/rating/models/rating_mixin.py273
5 files changed, 511 insertions, 0 deletions
diff --git a/addons/rating/models/__init__.py b/addons/rating/models/__init__.py
new file mode 100644
index 00000000..44c09740
--- /dev/null
+++ b/addons/rating/models/__init__.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+
+from . import rating
+from . import rating_mixin
+from . import mail_thread
+from . import mail_message
diff --git a/addons/rating/models/mail_message.py b/addons/rating/models/mail_message.py
new file mode 100644
index 00000000..015f21d3
--- /dev/null
+++ b/addons/rating/models/mail_message.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models
+
+
+class MailMessage(models.Model):
+ _inherit = 'mail.message'
+
+ rating_ids = fields.One2many('rating.rating', 'message_id', groups='base.group_user', string='Related ratings')
+ rating_value = fields.Float(
+ 'Rating Value', compute='_compute_rating_value', compute_sudo=True,
+ store=False, search='_search_rating_value')
+
+ @api.depends('rating_ids', 'rating_ids.rating')
+ def _compute_rating_value(self):
+ ratings = self.env['rating.rating'].search([('message_id', 'in', self.ids), ('consumed', '=', True)], order='create_date DESC')
+ mapping = dict((r.message_id.id, r.rating) for r in ratings)
+ for message in self:
+ message.rating_value = mapping.get(message.id, 0.0)
+
+ def _search_rating_value(self, operator, operand):
+ ratings = self.env['rating.rating'].sudo().search([
+ ('rating', operator, operand),
+ ('message_id', '!=', False)
+ ])
+ return [('id', 'in', ratings.mapped('message_id').ids)]
diff --git a/addons/rating/models/mail_thread.py b/addons/rating/models/mail_thread.py
new file mode 100644
index 00000000..e2182ea9
--- /dev/null
+++ b/addons/rating/models/mail_thread.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, models
+
+
+class MailThread(models.AbstractModel):
+ _inherit = 'mail.thread'
+
+ @api.returns('mail.message', lambda value: value.id)
+ def message_post(self, **kwargs):
+ rating_value = kwargs.pop('rating_value', False)
+ rating_feedback = kwargs.pop('rating_feedback', False)
+ message = super(MailThread, self).message_post(**kwargs)
+
+ # create rating.rating record linked to given rating_value. Using sudo as portal users may have
+ # rights to create messages and therefore ratings (security should be checked beforehand)
+ if rating_value:
+ ir_model = self.env['ir.model'].sudo().search([('model', '=', self._name)])
+ self.env['rating.rating'].sudo().create({
+ 'rating': float(rating_value) if rating_value is not None else False,
+ 'feedback': rating_feedback,
+ 'res_model_id': ir_model.id,
+ 'res_id': self.id,
+ 'message_id': message.id,
+ 'consumed': True,
+ 'partner_id': self.env.user.partner_id.id,
+ })
+ return message
diff --git a/addons/rating/models/rating.py b/addons/rating/models/rating.py
new file mode 100644
index 00000000..c5fa842b
--- /dev/null
+++ b/addons/rating/models/rating.py
@@ -0,0 +1,176 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import base64
+import uuid
+
+from odoo import api, fields, models
+
+from odoo.modules.module import get_resource_path
+
+RATING_LIMIT_SATISFIED = 5
+RATING_LIMIT_OK = 3
+RATING_LIMIT_MIN = 1
+
+
+class Rating(models.Model):
+ _name = "rating.rating"
+ _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):
+ return uuid.uuid4().hex
+
+ @api.model
+ def _selection_target_model(self):
+ return [(model.model, model.name) for model in self.env['ir.model'].search([])]
+
+ create_date = fields.Datetime(string="Submitted on")
+ res_name = fields.Char(string='Resource name', compute='_compute_res_name', store=True, help="The name of the rated resource.")
+ res_model_id = fields.Many2one('ir.model', 'Related Document Model', index=True, ondelete='cascade', help='Model of the followed resource')
+ res_model = fields.Char(string='Document Model', related='res_model_id.model', store=True, index=True, readonly=True)
+ res_id = fields.Integer(string='Document', required=True, help="Identifier of the rated object", index=True)
+ resource_ref = fields.Reference(
+ string='Resource Ref', selection='_selection_target_model',
+ compute='_compute_resource_ref', readonly=True)
+ parent_res_name = fields.Char('Parent Document Name', compute='_compute_parent_res_name', store=True)
+ parent_res_model_id = fields.Many2one('ir.model', 'Parent Related Document Model', index=True, ondelete='cascade')
+ parent_res_model = fields.Char('Parent Document Model', store=True, related='parent_res_model_id.model', index=True, readonly=False)
+ parent_res_id = fields.Integer('Parent Document', index=True)
+ parent_ref = fields.Reference(
+ string='Parent Ref', selection='_selection_target_model',
+ compute='_compute_parent_ref', readonly=True)
+ rated_partner_id = fields.Many2one('res.partner', string="Rated Operator", help="Owner of the rated resource")
+ partner_id = fields.Many2one('res.partner', string='Customer', help="Author of the rating")
+ rating = fields.Float(string="Rating Value", group_operator="avg", default=0, help="Rating value: 0=Unhappy, 5=Happy")
+ rating_image = fields.Binary('Image', compute='_compute_rating_image')
+ rating_text = fields.Selection([
+ ('satisfied', 'Satisfied'),
+ ('not_satisfied', 'Not satisfied'),
+ ('highly_dissatisfied', 'Highly dissatisfied'),
+ ('no_rating', 'No Rating yet')], string='Rating', store=True, compute='_compute_rating_text', readonly=True)
+ feedback = fields.Text('Comment', help="Reason of the rating")
+ message_id = fields.Many2one(
+ 'mail.message', string="Message",
+ index=True, ondelete='cascade',
+ help="Associated message when posting a review. Mainly used in website addons.")
+ is_internal = fields.Boolean('Visible Internally Only', readonly=False, related='message_id.is_internal', store=True)
+ 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.")
+
+ @api.depends('res_model', 'res_id')
+ def _compute_resource_ref(self):
+ for rating in self:
+ if rating.res_model and rating.res_model in self.env:
+ rating.resource_ref = '%s,%s' % (rating.res_model, rating.res_id or 0)
+ else:
+ rating.resource_ref = None
+
+ @api.depends('parent_res_model', 'parent_res_id')
+ def _compute_parent_ref(self):
+ for rating in self:
+ if rating.parent_res_model and rating.parent_res_model in self.env:
+ rating.parent_ref = '%s,%s' % (rating.parent_res_model, rating.parent_res_id or 0)
+ else:
+ rating.parent_ref = None
+
+ @api.depends('parent_res_model', 'parent_res_id')
+ def _compute_parent_res_name(self):
+ for rating in self:
+ name = False
+ if rating.parent_res_model and rating.parent_res_id:
+ name = self.env[rating.parent_res_model].sudo().browse(rating.parent_res_id).name_get()
+ name = name and name[0][1] or ('%s/%s') % (rating.parent_res_model, rating.parent_res_id)
+ rating.parent_res_name = name
+
+ def _get_rating_image_filename(self):
+ self.ensure_one()
+ if self.rating >= RATING_LIMIT_SATISFIED:
+ rating_int = 5
+ elif self.rating >= RATING_LIMIT_OK:
+ rating_int = 3
+ elif self.rating >= RATING_LIMIT_MIN:
+ rating_int = 1
+ else:
+ rating_int = 0
+ return 'rating_%s.png' % rating_int
+
+ def _compute_rating_image(self):
+ for rating in self:
+ try:
+ image_path = get_resource_path('rating', 'static/src/img', rating._get_rating_image_filename())
+ rating.rating_image = base64.b64encode(open(image_path, 'rb').read()) if image_path else False
+ except (IOError, OSError):
+ rating.rating_image = False
+
+ @api.depends('rating')
+ def _compute_rating_text(self):
+ for rating in self:
+ if rating.rating >= RATING_LIMIT_SATISFIED:
+ rating.rating_text = 'satisfied'
+ elif rating.rating >= RATING_LIMIT_OK:
+ rating.rating_text = 'not_satisfied'
+ elif rating.rating >= RATING_LIMIT_MIN:
+ rating.rating_text = 'highly_dissatisfied'
+ else:
+ rating.rating_text = 'no_rating'
+
+ @api.model
+ def create(self, values):
+ if values.get('res_model_id') and values.get('res_id'):
+ values.update(self._find_parent_data(values))
+ return super(Rating, self).create(values)
+
+ def write(self, values):
+ if values.get('res_model_id') and values.get('res_id'):
+ values.update(self._find_parent_data(values))
+ return super(Rating, self).write(values)
+
+ def unlink(self):
+ # OPW-2181568: Delete the chatter message too
+ self.env['mail.message'].search([('rating_ids', 'in', self.ids)]).unlink()
+ return super(Rating, self).unlink()
+
+ def _find_parent_data(self, values):
+ """ Determine the parent res_model/res_id, based on the values to create or write """
+ current_model_name = self.env['ir.model'].sudo().browse(values['res_model_id']).model
+ current_record = self.env[current_model_name].browse(values['res_id'])
+ data = {
+ 'parent_res_model_id': False,
+ 'parent_res_id': False,
+ }
+ if hasattr(current_record, '_rating_get_parent_field_name'):
+ current_record_parent = current_record._rating_get_parent_field_name()
+ if current_record_parent:
+ parent_res_model = getattr(current_record, current_record_parent)
+ data['parent_res_model_id'] = self.env['ir.model']._get(parent_res_model._name).id
+ data['parent_res_id'] = parent_res_model.id
+ return data
+
+ def reset(self):
+ for record in self:
+ record.write({
+ 'rating': 0,
+ 'access_token': record._default_access_token(),
+ 'feedback': False,
+ 'consumed': False,
+ })
+
+ def action_open_rated_object(self):
+ self.ensure_one()
+ return {
+ 'type': 'ir.actions.act_window',
+ 'res_model': self.res_model,
+ 'res_id': self.res_id,
+ 'views': [[False, 'form']]
+ }
diff --git a/addons/rating/models/rating_mixin.py b/addons/rating/models/rating_mixin.py
new file mode 100644
index 00000000..62d0d6c1
--- /dev/null
+++ b/addons/rating/models/rating_mixin.py
@@ -0,0 +1,273 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+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
+
+
+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")
+
+ @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', '>=', 1), ('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}}
+ 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']
+
+ # compute percentage per parent
+ for record in self:
+ repartition = grades_per_parent.get(record.id, default_grades)
+ record.rating_percentage_satisfaction = repartition['great'] * 100 / sum(repartition.values()) if sum(repartition.values()) else -1
+
+
+class RatingMixin(models.AbstractModel):
+ _name = 'rating.mixin'
+ _description = "Rating Mixin"
+
+ 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')
+ 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)
+
+ @api.depends('rating_ids.rating', 'rating_ids.consumed')
+ def _compute_rating_last_value(self):
+ for record in self:
+ ratings = self.env['rating.rating'].search([('res_model', '=', self._name), ('res_id', '=', record.id), ('consumed', '=', True)], limit=1)
+ record.rating_last_value = ratings and ratings.rating or 0
+
+ @api.depends('rating_ids.res_id', 'rating_ids.rating')
+ def _compute_rating_stats(self):
+ """ Compute avg and count in one query, as thoses fields will be used together most of the time. """
+ domain = expression.AND([self._rating_domain(), [('rating', '>=', RATING_LIMIT_MIN)]])
+ read_group_res = self.env['rating.rating'].read_group(domain, ['rating:avg'], groupby=['res_id'], lazy=False) # force average on rating column
+ mapping = {item['res_id']: {'rating_count': item['__count'], 'rating_avg': item['rating']} for item in read_group_res}
+ for record in self:
+ record.rating_count = mapping.get(record.id, {}).get('rating_count', 0)
+ record.rating_avg = mapping.get(record.id, {}).get('rating_avg', 0)
+
+ def write(self, values):
+ """ If the rated ressource name is modified, we should update the rating res_name too.
+ If the rated ressource parent is changed we should update the parent_res_id too"""
+ with self.env.norecompute():
+ result = super(RatingMixin, self).write(values)
+ for record in self:
+ if record._rec_name in values: # set the res_name of ratings to be recomputed
+ res_name_field = self.env['rating.rating']._fields['res_name']
+ self.env.add_to_compute(res_name_field, record.rating_ids)
+ if record._rating_get_parent_field_name() in values:
+ record.rating_ids.sudo().write({'parent_res_id': record[record._rating_get_parent_field_name()].id})
+
+ 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 None
+
+ def _rating_domain(self):
+ """ Returns a normalized domain on rating.rating to select the records to
+ include in count, avg, ... computation of current model.
+ """
+ return ['&', '&', ('res_model', '=', self._name), ('res_id', 'in', self.ids), ('consumed', '=', True)]
+
+ def rating_get_partner_id(self):
+ if hasattr(self, 'partner_id') and self.partner_id:
+ return self.partner_id
+ return self.env['res.partner']
+
+ def rating_get_rated_partner_id(self):
+ 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_id()
+ rated_partner = self.rating_get_rated_partner_id()
+ ratings = self.rating_ids.sudo().filtered(lambda x: x.partner_id.id == partner.id and not x.consumed)
+ if not ratings:
+ record_model_id = self.env['ir.model'].sudo().search([('model', '=', self._name)], limit=1).id
+ rating = self.env['rating.rating'].sudo().create({
+ 'partner_id': partner.id,
+ 'rated_partner_id': rated_partner.id,
+ 'res_model_id': record_model_id,
+ 'res_id': self.id,
+ 'is_internal': False,
+ })
+ else:
+ rating = ratings[0]
+ return rating.access_token
+
+ def rating_send_request(self, template, lang=False, subtype_id=False, force_send=True, composition_mode='comment', notif_layout=None):
+ """ This method send rating request by email, using a template given
+ in parameter.
+
+ :param template: a mail.template record used to compute the message body;
+ :param lang: optional lang; it can also be specified directly on the template
+ itself in the lang field;
+ :param subtype_id: optional subtype to use when creating the message; is
+ a note by default to avoid spamming followers;
+ :param force_send: whether to send the request directly or use the mail
+ queue cron (preferred option);
+ :param composition_mode: comment (message_post) or mass_mail (template.send_mail);
+ :param notif_layout: layout used to encapsulate the content when sending email;
+ """
+ if lang:
+ template = template.with_context(lang=lang)
+ if subtype_id is False:
+ subtype_id = self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note')
+ if force_send:
+ self = self.with_context(mail_notify_force_send=True) # default value is True, should be set to false if not?
+ for record in self:
+ record.message_post_with_template(
+ template.id,
+ composition_mode=composition_mode,
+ email_layout_xmlid=notif_layout if notif_layout is not None else 'mail.mail_notification_light',
+ subtype_id=subtype_id
+ )
+
+ def rating_apply(self, rate, token=None, feedback=None, subtype_xmlid=None):
+ """ Apply a rating given a token. 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 because of rating
+ manipulation (either employee, either sudo-ed in public controllers after
+ security check granting access).
+
+ :param float rate : the rating value to apply
+ :param string token : access token
+ :param string feedback : additional feedback
+ :param string subtype_xmlid : xml id of a valid mail.message.subtype
+
+ :returns rating.rating record
+ """
+ rating = None
+ if token:
+ rating = self.env['rating.rating'].search([('access_token', '=', token)], limit=1)
+ else:
+ rating = self.env['rating.rating'].search([('res_model', '=', self._name), ('res_id', '=', self.ids[0])], limit=1)
+ if rating:
+ rating.write({'rating': rate, 'feedback': feedback, 'consumed': True})
+ if hasattr(self, 'message_post'):
+ feedback = tools.plaintext2html(feedback or '')
+ self.message_post(
+ body="<img src='/rating/static/src/img/rating_%s.png' alt=':%s/10' style='width:18px;height:18px;float:left;margin-right: 5px;'/>%s"
+ % (rate, rate, feedback),
+ subtype_xmlid=subtype_xmlid or "mail.mt_comment",
+ author_id=rating.partner_id and rating.partner_id.id or None # None will set the default author in mail_thread.py
+ )
+ if hasattr(self, 'stage_id') and self.stage_id and hasattr(self.stage_id, 'auto_validation_kanban_state') and self.stage_id.auto_validation_kanban_state:
+ if rating.rating > 2:
+ self.write({'kanban_state': 'done'})
+ else:
+ self.write({'kanban_state': 'blocked'})
+ return rating
+
+ 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
+ :type add_stats : boolean
+ :param domain : optional extra domain of the rating to include/exclude in repartition
+ :return dictionnary
+ if not add_stats, the dict is like
+ - key is the rating value (integer)
+ - value is the number of object (res_model, res_id) having the value
+ otherwise, key is the value of the information (string) : either stat name (avg, total, ...) or 'repartition'
+ containing the same dict if add_stats was False.
+ """
+ base_domain = expression.AND([self._rating_domain(), [('rating', '>=', 1)]])
+ if domain:
+ base_domain += domain
+ data = self.env['rating.rating'].read_group(base_domain, ['rating'], ['rating', 'res_id'])
+ # init dict with all posible rate value, except 0 (no value for the rating)
+ values = dict.fromkeys(range(1, 6), 0)
+ values.update((d['rating'], d['rating_count']) for d in data)
+ # add other stats
+ if add_stats:
+ rating_number = sum(values.values())
+ result = {
+ 'repartition': values,
+ 'avg': sum(float(key * values[key]) for key in values) / rating_number if rating_number > 0 else 0,
+ 'total': sum(it['rating_count'] for it in data),
+ }
+ return result
+ return values
+
+ def rating_get_grades(self, domain=None):
+ """ get the repatition of rating grade for the given res_ids.
+ :param domain : optional domain of the rating to include/exclude in grades computation
+ :return dictionnary where the key is the grade (great, okay, bad), and the value, the number of object (res_model, res_id) having the grade
+ the grade are compute as 0-30% : Bad
+ 31-69%: Okay
+ 70-100%: Great
+ """
+ data = self._rating_get_repartition(domain=domain)
+ res = dict.fromkeys(['great', 'okay', 'bad'], 0)
+ for key in data:
+ if key >= RATING_LIMIT_SATISFIED:
+ res['great'] += data[key]
+ elif key >= RATING_LIMIT_OK:
+ res['okay'] += data[key]
+ else:
+ res['bad'] += data[key]
+ return res
+
+ def rating_get_stats(self, domain=None):
+ """ get the statistics of the rating repatition
+ :param domain : optional domain of the rating to include/exclude in statistic computation
+ :return dictionnary where
+ - key is the name of the information (stat name)
+ - value is statistic value : 'percent' contains the repartition in percentage, 'avg' is the average rate
+ and 'total' is the number of rating
+ """
+ data = self._rating_get_repartition(domain=domain, add_stats=True)
+ result = {
+ 'avg': data['avg'],
+ 'total': data['total'],
+ 'percent': dict.fromkeys(range(1, 6), 0),
+ }
+ for rate in data['repartition']:
+ result['percent'][rate] = (data['repartition'][rate] * 100) / data['total'] if data['total'] > 0 else 0
+ return result