diff options
| -rw-r--r-- | addons/rating/views/rating_view.xml | 2 | ||||
| -rw-r--r-- | addons/website_rating/__manifest__.py | 1 | ||||
| -rw-r--r-- | addons/website_rating/controllers/__init__.py | 1 | ||||
| -rw-r--r-- | addons/website_rating/controllers/portal_rating.py | 17 | ||||
| -rw-r--r-- | addons/website_rating/models/mail_message.py | 16 | ||||
| -rw-r--r-- | addons/website_rating/models/rating.py | 19 | ||||
| -rw-r--r-- | addons/website_rating/static/src/js/portal_chatter.js | 203 | ||||
| -rw-r--r-- | addons/website_rating/static/src/xml/portal_chatter.xml | 77 | ||||
| -rw-r--r-- | addons/website_rating/views/rating_templates.xml | 8 | ||||
| -rw-r--r-- | addons/website_rating/views/rating_views.xml | 13 | ||||
| -rw-r--r-- | addons/website_slides/data/slide_user_demo.xml | 3 |
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" |
