summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--addons/rating/views/rating_view.xml2
-rw-r--r--addons/website_rating/__manifest__.py1
-rw-r--r--addons/website_rating/controllers/__init__.py1
-rw-r--r--addons/website_rating/controllers/portal_rating.py17
-rw-r--r--addons/website_rating/models/mail_message.py16
-rw-r--r--addons/website_rating/models/rating.py19
-rw-r--r--addons/website_rating/static/src/js/portal_chatter.js203
-rw-r--r--addons/website_rating/static/src/xml/portal_chatter.xml77
-rw-r--r--addons/website_rating/views/rating_templates.xml8
-rw-r--r--addons/website_rating/views/rating_views.xml13
-rw-r--r--addons/website_slides/data/slide_user_demo.xml3
11 files changed, 335 insertions, 25 deletions
diff --git a/addons/rating/views/rating_view.xml b/addons/rating/views/rating_view.xml
index 423c96b7e27..768577979af 100644
--- a/addons/rating/views/rating_view.xml
+++ b/addons/rating/views/rating_view.xml
@@ -21,7 +21,7 @@
<field name="name">rating.rating.form</field>
<field name="model">rating.rating</field>
<field name="arch" type="xml">
- <form string="Rating" create="false" edit="false">
+ <form string="Rating" create="false">
<sheet>
<div class="oe_button_box" name="button_box">
<button class="oe_stat_button" type="object" name="action_open_rated_object" icon="fa-file">
diff --git a/addons/website_rating/__manifest__.py b/addons/website_rating/__manifest__.py
index 610a61f3183..3a454763d21 100644
--- a/addons/website_rating/__manifest__.py
+++ b/addons/website_rating/__manifest__.py
@@ -11,6 +11,7 @@ Bridge module for rating on website stuff.
'depends': ['rating', 'website_mail'],
'data': [
'views/assets.xml',
+ 'views/rating_views.xml',
'views/portal_templates.xml',
'views/rating_templates.xml',
],
diff --git a/addons/website_rating/controllers/__init__.py b/addons/website_rating/controllers/__init__.py
index be30644ac93..514e3fde3d2 100644
--- a/addons/website_rating/controllers/__init__.py
+++ b/addons/website_rating/controllers/__init__.py
@@ -2,3 +2,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import portal_chatter
+from . import portal_rating
diff --git a/addons/website_rating/controllers/portal_rating.py b/addons/website_rating/controllers/portal_rating.py
new file mode 100644
index 00000000000..06d1fe06ac1
--- /dev/null
+++ b/addons/website_rating/controllers/portal_rating.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import http, _
+from odoo.http import request
+
+
+class PortalRating(http.Controller):
+
+ @http.route(['/website/rating/comment'], type='json', auth="user", method=['POST'], website=True)
+ def publish_rating_comment(self, rating_id, publisher_comment):
+ rating = request.env['rating.rating'].search([('id', '=', int(rating_id))])
+ if not rating:
+ return {'error': _('Invalid rating')}
+ rating.write({'publisher_comment': publisher_comment})
+ # return to the front-end the created/updated publisher comment
+ return rating.read(['publisher_comment', 'publisher_id', 'publisher_datetime'])[0]
diff --git a/addons/website_rating/models/mail_message.py b/addons/website_rating/models/mail_message.py
index 5a31a0d96c2..8502d0fad0c 100644
--- a/addons/website_rating/models/mail_message.py
+++ b/addons/website_rating/models/mail_message.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo import api, fields, models
+from odoo import models
class MailMessage(models.Model):
@@ -12,3 +12,17 @@ class MailMessage(models.Model):
if self._context.get('rating_include'):
field_list += ['rating_value']
return super(MailMessage, self)._portal_message_format(field_list)
+
+ def _message_read_dict_postprocess(self, message_values, message_tree):
+ """ Override the method to add information about a publisher comment
+ on each rating messages if requested, and compute a plaintext value of it.
+ """
+ res = super(MailMessage, self)._message_read_dict_postprocess(message_values, message_tree)
+
+ if self._context.get('rating_include'):
+ infos = ["id", "publisher_comment", "publisher_id", "publisher_datetime", "message_id"]
+ related_rating = self.env['rating.rating'].search([('message_id', 'in', self.ids)]).read(infos)
+ mid_rating_tree = dict((rating['message_id'][0], rating) for rating in related_rating)
+ for values in message_values:
+ values["rating"] = mid_rating_tree.get(values['id'], {})
+ return res
diff --git a/addons/website_rating/models/rating.py b/addons/website_rating/models/rating.py
index 1b5e99e3e62..3c5f3586c59 100644
--- a/addons/website_rating/models/rating.py
+++ b/addons/website_rating/models/rating.py
@@ -1,10 +1,25 @@
# -*- coding: utf-8 -*-
-from odoo import fields, models
+from odoo import fields, models, exceptions, _
-class Rating(models.Model):
+class Rating(models.Model):
_inherit = 'rating.rating'
# Add this related field to mail.message for performance reason
website_published = fields.Boolean(related='message_id.website_published', store=True, readonly=False)
+ # Adding information for comment a rating message
+ publisher_comment = fields.Text("Publisher Comment")
+ publisher_id = fields.Many2one('res.partner', 'Commented by',
+ ondelete='set null', readonly=True)
+ publisher_datetime = fields.Datetime("Commented on", readonly=True)
+
+ def write(self, values):
+ if values.get('publisher_comment'):
+ if not self.env.user.has_group("website.group_website_publisher"):
+ raise exceptions.AccessError(_("Only the publisher of the website can change the rating comment"))
+ if not values.get('publisher_datetime'):
+ values['publisher_datetime'] = fields.Datetime.now()
+ if not values.get('publisher_id'):
+ values['publisher_id'] = self.env.user.partner_id.id
+ return super(Rating, self).write(values)
diff --git a/addons/website_rating/static/src/js/portal_chatter.js b/addons/website_rating/static/src/js/portal_chatter.js
index d8d4f9d6d29..bd65e6b339d 100644
--- a/addons/website_rating/static/src/js/portal_chatter.js
+++ b/addons/website_rating/static/src/js/portal_chatter.js
@@ -1,12 +1,15 @@
odoo.define('rating.portal.chatter', function (require) {
'use strict';
+var core = require('web.core');
var portalChatter = require('portal.chatter');
var utils = require('web.utils');
+var time = require('web.time');
+var _t = core._t;
var PortalChatter = portalChatter.PortalChatter;
-
-var STAR_RATING_RATIO = 2; // conversion factor from the star (1-5) to the db rating range (1-10)
+var qweb = core.qweb;
+var STAR_RATING_RATIO = 2; // conversion factor from the star (1-5) to the db rating range (1-10)
/**
* PortalChatter
@@ -15,8 +18,15 @@ var STAR_RATING_RATIO = 2; // conversion factor from the star (1-5) to the db r
*/
PortalChatter.include({
events: _.extend({}, PortalChatter.prototype.events, {
- "click .o_website_rating_select": "_onClickStarDomain",
- "click .o_website_rating_select_text": "_onClickStarDomainReset",
+ // star based control
+ 'click .o_website_rating_select': '_onClickStarDomain',
+ 'click .o_website_rating_select_text': '_onClickStarDomainReset',
+ // publisher comments
+ 'click .o_wrating_js_publisher_comment_btn': '_onClickPublisherComment',
+ 'click .o_wrating_js_publisher_comment_edit': '_onClickPublisherComment',
+ 'click .o_wrating_js_publisher_comment_delete': '_onClickPublisherCommentDelete',
+ 'click .o_wrating_js_publisher_comment_submit': '_onClickPublisherCommentSubmit',
+ 'click .o_wrating_js_publisher_comment_cancel': '_onClickPublisherCommentCancel',
}),
xmlDependencies: (PortalChatter.prototype.xmlDependencies || [])
.concat([
@@ -56,10 +66,13 @@ PortalChatter.include({
var self = this;
messages = this._super.apply(this, arguments);
if (this.options['display_rating']) {
- _.each(messages, function (m) {
- m['rating_value'] = self.roundToHalf(m['rating_value'] / STAR_RATING_RATIO);
+ _.each(messages, function (m, i) {
+ m.rating_value = self.roundToHalf(m['rating_value'] / STAR_RATING_RATIO);
+ m.rating = self._preprocessCommentData(m.rating, i);
});
}
+ // save messages in the widget to process correctly the publisher comment templates
+ this.messages = messages;
return messages;
},
/**
@@ -126,16 +139,77 @@ PortalChatter.include({
return params;
},
+ /**
+ * Default rating data for publisher comment qweb template
+ * @private
+ * @param {Integer} messageIndex
+ */
+ _newPublisherCommentData: function (messageIndex) {
+ return {
+ mes_index: messageIndex,
+ publisher_id: this.options.partner_id,
+ publisher_avatar: _.str.sprintf('/web/image/%s/%s/image_64/50x50', 'res.partner', this.options.partner_id),
+ publisher_name: _t("Write your comment"),
+ publisher_datetime: '',
+ publisher_comment: '',
+ };
+ },
+
+ /**
+ * preprocess the rating data comming from /website/rating/comment or the chatter_init
+ * Can be also use to have new rating data for a new publisher comment
+ * @param {JSON} rawRating
+ * @returns {JSON} the process rating data
+ */
+ _preprocessCommentData: function (rawRating, messageIndex) {
+ var ratingData = {
+ id: rawRating.id,
+ mes_index: messageIndex,
+ publisher_datetime: rawRating.publisher_datetime ? moment(time.str_to_datetime(rawRating.publisher_datetime)).format('MMMM Do YYYY, h:mm:ss a') : "",
+ publisher_comment: rawRating.publisher_comment ? rawRating.publisher_comment : '',
+ };
+
+ // split array (id, display_name) of publisher_id into publisher_id and publisher_name
+ if (rawRating.publisher_id && rawRating.publisher_id.length >= 2) {
+ ratingData.publisher_id = rawRating.publisher_id[0];
+ ratingData.publisher_name = rawRating.publisher_id[1];
+ ratingData.publisher_avatar = _.str.sprintf('/web/image/%s/%s/image_64/50x50', 'res.partner', ratingData.publisher_id);
+ }
+ var commentData = _.extend(this._newPublisherCommentData(messageIndex), ratingData);
+ return commentData;
+ },
+
+ /** ---------------
+ * Selection of elements for the publisher comment feature
+ * Only available from a source in a publisher_comment or publisher_comment_form template
+ */
+
+ _getCommentContainer: function ($source) {
+ return $source.parents(".o_wrating_publisher_container").first().find(".o_wrating_publisher_comment").first();
+ },
+
+ _getCommentButton: function ($source) {
+ return $source.parents(".o_wrating_publisher_container").first().find(".o_wrating_js_publisher_comment_btn").first();
+ },
+
+ _getCommentTextarea: function ($source) {
+ return $source.parents(".o_wrating_publisher_container").first().find(".o_portal_rating_comment_input").first();
+ },
+
+ _focusTextComment: function ($source) {
+ this._getCommentTextarea($source).focus();
+ },
+
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
- * @param {MouseEvent} event
+ * @param {MouseEvent} ev
*/
- _onClickStarDomain: function (e) {
- var $tr = this.$(e.currentTarget);
+ _onClickStarDomain: function (ev) {
+ var $tr = this.$(ev.currentTarget);
var num = $tr.data('star');
if ($tr.css('opacity') === '1') {
this.set('rating_value', num);
@@ -153,17 +227,120 @@ PortalChatter.include({
},
/**
* @private
- * @param {MouseEvent} event
+ * @param {MouseEvent} ev
*/
- _onClickStarDomainReset: function (e) {
- e.stopPropagation();
- e.preventDefault();
+ _onClickStarDomainReset: function (ev) {
+ ev.stopPropagation();
+ ev.preventDefault();
this.set('rating_value', false);
this.$('.o_website_rating_select_text').css('visibility', 'hidden');
this.$('.o_website_rating_select').css({
'opacity': 1,
});
},
+
+ /**
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickPublisherComment: function (ev) {
+ var $source = this.$(ev.currentTarget);
+ // If the form is already present => like cancel remove the form
+ if (this._getCommentTextarea($source).length === 1) {
+ this._getCommentContainer($source).empty();
+ return;
+ }
+ var messageIndex = $source.data("mes_index");
+ var data = {is_publisher: this.options['is_user_publisher']};
+ data.rating = this._newPublisherCommentData(messageIndex);
+
+ var oldRating = this.messages[messageIndex].rating;
+ data.rating.publisher_comment = oldRating.publisher_comment ? oldRating.publisher_comment : '';
+ this._getCommentContainer($source).html($(qweb.render("website_rating.chatter_rating_publisher_form", data)));
+ this._focusTextComment($source);
+ },
+
+ /**
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickPublisherCommentDelete: function (ev) {
+ var self = this;
+ var $source = this.$(ev.currentTarget);
+
+ var messageIndex = $source.data("mes_index");
+ var ratingId = this.messages[messageIndex].rating.id;
+
+ this._rpc({
+ route: '/website/rating/comment',
+ params: {
+ "rating_id": ratingId,
+ "publisher_comment": '' // Empty publisher comment means no comment
+ }
+ }).then(function (res) {
+ self.messages[messageIndex].rating = self._preprocessCommentData(res, messageIndex);
+ self._getCommentButton($source).removeClass("d-none");
+ self._getCommentContainer($source).empty();
+ });
+ },
+
+ /**
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickPublisherCommentSubmit: function (ev) {
+ var self = this;
+ var $source = this.$(ev.currentTarget);
+
+ var messageIndex = $source.data("mes_index");
+ var comment = this._getCommentTextarea($source).val();
+ var ratingId = this.messages[messageIndex].rating.id;
+
+ this._rpc({
+ route: '/website/rating/comment',
+ params: {
+ "rating_id": ratingId,
+ "publisher_comment": comment
+ }
+ }).then(function (res) {
+
+ // Modify the related message
+ self.messages[messageIndex].rating = self._preprocessCommentData(res, messageIndex);
+ if (self.messages[messageIndex].rating.publisher_comment !== '') {
+ // Remove the button comment if exist and render the comment
+ self._getCommentButton($source).addClass('d-none');
+ self._getCommentContainer($source).html($(qweb.render("website_rating.chatter_rating_publisher_comment", {
+ rating: self.messages[messageIndex].rating,
+ is_publisher: self.options.is_user_publisher
+ })));
+ } else {
+ // Empty string or false considers as no comment
+ self._getCommentButton($source).removeClass("d-none");
+ self._getCommentContainer($source).empty();
+ }
+ });
+ },
+
+ /**
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickPublisherCommentCancel: function (ev) {
+ var $source = this.$(ev.currentTarget);
+ var messageIndex = $source.data("mes_index");
+
+ var comment = this.messages[messageIndex].rating.publisher_comment;
+ if (comment) {
+ var data = {
+ rating: this.messages[messageIndex].rating,
+ is_publisher: this.options.is_user_publisher
+ };
+ this._getCommentContainer($source).html($(qweb.render("website_rating.chatter_rating_publisher_comment", data)));
+ } else {
+ this._getCommentContainer($source).empty();
+ }
+ },
+
/**
* @private
*/
diff --git a/addons/website_rating/static/src/xml/portal_chatter.xml b/addons/website_rating/static/src/xml/portal_chatter.xml
index c2bbb2781e6..36a19759aba 100644
--- a/addons/website_rating/static/src/xml/portal_chatter.xml
+++ b/addons/website_rating/static/src/xml/portal_chatter.xml
@@ -24,6 +24,15 @@
</t>
</t>
</t>
+ <t t-jquery=".o_portal_chatter_attachments" t-operation="after">
+ <!--Only possible if a rating is link to the message, for now we can't comment if no rating
+ is link to the message (because publisher comment data
+ is on the rating.rating model - one comment max) -->
+ <t t-if="message.rating and message.rating.id" t-call="website_rating.chatter_rating_publisher">
+ <t t-set="is_publisher" t-value="widget.options['is_user_publisher']"/>
+ <t t-set="rating" t-value="message.rating"/>
+ </t>
+ </t>
</t>
<t t-extend="portal.Chatter">
@@ -36,4 +45,72 @@
</t>
</t>
</t>
+
+ <!--
+ New templates specific of rating in Chatter
+ -->
+ <t t-name="website_rating.chatter_rating_publisher">
+ <div class="o_wrating_publisher_container">
+ <button t-if="is_publisher"
+ t-attf-class="btn px-2 btn-sm border o_wrating_js_publisher_comment_btn {{ rating.publisher_comment !== '' ? 'd-none' : '' }}"
+ t-att-data-mes_index="rating.mes_index">
+ <i class="fa fa-comment text-muted mr-1"/>Comment
+ </button>
+ <div class="o_wrating_publisher_comment mt-2 mb-2">
+ <t t-if="rating.publisher_comment" t-call="website_rating.chatter_rating_publisher_comment"/>
+ </div>
+ </div>
+ </t>
+
+ <t t-name="website_rating.chatter_rating_publisher_comment">
+ <div class="media o_portal_chatter_message">
+ <img class="o_portal_chatter_avatar" t-att-src="rating.publisher_avatar" alt="avatar"/>
+ <div class="media-body">
+ <div class="o_portal_chatter_message_title">
+ <div class="d-inline-block">
+ <h5><t t-esc="rating.publisher_name"/></h5>
+ </div>
+ <div t-if="is_publisher" class="dropdown d-inline-block">
+ <button class="btn py-0" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+ <i class="fa fa-ellipsis-v"/>
+ </button>
+ <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
+ <button class="dropdown-item o_wrating_js_publisher_comment_edit" t-att-data-mes_index="rating.mes_index">
+ <i class="fa fa-fw fa-pencil mr-1"/>Edit
+ </button>
+ <button class="dropdown-item o_wrating_js_publisher_comment_delete" t-att-data-mes_index="rating.mes_index">
+ <i class="fa fa-fw fa-trash-o mr-1"/>Delete
+ </button>
+ </div>
+ </div>
+ <p>Published on <t t-esc="rating.publisher_datetime"/></p>
+ </div>
+ <t t-raw="rating.publisher_comment"/>
+ </div>
+ </div>
+ </t>
+ <t t-name="website_rating.chatter_rating_publisher_form">
+ <div t-if="is_publisher" class="media o_portal_chatter_message shadow bg-white rounded px-3 py-3 my-1">
+ <img class="o_portal_chatter_avatar" t-att-src="rating.publisher_avatar" alt="avatar"/>
+ <div class="media-body">
+ <div class="o_portal_chatter_message_title">
+ <h5><t t-esc="rating.publisher_name"/></h5>
+ <p>Published on <t t-esc="rating.publisher_datetime"/></p>
+ </div>
+ <textarea rows="3" class="form-control o_portal_rating_comment_input"><t t-esc="rating.publisher_comment"/></textarea>
+ <div>
+ <button class="btn btn-primary mt-2 o_wrating_js_publisher_comment_submit" t-att-data-mes_index="rating.mes_index">
+ <t t-if="rating.publisher_comment === ''">
+ Post comment
+ </t><t t-else="">
+ Update comment
+ </t>
+ </button>
+ <button class="border btn btn-light mt-2 bg-white o_wrating_js_publisher_comment_cancel" t-att-data-mes_index="rating.mes_index">
+ Cancel
+ </button>
+ </div>
+ </div>
+ </div>
+ </t>
</templates>
diff --git a/addons/website_rating/views/rating_templates.xml b/addons/website_rating/views/rating_templates.xml
index 8f7a287f336..4eabc7c3b85 100644
--- a/addons/website_rating/views/rating_templates.xml
+++ b/addons/website_rating/views/rating_templates.xml
@@ -1,12 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
-
- <template id="message_thread" inherit_id="portal.message_thread">
- <xpath expr="//div[@id='discussion']" position="attributes">
- <attribute name="t-att-data-display_rating">display_rating or False</attribute>
- </xpath>
- </template>
-
<!--
Display static star widget, and open rating composer on click
This template provide the container of the Popup Rating Composer. The rest is done in js.
@@ -34,5 +27,4 @@
t-att-data-disable_composer="disable_composer">
</div>
</template>
-
</odoo>
diff --git a/addons/website_rating/views/rating_views.xml b/addons/website_rating/views/rating_views.xml
new file mode 100644
index 00000000000..2a9e5bf0866
--- /dev/null
+++ b/addons/website_rating/views/rating_views.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <record id="rating_rating_view_form" model="ir.ui.view">
+ <field name="name">rating.rating.view.form</field>
+ <field name="model">rating.rating</field>
+ <field name="inherit_id" ref="rating.rating_rating_view_form"/>
+ <field name="arch" type="xml">
+ <xpath expr="//field[@name='feedback']" position="after">
+ <field name="publisher_comment" string="Publisher comment"/>
+ </xpath>
+ </field>
+ </record>
+</odoo>
diff --git a/addons/website_slides/data/slide_user_demo.xml b/addons/website_slides/data/slide_user_demo.xml
index e53fdb4cb0d..847d2bfa297 100644
--- a/addons/website_slides/data/slide_user_demo.xml
+++ b/addons/website_slides/data/slide_user_demo.xml
@@ -191,6 +191,9 @@
<field name="rating">6</field>
<field name="feedback">Interesting. Could be great to include more examples.</field>
<field name="message_id" ref="website_slides.message_channel_1_portal"/>
+ <field name="publisher_comment">Thanks for the feedback! We just updated first lessons to better include newcomers.</field>
+ <field name="publisher_id" ref="base.partner_admin"/>
+ <field name="publisher_datetime" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M')"/>
</record>
<function model="slide.channel" name="message_subscribe"