odoo/addons/rating/models/rating.py

210 lines
9.9 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import uuid
from odoo import api, fields, models, tools
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 <= 10)', 'Rating should be between 0 to 10'),
]
@api.one
@api.depends('res_model', 'res_id')
def _compute_res_name(self):
name = self.env[self.res_model].sudo().browse(self.res_id).name_get()
self.res_name = name and name[0][1] or ('%s/%s') % (self.res_model, self.res_id)
@api.model
def new_access_token(self):
return uuid.uuid4().hex
res_name = fields.Char(string='Resource Name', compute='_compute_res_name', store=True, help="The name of the rated resource.")
res_model = fields.Char(string='Document Model', required=True, help="Model name of the rated object", index=True)
res_id = fields.Integer(string='Document ID', required=True, help="Identifier of the rated object", index=True)
rated_partner_id = fields.Many2one('res.partner', string="Rated Partner", help="Owner of the rated resource")
partner_id = fields.Many2one('res.partner', string='Customer', help="Author of the rating")
rating = fields.Float(string="Rating", group_operator="avg", default=0, help="Rating value: 0=Unhappy, 10=Happy")
feedback = fields.Text('Feedback reason', help="Reason of the rating")
message_id = fields.Many2one('mail.message', string="Linked message", help="Associated message when posting a review. Mainly used in website addons.", index=True)
access_token = fields.Char('Security Token', default=new_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.multi
def reset(self):
for record in self:
record.write({
'rating': 0,
'access_token': record.new_access_token(),
'feedback': False,
'consumed': False,
})
class RatingMixin(models.AbstractModel):
_name = 'rating.mixin'
_description = "Rating Mixin"
rating_ids = fields.One2many('rating.rating', 'res_id', string='Rating', domain=lambda self: [('res_model', '=', self._name)])
rating_last_value = fields.Float('Rating Last Value', compute='_compute_rating_last_value', compute_sudo=True, store=True)
rating_count = fields.Integer('Rating count', compute="_compute_rating_count")
@api.multi
@api.depends('rating_ids.rating')
def _compute_rating_last_value(self):
for record in self:
ratings = self.env['rating.rating'].search([('res_model', '=', self._name), ('res_id', '=', record.id)], limit=1)
if ratings:
record.rating_last_value = ratings.rating
@api.multi
def _compute_rating_count(self):
read_group_res = self.env['rating.rating'].read_group(
[('res_model', '=', self._name), ('res_id', 'in', self.ids), ('consumed', '=', True)],
['res_id'], groupby=['res_id'])
result = dict.fromkeys(self.ids, 0)
for data in read_group_res:
result[data['res_id']] += data['res_id_count']
for record in self:
record.rating_count = result.get(record.id)
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):
if not partner:
partner = self.rating_get_partner_id()
rated_partner = self.rating_get_rated_partner_id()
ratings = self.rating_ids.filtered(lambda x: x.partner_id.id == partner.id and not x.consumed)
if not ratings:
rating = self.env['rating.rating'].create({'partner_id': partner.id, 'rated_partner_id': rated_partner.id, 'res_model': self._name, 'res_id': self.id})
else:
rating = ratings[0]
return rating.access_token
@api.multi
def rating_send_request(self, template, partner=None, rated_partner=None, reuse_rating=True, force_send=True):
""" This method send rating request by email, using a template given
in parameter. """
for record in self:
template.send_mail(record.id, force_send=force_send)
@api.multi
def rating_apply(self, rate, token=None, feedback=None, subtype=None):
""" Apply a rating given a token. If the current model inherits from
mail.thread mixing, a message is posted on its chatter.
:param rate : the rating value to apply
:type rate : float
:param token : access token
:param feedback : additional feedback
:type feedback : string
:param subtype : subtype for mail
:type subtype : string
:returns rating.rating record
"""
Rating, rating = self.env['rating.rating'], None
if token:
rating = self.env['rating.rating'].search([('access_token', '=', token)], limit=1)
else:
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' style='width:20px;height:20px;float:left;margin-right: 5px;'/>%s"
% (rate, feedback),
subtype=subtype 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 > 5:
self.write({'kanban_state': 'done'})
if rating.rating < 5:
self.write({'kanban_state': 'blocked'})
return rating
@api.multi
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 = [('res_model', '=', self._name), ('res_id', 'in', self.ids), ('rating', '>=', 1), ('consumed', '=', True)]
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, 11), 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': reduce(lambda x, y: y['rating_count'] + x, data, 0),
}
return result
return values
@api.multi
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 >= 7:
res['great'] += data[key]
elif key > 3:
res['okay'] += data[key]
else:
res['bad'] += data[key]
return res
@api.multi
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 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, 11), 0),
}
for rate in data['repartition']:
result['percent'][rate] = (data['repartition'][rate] * 100) / data['total'] if data['total'] > 0 else 0
return result