[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
This commit is contained in:
@@ -25,13 +25,15 @@
|
||||
<field name="anonymous_name">Visitor #234, Mitchell Admin</field>
|
||||
</record>
|
||||
<record id="im_livechat_rating_1" model="rating.rating">
|
||||
<field name="access_token">LIVECHAT_1</field>
|
||||
<field name="res_id" ref="im_livechat.im_livechat_mail_channel_data_0"/>
|
||||
<field name="res_model_id" ref="mail.model_mail_channel"/>
|
||||
<field name="rated_partner_id" ref="base.partner_admin"/>
|
||||
<field eval="False" name="partner_id"/>
|
||||
<field eval="True" name="consumed"/>
|
||||
</record>
|
||||
<function eval="([ref('im_livechat.im_livechat_mail_channel_data_0')], 5)" model="mail.channel" name="rating_apply"/>
|
||||
<function model="mail.channel" name="rating_apply"
|
||||
eval="([ref('im_livechat.im_livechat_mail_channel_data_0')], 5, 'LIVECHAT_1', None, 'Good Job')"/>
|
||||
<record id="im_livechat_mail_message_5_data" model="mail.message">
|
||||
<field name="model">mail.channel</field>
|
||||
<field name="res_id" ref="im_livechat.im_livechat_mail_channel_data_0"/>
|
||||
@@ -81,13 +83,15 @@
|
||||
<field name="anonymous_name">Visitor #323, Marc Demo</field>
|
||||
</record>
|
||||
<record id="im_livechat_rating_2" model="rating.rating">
|
||||
<field name="access_token">LIVECHAT_2</field>
|
||||
<field name="res_id" ref="im_livechat.im_livechat_mail_channel_data_1"/>
|
||||
<field name="res_model_id" ref="mail.model_mail_channel"/>
|
||||
<field name="rated_partner_id" ref="base.partner_demo"/>
|
||||
<field eval="False" name="partner_id"/>
|
||||
<field eval="True" name="consumed"/>
|
||||
</record>
|
||||
<function eval="([ref('im_livechat.im_livechat_mail_channel_data_1')], 5)" model="mail.channel" name="rating_apply"/>
|
||||
<function model="mail.channel" name="rating_apply"
|
||||
eval="([ref('im_livechat.im_livechat_mail_channel_data_1')], 5, 'LIVECHAT_2', None, 'Super Job')"/>
|
||||
<record id="im_livechat_mail_message_10_data" model="mail.message">
|
||||
<field name="model">mail.channel</field>
|
||||
<field name="res_id" ref="im_livechat.im_livechat_mail_channel_data_1"/>
|
||||
@@ -137,13 +141,16 @@
|
||||
<field name="anonymous_name">Joel Willis, Mitchell Admin</field>
|
||||
</record>
|
||||
<record id="im_livechat_rating_3" model="rating.rating">
|
||||
<field name="access_token">LIVECHAT_3</field>
|
||||
<field name="res_id" ref="im_livechat.im_livechat_mail_channel_data_2"/>
|
||||
<field name="res_model_id" ref="mail.model_mail_channel"/>
|
||||
<field name="rated_partner_id" ref="base.partner_admin"/>
|
||||
<field name="partner_id" ref="base.partner_demo_portal"/>
|
||||
<field eval="True" name="consumed"/>
|
||||
</record>
|
||||
<function eval="([ref('im_livechat.im_livechat_mail_channel_data_2')], 5)" model="mail.channel" name="rating_apply"/>
|
||||
<function model="mail.channel" name="rating_apply"
|
||||
eval="([ref('im_livechat.im_livechat_mail_channel_data_2')], 5, 'LIVECHAT_3', None, 'Mega Job')"/>
|
||||
|
||||
<record id="im_livechat_mail_message_14_data" model="mail.message">
|
||||
<field name="model">mail.channel</field>
|
||||
<field name="res_id" ref="im_livechat.im_livechat_mail_channel_data_2"/>
|
||||
@@ -185,13 +192,16 @@
|
||||
<field name="anonymous_name">Joel Willis, Marc Demo</field>
|
||||
</record>
|
||||
<record id="im_livechat_rating_4" model="rating.rating">
|
||||
<field name="access_token">LIVECHAT_4</field>
|
||||
<field name="res_id" ref="im_livechat.im_livechat_mail_channel_data_3"/>
|
||||
<field name="res_model_id" ref="mail.model_mail_channel"/>
|
||||
<field name="rated_partner_id" ref="base.partner_demo"/>
|
||||
<field name="partner_id" ref="base.partner_demo_portal"/>
|
||||
<field eval="True" name="consumed"/>
|
||||
</record>
|
||||
<function eval="([ref('im_livechat.im_livechat_mail_channel_data_3')], 5)" model="mail.channel" name="rating_apply"/>
|
||||
<function model="mail.channel" name="rating_apply"
|
||||
eval="([ref('im_livechat.im_livechat_mail_channel_data_3')], 5, 'LIVECHAT_4', None, 'Good Job')"/>
|
||||
|
||||
<record id="im_livechat_mail_message_17_data" model="mail.message">
|
||||
<field name="model">mail.channel</field>
|
||||
<field name="res_id" ref="im_livechat.im_livechat_mail_channel_data_3"/>
|
||||
@@ -225,13 +235,15 @@
|
||||
<field name="anonymous_name">Visitor #532, Mitchell Admin</field>
|
||||
</record>
|
||||
<record id="im_livechat_rating_5" model="rating.rating">
|
||||
<field name="access_token">LIVECHAT_5</field>
|
||||
<field name="res_id" ref="im_livechat.im_livechat_mail_channel_data_4"/>
|
||||
<field name="res_model_id" ref="mail.model_mail_channel"/>
|
||||
<field name="rated_partner_id" ref="base.partner_admin"/>
|
||||
<field eval="False" name="partner_id"/>
|
||||
<field eval="True" name="consumed"/>
|
||||
</record>
|
||||
<function eval="([ref('im_livechat.im_livechat_mail_channel_data_4')], 1)" model="mail.channel" name="rating_apply"/>
|
||||
<function model="mail.channel" name="rating_apply"
|
||||
eval="([ref('im_livechat.im_livechat_mail_channel_data_4')], 5, 'LIVECHAT_5', None, 'Super Job')"/>
|
||||
<record id="im_livechat_mail_message_21_data" model="mail.message">
|
||||
<field name="model">mail.channel</field>
|
||||
<field name="res_id" ref="im_livechat.im_livechat_mail_channel_data_4"/>
|
||||
@@ -273,13 +285,15 @@
|
||||
<field name="anonymous_name">Visitor #649, Mitchell Admin</field>
|
||||
</record>
|
||||
<record id="im_livechat_rating_6" model="rating.rating">
|
||||
<field name="access_token">LIVECHAT_6</field>
|
||||
<field name="res_id" ref="im_livechat.im_livechat_mail_channel_data_5"/>
|
||||
<field name="res_model_id" ref="mail.model_mail_channel"/>
|
||||
<field name="rated_partner_id" ref="base.partner_admin"/>
|
||||
<field eval="False" name="partner_id"/>
|
||||
<field eval="True" name="consumed"/>
|
||||
</record>
|
||||
<function eval="([ref('im_livechat.im_livechat_mail_channel_data_5')], 5)" model="mail.channel" name="rating_apply"/>
|
||||
<function model="mail.channel" name="rating_apply"
|
||||
eval="([ref('im_livechat.im_livechat_mail_channel_data_5')], 5, 'LIVECHAT_6', None, 'Good Job')"/>
|
||||
<record id="im_livechat_mail_message_25_data" model="mail.message">
|
||||
<field name="model">mail.channel</field>
|
||||
<field name="res_id" ref="im_livechat.im_livechat_mail_channel_data_5"/>
|
||||
@@ -321,13 +335,15 @@
|
||||
<field name="anonymous_name">Joel Willis, Mitchell Admin</field>
|
||||
</record>
|
||||
<record id="im_livechat_rating_7" model="rating.rating">
|
||||
<field name="access_token">LIVECHAT_7</field>
|
||||
<field name="res_id" ref="im_livechat.im_livechat_mail_channel_data_6"/>
|
||||
<field name="res_model_id" ref="mail.model_mail_channel"/>
|
||||
<field name="rated_partner_id" ref="base.partner_admin"/>
|
||||
<field name="partner_id" ref="base.partner_demo_portal"/>
|
||||
<field eval="True" name="consumed"/>
|
||||
</record>
|
||||
<function eval="([ref('im_livechat.im_livechat_mail_channel_data_6')], 5)" model="mail.channel" name="rating_apply"/>
|
||||
<function model="mail.channel" name="rating_apply"
|
||||
eval="([ref('im_livechat.im_livechat_mail_channel_data_6')], 5, 'LIVECHAT_7', None, 'Super Job')"/>
|
||||
<record id="im_livechat_mail_message_29_data" model="mail.message">
|
||||
<field name="model">mail.channel</field>
|
||||
<field name="res_id" ref="im_livechat.im_livechat_mail_channel_data_6"/>
|
||||
@@ -369,13 +385,15 @@
|
||||
<field name="anonymous_name">Visitor #722, Marc Demo</field>
|
||||
</record>
|
||||
<record id="im_livechat_rating_8" model="rating.rating">
|
||||
<field name="access_token">LIVECHAT_8</field>
|
||||
<field name="res_id" ref="im_livechat.im_livechat_mail_channel_data_7"/>
|
||||
<field name="res_model_id" ref="mail.model_mail_channel"/>
|
||||
<field name="rated_partner_id" ref="base.partner_demo"/>
|
||||
<field eval="False" name="partner_id"/>
|
||||
<field eval="True" name="consumed"/>
|
||||
</record>
|
||||
<function eval="([ref('im_livechat.im_livechat_mail_channel_data_7')], 5)" model="mail.channel" name="rating_apply"/>
|
||||
<function model="mail.channel" name="rating_apply"
|
||||
eval="([ref('im_livechat.im_livechat_mail_channel_data_7')], 5, 'LIVECHAT_8', None, 'Super Job')"/>
|
||||
<record id="im_livechat_mail_message_33_data" model="mail.message">
|
||||
<field name="model">mail.channel</field>
|
||||
<field name="res_id" ref="im_livechat.im_livechat_mail_channel_data_7"/>
|
||||
@@ -470,14 +488,15 @@
|
||||
<field name="model">mail.channel</field>
|
||||
</record>
|
||||
<record id="rating_rating_livechat_1" model="rating.rating">
|
||||
<field name="access_token">LIVECHAT_9</field>
|
||||
<field name="res_model_id" ref="mail.model_mail_channel"/>
|
||||
<field name="rated_partner_id" ref="base.partner_admin"/>
|
||||
<field name="partner_id" ref="base.partner_admin"/>
|
||||
<field name="create_date" eval="datetime.now() - timedelta(days=1, seconds=-53)"/>
|
||||
<field name="res_id" ref="im_livechat.mail_channel_livechat_1"/>
|
||||
<field name="rating" eval="5"/>
|
||||
<field name="consumed" eval="True"/>
|
||||
</record>
|
||||
<function model="mail.channel" name="rating_apply"
|
||||
eval="([ref('im_livechat.mail_channel_livechat_1')], 5, 'LIVECHAT_9', None, 'Super Job')"/>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -2930,9 +2930,6 @@ class MailThread(models.AbstractModel):
|
||||
# DISCORDUSS API
|
||||
# ------------------------------------------------------
|
||||
|
||||
def _message_update_content_after_hook(self, message):
|
||||
""" Hook to add custom behavior after having updated the message content. """
|
||||
|
||||
def _message_add_reaction_after_hook(self, message, content):
|
||||
""" Hook to add custom behavior after having added a reaction to a message. """
|
||||
|
||||
@@ -2940,7 +2937,7 @@ class MailThread(models.AbstractModel):
|
||||
""" Hook to add custom behavior after having removed a reaction from a message. """
|
||||
|
||||
# ------------------------------------------------------
|
||||
# WRAPPERS AND TOOLS
|
||||
# THREAD MESSAGE UPDATE
|
||||
# ------------------------------------------------------
|
||||
|
||||
def message_change_thread(self, new_thread, new_parent_message=False):
|
||||
@@ -2983,6 +2980,9 @@ class MailThread(models.AbstractModel):
|
||||
msg_not_comment.write(msg_vals)
|
||||
return True
|
||||
|
||||
def _message_update_content_after_hook(self, message):
|
||||
""" Hook to add custom behavior after having updated the message content. """
|
||||
|
||||
# ------------------------------------------------------
|
||||
# CONTROLLERS
|
||||
# ------------------------------------------------------
|
||||
|
||||
@@ -1227,36 +1227,44 @@ Send it ASAP, its urgent.</field>
|
||||
|
||||
<!-- Rating Demo Data -->
|
||||
<record id="rating_task_1" model="rating.rating">
|
||||
<field name="access_token">PROJECT_1</field>
|
||||
<field name="res_model_id" ref="project.model_project_task"/>
|
||||
<field name="rated_partner_id" ref="base.partner_root"/>
|
||||
<field name="partner_id" ref="base.partner_demo_portal"/>
|
||||
<field name="res_id" ref="project.project_1_task_3"/>
|
||||
</record>
|
||||
<function model="project.task" name="rating_apply" eval="([ref('project.project_1_task_3')], 5, None, 'Good Job')"/>
|
||||
<function model="project.task" name="rating_apply"
|
||||
eval="([ref('project.project_1_task_3')], 5, 'PROJECT_1', None, 'Good Job')"/>
|
||||
|
||||
<record id="rating_task_2" model="rating.rating">
|
||||
<field name="access_token">PROJECT_2</field>
|
||||
<field name="res_model_id" ref="project.model_project_task"/>
|
||||
<field name="rated_partner_id" ref="base.partner_demo"/>
|
||||
<field name="partner_id" ref="base.partner_demo"/>
|
||||
<field name="res_id" ref="project.project_2_task_7"/>
|
||||
</record>
|
||||
<function model="project.task" name="rating_apply" eval="([ref('project.project_2_task_7')], 1, 'Not as good as expected')"/>
|
||||
<function model="project.task" name="rating_apply"
|
||||
eval="([ref('project.project_2_task_7')], 1, 'PROJECT_2', None, 'Not as good as expected')"/>
|
||||
|
||||
<record id="rating_task_3" model="rating.rating">
|
||||
<field name="access_token">PROJECT_3</field>
|
||||
<field name="res_model_id" ref="project.model_project_task"/>
|
||||
<field name="rated_partner_id" ref="base.partner_root"/>
|
||||
<field name="partner_id" ref="base.partner_demo_portal"/>
|
||||
<field name="res_id" ref="project.project_1_task_4"/>
|
||||
</record>
|
||||
<function model="project.task" name="rating_apply" eval="([ref('project.project_1_task_4')], 5, None, 'Exactly what I asked for, thank you!')"/>
|
||||
<function model="project.task" name="rating_apply"
|
||||
eval="([ref('project.project_1_task_4')], 5, 'PROJECT_3', None, 'Exactly what I asked for, thank you!')"/>
|
||||
|
||||
<record id="rating_task_4" model="rating.rating">
|
||||
<field name="access_token">PROJECT_4</field>
|
||||
<field name="res_model_id" ref="project.model_project_task"/>
|
||||
<field name="rated_partner_id" ref="base.partner_root"/>
|
||||
<field name="partner_id" ref="base.partner_demo_portal"/>
|
||||
<field name="res_id" ref="project.project_1_task_2"/>
|
||||
</record>
|
||||
<function model="project.task" name="rating_apply" eval="([ref('project.project_1_task_2')], 1, None, 'There must have been some miscomunication, because the result is not quite what I had in mind. I would like to request some modifications.')"/>
|
||||
<function model="project.task" name="rating_apply"
|
||||
eval="([ref('project.project_1_task_2')], 1, 'PROJECT_4', None, 'There must have been some miscomunication, because the result is not quite what I had in mind. I would like to request some modifications.')"/>
|
||||
|
||||
|
||||
<!-- add the email template as value for the project stage 2 -->
|
||||
|
||||
@@ -9,6 +9,7 @@ from datetime import timedelta, datetime, time
|
||||
from random import randint
|
||||
|
||||
from odoo import api, Command, fields, models, tools, SUPERUSER_ID, _, _lt
|
||||
from odoo.addons.rating.models.rating_data import RATING_LIMIT_OK
|
||||
from odoo.exceptions import UserError, ValidationError, AccessError
|
||||
from odoo.osv.expression import OR, AND
|
||||
from odoo.tools.misc import get_lang
|
||||
@@ -2278,8 +2279,15 @@ class Task(models.Model):
|
||||
return self.project_id.partner_id
|
||||
return res
|
||||
|
||||
def rating_apply(self, rate, token=None, feedback=None, subtype_xmlid=None):
|
||||
return super(Task, self).rating_apply(rate, token=token, feedback=feedback, subtype_xmlid="project.mt_task_rating")
|
||||
def rating_apply(self, rate, token=None, rating=None, feedback=None, subtype_xmlid=None):
|
||||
rating = super(Task, self).rating_apply(rate, token=token, rating=rating, feedback=feedback, subtype_xmlid=subtype_xmlid)
|
||||
if self.stage_id and self.stage_id.auto_validation_kanban_state:
|
||||
kanban_state = 'done' if rating.rating >= RATING_LIMIT_OK else 'blocked'
|
||||
self.write({'kanban_state': kanban_state})
|
||||
return rating
|
||||
|
||||
def _rating_apply_get_default_subtype_id(self):
|
||||
return self.env['ir.model.data']._xmlid_to_res_id("project.mt_task_rating")
|
||||
|
||||
def _rating_get_parent_field_name(self):
|
||||
return 'project_id'
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
{
|
||||
'name': 'Customer Rating',
|
||||
'version': '1.0',
|
||||
'version': '1.1',
|
||||
'category': 'Productivity',
|
||||
'description': """
|
||||
This module allows a customer to give rating.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
import werkzeug
|
||||
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
@@ -20,31 +21,51 @@ class Rating(http.Controller):
|
||||
|
||||
@http.route('/rate/<string:token>/<int:rate>', type='http', auth="public", website=True)
|
||||
def action_open_rating(self, token, rate, **kwargs):
|
||||
assert rate in (1, 3, 5), "Incorrect rating"
|
||||
if rate not in (1, 3, 5):
|
||||
raise ValueError(_("Incorrect rating: should be 1, 3 or 5 (received %d)"), rate)
|
||||
|
||||
rating = request.env['rating.rating'].sudo().search([('access_token', '=', token)])
|
||||
if not rating:
|
||||
return request.not_found()
|
||||
rate_names = {
|
||||
5: _("Satisfied"),
|
||||
3: _("Okay"),
|
||||
1: _("Dissatisfied")
|
||||
}
|
||||
return werkzeug.exceptions.Forbidden()
|
||||
|
||||
record_sudo = request.env[rating.res_model].sudo().browse(rating.res_id)
|
||||
if not record_sudo.exists():
|
||||
return werkzeug.exceptions.Forbidden()
|
||||
|
||||
rating.write({'rating': rate, 'consumed': True})
|
||||
|
||||
lang = rating.partner_id.lang or get_lang(request.env).code
|
||||
return request.env['ir.ui.view'].with_context(lang=lang)._render_template('rating.rating_external_page_submit', {
|
||||
'rating': rating, 'token': token,
|
||||
'rate_names': rate_names, 'rate': rate
|
||||
'rating': rating,
|
||||
'token': token,
|
||||
'rate_names': {
|
||||
5: _("Satisfied"),
|
||||
3: _("Okay"),
|
||||
1: _("Dissatisfied"),
|
||||
},
|
||||
'rate': rate,
|
||||
})
|
||||
|
||||
@http.route(['/rate/<string:token>/submit_feedback'], type="http", auth="public", methods=['post'], website=True)
|
||||
def action_submit_rating(self, token, **kwargs):
|
||||
rate = int(kwargs.get('rate'))
|
||||
assert rate in (1, 3, 5), "Incorrect rating"
|
||||
def action_submit_rating(self, token, rate, **kwargs):
|
||||
rate = int(rate)
|
||||
if rate not in (1, 3, 5):
|
||||
raise ValueError(_("Incorrect rating: should be 1, 3 or 5 (received %d)"), rate)
|
||||
|
||||
rating = request.env['rating.rating'].sudo().search([('access_token', '=', token)])
|
||||
if not rating:
|
||||
return request.not_found()
|
||||
return werkzeug.exceptions.Forbidden()
|
||||
|
||||
record_sudo = request.env[rating.res_model].sudo().browse(rating.res_id)
|
||||
record_sudo.rating_apply(rate, token=token, feedback=kwargs.get('feedback'))
|
||||
if not record_sudo.exists():
|
||||
return werkzeug.exceptions.Forbidden()
|
||||
|
||||
record_sudo.rating_apply(rate,
|
||||
rating=rating,
|
||||
feedback=kwargs.get('feedback'),
|
||||
subtype_xmlid=None, # force default subtype choice
|
||||
)
|
||||
|
||||
lang = rating.partner_id.lang or get_lang(request.env).code
|
||||
return request.env['ir.ui.view'].with_context(lang=lang)._render_template('rating.rating_external_page_view', {
|
||||
'web_base_url': rating.get_base_url(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, models
|
||||
from odoo import api, models, _
|
||||
|
||||
|
||||
class MailThread(models.AbstractModel):
|
||||
@@ -9,6 +9,7 @@ class MailThread(models.AbstractModel):
|
||||
|
||||
@api.returns('mail.message', lambda value: value.id)
|
||||
def message_post(self, **kwargs):
|
||||
rating_id = kwargs.pop('rating_id', False)
|
||||
rating_value = kwargs.pop('rating_value', False)
|
||||
rating_feedback = kwargs.pop('rating_feedback', False)
|
||||
message = super(MailThread, self).message_post(**kwargs)
|
||||
@@ -25,4 +26,17 @@ class MailThread(models.AbstractModel):
|
||||
'consumed': True,
|
||||
'partner_id': self.env.user.partner_id.id,
|
||||
})
|
||||
elif rating_id:
|
||||
self.env['rating.rating'].browse(rating_id).write({'message_id': message.id})
|
||||
|
||||
return message
|
||||
|
||||
def _message_create(self, values_list):
|
||||
""" Force usage of rating-specific methods and API allowing to delegate
|
||||
computation to records. Keep methods optimized and skip rating_ids
|
||||
support to simplify MailThrad main API. """
|
||||
if not isinstance(values_list, (list)):
|
||||
values_list = [values_list]
|
||||
if any(values.get('rating_ids') for values in values_list):
|
||||
raise ValueError(_("Posting a rating should be done using message post API."))
|
||||
return super()._message_create(values_list)
|
||||
|
||||
@@ -42,6 +42,7 @@ class Rating(models.Model):
|
||||
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(
|
||||
@@ -91,13 +92,17 @@ class Rating(models.Model):
|
||||
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):
|
||||
rating.rating_image = False
|
||||
pass
|
||||
|
||||
@api.depends('rating')
|
||||
def _compute_rating_text(self):
|
||||
|
||||
@@ -173,42 +173,52 @@ class RatingMixin(models.AbstractModel):
|
||||
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).
|
||||
def rating_apply(self, rate, token=None, rating=None, feedback=None, subtype_xmlid=None):
|
||||
""" Apply a rating to the record. This rating can either be linked to a
|
||||
token (customer flow) or directly a rating record (code flow).
|
||||
|
||||
: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
|
||||
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 as well as rights on the current record because of rating
|
||||
manipulation and chatter post (either employee, either sudo-ed in public
|
||||
controllers after security check granting access).
|
||||
|
||||
:returns rating.rating record
|
||||
:param float rate: the rating value to apply (from 0 to 5);
|
||||
:param string token: access token to fetch the rating to apply (optional);
|
||||
:param record rating: rating.rating to apply (if no token);
|
||||
:param string feedback: additional feedback (plaintext);
|
||||
:param string subtype_xmlid: xml id of a valid mail.message.subtype used
|
||||
to post the message (if it applies). If not given a classic comment is
|
||||
posted;
|
||||
|
||||
:returns rating: rating.rating record
|
||||
"""
|
||||
rating = None
|
||||
if rate < 0 or rate > 5:
|
||||
raise ValueError('Wrong rating value. A rate should be between 0 and 5 (received %d).' % rate)
|
||||
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/5' 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'})
|
||||
if not rating:
|
||||
raise ValueError('Invalid token or rating.')
|
||||
|
||||
rating.write({'rating': rate, 'feedback': feedback, 'consumed': True})
|
||||
if hasattr(self, 'message_post'):
|
||||
if subtype_xmlid is None:
|
||||
subtype_id = self._rating_apply_get_default_subtype_id()
|
||||
else:
|
||||
subtype_id = self.env['ir.model.data']._xmlid_to_res_id(subtype_xmlid)
|
||||
feedback = tools.plaintext2html(feedback or '')
|
||||
self.message_post(
|
||||
author_id=rating.partner_id.id or None, # None will set the default author in mail_thread.py
|
||||
body="<img src='%s' alt=':%s/5' style='width:18px;height:18px;float:left;margin-right: 5px;'/>%s"
|
||||
% (rating.rating_image_url, rate, feedback),
|
||||
rating_id=rating.id,
|
||||
subtype_id=subtype_id,
|
||||
)
|
||||
return rating
|
||||
|
||||
def _rating_apply_get_default_subtype_id(self):
|
||||
return self.env['ir.model.data']._xmlid_to_res_id("mail.mt_comment")
|
||||
|
||||
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
|
||||
|
||||
@@ -1045,21 +1045,24 @@
|
||||
|
||||
<!-- Rating Demo Data -->
|
||||
<record id="rating_task_1" model="rating.rating">
|
||||
<field name="access_token">TS_0</field>
|
||||
<field name="res_model_id" ref="project.model_project_task"/>
|
||||
<field name="rated_partner_id" ref="base.partner_admin"/>
|
||||
<field name="partner_id" ref="base.res_partner_2"/>
|
||||
<field name="res_id" ref="project_agr_task_1"/>
|
||||
</record>
|
||||
<function model="project.task" name="rating_apply" eval="([ref('project_agr_task_1')], 5, None, 'This is already looking very promising, thanks for the good work! 😃')"/>
|
||||
<function model="project.task" name="rating_apply"
|
||||
eval="([ref('project_agr_task_1')], 5, 'TS_0', None, 'This is already looking very promising, thanks for the good work! 😃')"/>
|
||||
|
||||
<record id="rating_task_2" model="rating.rating">
|
||||
<field name="access_token">TS_1</field>
|
||||
<field name="res_model_id" ref="project.model_project_task"/>
|
||||
<field name="rated_partner_id" ref="base.partner_admin"/>
|
||||
<field name="partner_id" ref="base.res_partner_4"/>
|
||||
<field name="res_id" ref="project_dpc_task_5"/>
|
||||
</record>
|
||||
<function model="project.task" name="rating_apply" eval="([ref('project_dpc_task_5')], 3, None, 'Everything is working fine, but there are some loose cables')"/>
|
||||
|
||||
<function model="project.task" name="rating_apply"
|
||||
eval="([ref('project_dpc_task_5')], 3, 'TS_1', None, 'Everything is working fine, but there are some loose cables')"/>
|
||||
|
||||
<!-- Timesheet for those tasks -->
|
||||
<record id="project_agr_task_1_account_analytic_line_1" model="account.analytic.line">
|
||||
|
||||
@@ -24,6 +24,7 @@ real applications. """,
|
||||
'sms',
|
||||
],
|
||||
'data': [
|
||||
'data/mail_message_subtype_data.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'security/ir_rule_data.xml',
|
||||
],
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="mt_mail_test_rating_rating_done" model="mail.message.subtype">
|
||||
<field name="name">Rating Done</field>
|
||||
<field name="description">Rating Done</field>
|
||||
<field name="res_model">mail.test.rating</field>
|
||||
<field name="default" eval="True"/>
|
||||
<field name="internal" eval="False"/>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -51,5 +51,8 @@ class MailTestRating(models.Model):
|
||||
elif not rating.phone_nbr:
|
||||
rating.phone_nbr = False
|
||||
|
||||
def _rating_apply_get_default_subtype_id(self):
|
||||
return self.env['ir.model.data']._xmlid_to_res_id("test_mail_full.mt_mail_test_rating_rating_done")
|
||||
|
||||
def rating_get_partner_id(self):
|
||||
return self.customer_id
|
||||
|
||||
@@ -48,27 +48,51 @@ class TestRatingFlow(TestMailFullCommon, TestMailFullRecipients):
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_rating_rating_apply(self):
|
||||
record_rating = self.env['mail.test.rating'].browse(self.record_rating.ids)
|
||||
record_messages = record_rating.message_ids
|
||||
|
||||
# prepare rating token
|
||||
access_token = record_rating.rating_get_access_token()
|
||||
record_rating.invalidate_cache(fnames=['rating_ids'])
|
||||
|
||||
# apply a rate
|
||||
# apply a rate as note (first click)
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False), self.mock_mail_app():
|
||||
record_rating.rating_apply(5, token=access_token, feedback='Top Feedback')
|
||||
record_rating.rating_apply(5, token=access_token, feedback='Top Feedback', subtype_xmlid='mail.mt_note')
|
||||
message = record_rating.message_ids[0]
|
||||
rating = record_rating.rating_ids
|
||||
|
||||
# check posted message
|
||||
self.assertEqual(record_rating.message_ids, record_messages + message)
|
||||
self.assertIn('Top Feedback', message.body)
|
||||
self.assertIn('rating/static/src/img/rating_5.png', message.body)
|
||||
self.assertIn('/rating/static/src/img/rating_5.png', message.body)
|
||||
self.assertEqual(message.author_id, self.partner_1)
|
||||
self.assertFalse(message.rating_ids)
|
||||
self.assertEqual(message.notified_partner_ids, self.partner_admin)
|
||||
self.assertEqual(message.subtype_id, self.env.ref('mail.mt_comment'))
|
||||
self.assertEqual(message.rating_ids, rating)
|
||||
self.assertFalse(message.notified_partner_ids)
|
||||
self.assertEqual(message.subtype_id, self.env.ref('mail.mt_note'))
|
||||
|
||||
# check rating update
|
||||
self.assertTrue(rating.consumed)
|
||||
self.assertEqual(rating.feedback, 'Top Feedback')
|
||||
self.assertFalse(rating.message_id)
|
||||
self.assertEqual(rating.message_id, message)
|
||||
self.assertEqual(rating.rating, 5)
|
||||
|
||||
# apply a rate again (second click with feedback)
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False), self.mock_mail_app():
|
||||
record_rating.rating_apply(1, token=access_token, feedback='Bad Feedback')
|
||||
|
||||
# check posted message: a new message is posted with default subtype
|
||||
update_message = record_rating.message_ids[0]
|
||||
self.assertNotEqual(update_message, message)
|
||||
self.assertEqual(record_rating.message_ids, record_messages + message + update_message)
|
||||
self.assertIn('Bad Feedback', update_message.body)
|
||||
self.assertIn('/rating/static/src/img/rating_1.png', update_message.body)
|
||||
self.assertEqual(update_message.author_id, self.partner_1)
|
||||
self.assertEqual(update_message.rating_ids, rating)
|
||||
self.assertEqual(update_message.notified_partner_ids, self.partner_admin)
|
||||
self.assertEqual(update_message.subtype_id, self.env.ref("test_mail_full.mt_mail_test_rating_rating_done"))
|
||||
|
||||
# check rating update
|
||||
rating = record_rating.rating_ids
|
||||
self.assertTrue(rating.consumed)
|
||||
self.assertEqual(rating.feedback, 'Bad Feedback')
|
||||
self.assertEqual(rating.message_id, update_message)
|
||||
self.assertEqual(rating.rating, 1)
|
||||
|
||||
Reference in New Issue
Block a user