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
162 lines
7.6 KiB
Python
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']]
|
|
}
|