Files
odoo_source/addons/rating/models/rating.py
T
Thibault Delavallée cce67a0a4d [REF] rating, various: cleanup rating from routes
PURPOSE

Cleanup code and flow of customer rating: routes, rating_apply, rating and
message creation and update.

SPECIFICATIONS

Cleanup code about rating controllers and rating_apply. Notably correctly
link a message to a rating once a feedback is posted, whatever the flow.

Update ``rating_apply`` to use either a token, either an existing rating to
udpate it and post a message. Always link the message and the rating to
be sure DB data are coherent.

This requires an update in several demo data to correctly set tokens on
ratings and use it in calls to ``rating_apply`` done in xml files.

Extract default subtype computation of rating_apply in a sub-method to allow
setting this parameter without having to deal with ``rating_apply`` details.

CODE CLEANUP

Split behavior that is generic but actually used only in project about stage
update based on rating. Move it directly in project. As it is used only once
and as generic approach is hardcoded based on fields names better keep it
localized.

Task-2812665 (Rating: Cleanup rating flow code)

Part-of: odoo/odoo#80707
2022-04-04 17:48:23 +02:00

162 lines
7.6 KiB
Python

# -*- 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.addons.rating.models import rating_data
from odoo.modules.module import get_resource_path
class Rating(models.Model):
_name = "rating.rating"
_description = "Rating"
_order = 'write_date desc'
_rec_name = 'res_name'
@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'].sudo().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")
rated_partner_name = fields.Char(related="rated_partner_id.name")
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_image_url = fields.Char('Image URL', compute='_compute_rating_image')
rating_text = fields.Selection(rating_data.RATING_TEXT, 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.")
_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:
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()
return 'rating_%s.png' % rating_data._rating_to_threshold(self.rating)
@api.depends('rating')
def _compute_rating_image(self):
self.rating_image_url = False
self.rating_image = False
for rating in self:
try:
image_path = get_resource_path('rating', 'static/src/img', rating._get_rating_image_filename())
rating.rating_image_url = '/rating/static/src/img/%s' % rating._get_rating_image_filename()
rating.rating_image = base64.b64encode(open(image_path, 'rb').read()) if image_path else False
except (IOError, OSError):
pass
@api.depends('rating')
def _compute_rating_text(self):
for rating in self:
rating.rating_text = rating_data._rating_to_text(rating.rating)
@api.model_create_multi
def create(self, vals_list):
for values in vals_list:
if values.get('res_model_id') and values.get('res_id'):
values.update(self._find_parent_data(values))
return super().create(vals_list)
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']]
}