From bb197b1f870c64c120462f044441c19ceaf2e97b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 4 Sep 2019 09:19:49 +0000 Subject: [PATCH 1/3] [MOV] portal: split portal chatter from composer PURPOSE Allow to manage attachments when updating a frontend review. SPECIFICATIONS As those widgets grow it is better to have them in separate files in order to better understand code organization. Future commits will improve composer behavior, notably in attachments management. LINKS Task 2066600 (manage attachments) Task 2058595 (eLearning v13 testing) --- addons/portal/static/src/js/portal_chatter.js | 154 +---------------- .../portal/static/src/js/portal_composer.js | 161 ++++++++++++++++++ addons/portal/views/assets.xml | 1 + .../static/src/js/portal_composer.js | 4 +- .../static/src/js/portal_rating_composer.js | 4 +- 5 files changed, 168 insertions(+), 156 deletions(-) create mode 100644 addons/portal/static/src/js/portal_composer.js diff --git a/addons/portal/static/src/js/portal_chatter.js b/addons/portal/static/src/js/portal_chatter.js index a2a089d2a96..0b23ec2c386 100644 --- a/addons/portal/static/src/js/portal_chatter.js +++ b/addons/portal/static/src/js/portal_chatter.js @@ -1,163 +1,14 @@ odoo.define('portal.chatter', function (require) { 'use strict'; -var ajax = require('web.ajax'); var core = require('web.core'); var publicWidget = require('web.public.widget'); var time = require('web.time'); +var portalComposer = require('portal.composer'); var qweb = core.qweb; var _t = core._t; -/** - * Widget PortalComposer - * - * Display the composer (according to access right) - * - */ -var PortalComposer = publicWidget.Widget.extend({ - template: 'portal.Composer', - xmlDependencies: ['/portal/static/src/xml/portal_chatter.xml'], - events: { - 'change .o_portal_chatter_file_input': '_onFileInputChange', - 'click .o_portal_chatter_attachment_btn': '_onAttachmentButtonClick', - 'click .o_portal_chatter_attachment_delete': 'async _onAttachmentDeleteClick', - 'click .o_portal_chatter_composer_btn': 'async _onSubmitButtonClick', - }, - - /** - * @constructor - */ - init: function (parent, options) { - this._super.apply(this, arguments); - this.options = _.defaults(options || {}, { - 'allow_composer': true, - 'display_composer': false, - 'csrf_token': odoo.csrf_token, - 'token': false, - 'res_model': false, - 'res_id': false, - }); - this.attachments = []; - }, - /** - * @override - */ - start: function () { - this.$attachmentButton = this.$('.o_portal_chatter_attachment_btn'); - this.$fileInput = this.$('.o_portal_chatter_file_input'); - this.$sendButton = this.$('.o_portal_chatter_composer_btn'); - this.$attachments = this.$('.o_portal_chatter_composer_form .o_portal_chatter_attachments'); - this.$attachmentIds = this.$('.o_portal_chatter_attachment_ids'); - this.$attachmentTokens = this.$('.o_portal_chatter_attachment_tokens'); - - return this._super.apply(this, arguments); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * @private - */ - _onAttachmentButtonClick: function () { - this.$fileInput.click(); - }, - /** - * @private - * @param {Event} ev - * @returns {Promise} - */ - _onAttachmentDeleteClick: function (ev) { - var self = this; - var attachmentId = $(ev.currentTarget).closest('.o_portal_chatter_attachment').data('id'); - var accessToken = _.find(this.attachments, {'id': attachmentId}).access_token; - ev.preventDefault(); - ev.stopPropagation(); - - this.$sendButton.prop('disabled', true); - - return this._rpc({ - route: '/portal/attachment/remove', - params: { - 'attachment_id': attachmentId, - 'access_token': accessToken, - }, - }).then(function () { - self.attachments = _.reject(self.attachments, {'id': attachmentId}); - self._updateAttachments(); - self.$sendButton.prop('disabled', false); - }); - }, - /** - * @private - * @returns {Promise} - */ - _onFileInputChange: function () { - var self = this; - - this.$sendButton.prop('disabled', true); - - return Promise.all(_.map(this.$fileInput[0].files, function (file) { - return new Promise(function (resolve, reject) { - var data = { - 'name': file.name, - 'file': file, - 'res_id': self.options.res_id, - 'res_model': self.options.res_model, - 'access_token': self.options.token, - }; - ajax.post('/portal/attachment/add', data).then(function (attachment) { - self.attachments.push(attachment); - self._updateAttachments(); - resolve(); - }).guardedCatch(function (error) { - self.displayNotification({ - title: _t("Something went wrong."), - message: _.str.sprintf(_t("The file %s could not be saved."), - _.escape(file.name)), - type: 'warning', - sticky: true, - }); - resolve(); - }); - }); - })).then(function () { - self.$sendButton.prop('disabled', false); - }); - }, - /** - * Returns a Promise that is never resolved to prevent sending the form - * twice when clicking twice on the button, in combination with the `async` - * in the event definition. - * - * @private - * @returns {Promise} - */ - _onSubmitButtonClick: function () { - return new Promise(function (resolve, reject) {}); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * @private - */ - _updateAttachments: function () { - this.$attachmentIds.val(_.pluck(this.attachments, 'id')); - this.$attachmentTokens.val(_.pluck(this.attachments, 'access_token')); - this.$attachments.html(qweb.render('portal.Chatter.Attachments', { - attachments: this.attachments, - showDelete: true, - })); - }, -}); - - - /** * Widget PortalChatter * @@ -237,7 +88,7 @@ var PortalChatter = publicWidget.Widget.extend({ // instanciate and insert composer widget if (this.options['display_composer']) { - this._composer = new PortalComposer(this, this.options); + this._composer = new portalComposer.PortalComposer(this, this.options); defs.push(this._composer.replace(this.$('.o_portal_chatter_composer'))); } @@ -426,7 +277,6 @@ publicWidget.registry.portalChatter = publicWidget.Widget.extend({ }); return { - PortalComposer: PortalComposer, PortalChatter: PortalChatter, }; }); diff --git a/addons/portal/static/src/js/portal_composer.js b/addons/portal/static/src/js/portal_composer.js new file mode 100644 index 00000000000..ecd2d0cc6c1 --- /dev/null +++ b/addons/portal/static/src/js/portal_composer.js @@ -0,0 +1,161 @@ +odoo.define('portal.composer', function (require) { +'use strict'; + +var ajax = require('web.ajax'); +var core = require('web.core'); +var publicWidget = require('web.public.widget'); + +var qweb = core.qweb; +var _t = core._t; + +/** + * Widget PortalComposer + * + * Display the composer (according to access right) + * + */ +var PortalComposer = publicWidget.Widget.extend({ + template: 'portal.Composer', + xmlDependencies: ['/portal/static/src/xml/portal_chatter.xml'], + events: { + 'change .o_portal_chatter_file_input': '_onFileInputChange', + 'click .o_portal_chatter_attachment_btn': '_onAttachmentButtonClick', + 'click .o_portal_chatter_attachment_delete': 'async _onAttachmentDeleteClick', + 'click .o_portal_chatter_composer_btn': 'async _onSubmitButtonClick', + }, + + /** + * @constructor + */ + init: function (parent, options) { + this._super.apply(this, arguments); + this.options = _.defaults(options || {}, { + 'allow_composer': true, + 'display_composer': false, + 'csrf_token': odoo.csrf_token, + 'token': false, + 'res_model': false, + 'res_id': false, + }); + this.attachments = []; + }, + /** + * @override + */ + start: function () { + this.$attachmentButton = this.$('.o_portal_chatter_attachment_btn'); + this.$fileInput = this.$('.o_portal_chatter_file_input'); + this.$sendButton = this.$('.o_portal_chatter_composer_btn'); + this.$attachments = this.$('.o_portal_chatter_composer_form .o_portal_chatter_attachments'); + this.$attachmentIds = this.$('.o_portal_chatter_attachment_ids'); + this.$attachmentTokens = this.$('.o_portal_chatter_attachment_tokens'); + + return this._super.apply(this, arguments); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + */ + _onAttachmentButtonClick: function () { + this.$fileInput.click(); + }, + /** + * @private + * @param {Event} ev + * @returns {Promise} + */ + _onAttachmentDeleteClick: function (ev) { + var self = this; + var attachmentId = $(ev.currentTarget).closest('.o_portal_chatter_attachment').data('id'); + var accessToken = _.find(this.attachments, {'id': attachmentId}).access_token; + ev.preventDefault(); + ev.stopPropagation(); + + this.$sendButton.prop('disabled', true); + + return this._rpc({ + route: '/portal/attachment/remove', + params: { + 'attachment_id': attachmentId, + 'access_token': accessToken, + }, + }).then(function () { + self.attachments = _.reject(self.attachments, {'id': attachmentId}); + self._updateAttachments(); + self.$sendButton.prop('disabled', false); + }); + }, + /** + * @private + * @returns {Promise} + */ + _onFileInputChange: function () { + var self = this; + + this.$sendButton.prop('disabled', true); + + return Promise.all(_.map(this.$fileInput[0].files, function (file) { + return new Promise(function (resolve, reject) { + var data = { + 'name': file.name, + 'file': file, + 'res_id': self.options.res_id, + 'res_model': self.options.res_model, + 'access_token': self.options.token, + }; + ajax.post('/portal/attachment/add', data).then(function (attachment) { + self.attachments.push(attachment); + self._updateAttachments(); + resolve(); + }).guardedCatch(function (error) { + self.displayNotification({ + title: _t("Something went wrong."), + message: _.str.sprintf(_t("The file %s could not be saved."), + _.escape(file.name)), + type: 'warning', + sticky: true, + }); + resolve(); + }); + }); + })).then(function () { + self.$sendButton.prop('disabled', false); + }); + }, + /** + * Returns a Promise that is never resolved to prevent sending the form + * twice when clicking twice on the button, in combination with the `async` + * in the event definition. + * + * @private + * @returns {Promise} + */ + _onSubmitButtonClick: function () { + return new Promise(function (resolve, reject) {}); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + */ + _updateAttachments: function () { + this.$attachmentIds.val(_.pluck(this.attachments, 'id')); + this.$attachmentTokens.val(_.pluck(this.attachments, 'access_token')); + this.$attachments.html(qweb.render('portal.Chatter.Attachments', { + attachments: this.attachments, + showDelete: true, + })); + }, +}); + +return { + PortalComposer: PortalComposer, +}; +}); diff --git a/addons/portal/views/assets.xml b/addons/portal/views/assets.xml index 73024f03a0d..2fbcf61da04 100644 --- a/addons/portal/views/assets.xml +++ b/addons/portal/views/assets.xml @@ -19,6 +19,7 @@ + diff --git a/addons/website_rating/static/src/js/portal_composer.js b/addons/website_rating/static/src/js/portal_composer.js index bd7aac7a94d..37b0b9e3511 100644 --- a/addons/website_rating/static/src/js/portal_composer.js +++ b/addons/website_rating/static/src/js/portal_composer.js @@ -2,11 +2,11 @@ odoo.define('rating.portal.composer', function (require) { 'use strict'; var core = require('web.core'); -var portalChatter = require('portal.chatter'); +var portalComposer = require('portal.composer'); var _t = core._t; -var PortalComposer = portalChatter.PortalComposer; +var PortalComposer = portalComposer.PortalComposer; var STAR_RATING_RATIO = 2; // conversion factor from the star (1-5) to the db rating range (1-10) diff --git a/addons/website_rating/static/src/js/portal_rating_composer.js b/addons/website_rating/static/src/js/portal_rating_composer.js index 78e6a4d4d1d..c73ea5d0f78 100644 --- a/addons/website_rating/static/src/js/portal_rating_composer.js +++ b/addons/website_rating/static/src/js/portal_rating_composer.js @@ -3,9 +3,9 @@ odoo.define('portal.rating.composer', function (require) { var publicWidget = require('web.public.widget'); var session = require('web.session'); -var portalChatter = require('portal.chatter'); +var portalComposer = require('portal.composer'); -var PortalComposer = portalChatter.PortalComposer; +var PortalComposer = portalComposer.PortalComposer; var STAR_RATING_RATIO = 2; // conversion factor from the star (1-5) to the db rating range (1-10) From c0769308638d00cb983f84d9fa8736400463fef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 4 Sep 2019 11:11:05 +0000 Subject: [PATCH 2/3] [FIX] portal: allow portal discuss attachment css to be applied on popup composer PURPOSE Allow to manage attachments when updating a frontend review. SPECIFICATIONS Popup composer used notably in website_rating (eLearning, eCommerce) is not instantiated inside a frontend chatter. It does not get all available CSS rules as those are all set inside o_portal_chatter. In this commit we move some CSS rules to allow using them in composer or chatter independently of how they are instantiated. LINKS Task 2066600 (manage attachments) Task 2058595 (eLearning v13 testing) --- addons/portal/static/src/scss/portal.scss | 108 ++++++++++++---------- 1 file changed, 60 insertions(+), 48 deletions(-) diff --git a/addons/portal/static/src/scss/portal.scss b/addons/portal/static/src/scss/portal.scss index 5f24609f233..5bd0b6ff02d 100644 --- a/addons/portal/static/src/scss/portal.scss +++ b/addons/portal/static/src/scss/portal.scss @@ -418,54 +418,6 @@ img.o_we_custom_image { width: 50px; } -.o_portal_chatter { - padding: 10px; - - .o_portal_chatter_avatar{ - width: 45px; - height: 45px; - margin-right: 1rem; - } - - .o_portal_chatter_header { - margin-bottom: 15px; - } - - .o_portal_chatter_composer { - margin-bottom: 15px; - } - - .o_portal_chatter_messages { - margin-bottom: 15px; - - .o_portal_chatter_message_title { - p { - font-size:85%; - color:$o-main-color-muted; - margin: 0px; - } - } - } - - .o_portal_chatter_pager { - text-align: center; - } - - .o_portal_chatter_attachment { - .o_portal_chatter_attachment_name { - word-wrap: break-word; - } - - .o_portal_chatter_attachment_delete { - @include o-position-absolute($top: 0, $right: 0); - opacity: 0; - } - &:hover .o_portal_chatter_attachment_delete { - opacity: 1; - } - } -} - form label { font-weight: $font-weight-bold; &.label-optional { @@ -512,3 +464,63 @@ form label { } } } + +// ------------------------------------------------------------ +// Frontend Discuss widget +// ------------------------------------------------------------ + +// Readonly display +.o_portal_chatter { + padding: 10px; + + .o_portal_chatter_avatar { + width: 45px; + height: 45px; + margin-right: 1rem; + } + + .o_portal_chatter_header { + margin-bottom: 15px; + } + + .o_portal_chatter_composer { + margin-bottom: 15px; + } + + .o_portal_chatter_messages { + margin-bottom: 15px; + + .o_portal_chatter_message_title { + p { + font-size:85%; + color:$o-main-color-muted; + margin: 0px; + } + } + } + + .o_portal_chatter_pager { + text-align: center; + } + + +} + +// Readonly / Composer mix display +.o_portal_chatter, +.o_portal_chatter_composer { + .o_portal_chatter_attachment { + .o_portal_chatter_attachment_name { + word-wrap: break-word; + } + + .o_portal_chatter_attachment_delete { + @include o-position-absolute($top: 0, $right: 0); + opacity: 0; + } + + &:hover .o_portal_chatter_attachment_delete { + opacity: 1; + } + } +} From a08845d2a32827ce3ac7cabfd198e454b6f07e59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 4 Sep 2019 09:21:18 +0000 Subject: [PATCH 3/3] [IMP] website_{rating, slides}: allow to manage attachments when updating reviews PURPOSE Allow to manage attachments when updating a frontend review. SPECIFICATIONS Currently when posting a review in eLearning or eCommerce one can attach files to its review thanks for portal composer. Rating popup composer allow to update its comment and rating. However this edit mode has two limitations. It does not display attachments previously added and does not allow to add attachments. This commit fixes that by * giving attachment values to the composer, allowing its display by the composer display at startup; * allowing to give new attachments and properly handle them in slides route; Uploaded and validated attachments cannot be removed by users. Indeed message with attachments has probably already be sent to people and removing attachments would lead to information loss. LINKS Task 2066600 (manage attachments) Task 2058595 (eLearning v13 testing) --- addons/portal/controllers/mail.py | 17 ++++--- .../portal/static/src/js/portal_composer.js | 13 ++++- .../portal/static/src/xml/portal_chatter.xml | 2 +- .../static/src/js/portal_rating_composer.js | 3 +- .../website_rating/views/rating_templates.xml | 1 + addons/website_slides/controllers/__init__.py | 3 +- addons/website_slides/controllers/mail.py | 51 +++++++++++++------ addons/website_slides/controllers/main.py | 33 +++++++----- addons/website_slides/controllers/portal.py | 20 -------- .../views/website_slides_templates_course.xml | 3 +- 10 files changed, 86 insertions(+), 60 deletions(-) delete mode 100644 addons/website_slides/controllers/portal.py diff --git a/addons/portal/controllers/mail.py b/addons/portal/controllers/mail.py index 0869089ff69..1673d2d74e9 100644 --- a/addons/portal/controllers/mail.py +++ b/addons/portal/controllers/mail.py @@ -88,6 +88,15 @@ class PortalChatter(http.Controller): def _portal_post_filter_params(self): return ['token', 'hash', 'pid'] + def _portal_post_check_attachments(self, attachment_ids, attachment_tokens): + if len(attachment_tokens) != len(attachment_ids): + raise UserError(_("An access token must be provided for each attachment.")) + for (attachment_id, access_token) in zip(attachment_ids, attachment_tokens): + try: + CustomerPortal._document_check_access(self, 'ir.attachment', attachment_id, access_token) + except (AccessError, MissingError): + raise UserError(_("The attachment %s does not exist or you do not have the rights to access it.") % attachment_id) + @http.route(['/mail/chatter_post'], type='http', methods=['POST'], auth='public', website=True) def portal_chatter_post(self, res_model, res_id, message, redirect=None, attachment_ids='', attachment_tokens='', **kw): """Create a new `mail.message` with the given `message` and/or @@ -103,13 +112,7 @@ class PortalChatter(http.Controller): attachment_ids = [int(attachment_id) for attachment_id in attachment_ids.split(',') if attachment_id] attachment_tokens = [attachment_token for attachment_token in attachment_tokens.split(',') if attachment_token] - if len(attachment_tokens) != len(attachment_ids): - raise UserError(_("An access token must be provided for each attachment.")) - for (attachment_id, access_token) in zip(attachment_ids, attachment_tokens): - try: - CustomerPortal._document_check_access(self, 'ir.attachment', attachment_id, access_token) - except (AccessError, MissingError): - raise UserError(_("The attachment %s does not exist or you do not have the rights to access it.") % attachment_id) + self._portal_post_check_attachments(attachment_ids, attachment_tokens) if message or attachment_ids: # message is received in plaintext and saved in html diff --git a/addons/portal/static/src/js/portal_composer.js b/addons/portal/static/src/js/portal_composer.js index ecd2d0cc6c1..577eced363b 100644 --- a/addons/portal/static/src/js/portal_composer.js +++ b/addons/portal/static/src/js/portal_composer.js @@ -43,6 +43,7 @@ var PortalComposer = publicWidget.Widget.extend({ * @override */ start: function () { + var self = this; this.$attachmentButton = this.$('.o_portal_chatter_attachment_btn'); this.$fileInput = this.$('.o_portal_chatter_file_input'); this.$sendButton = this.$('.o_portal_chatter_composer_btn'); @@ -50,7 +51,16 @@ var PortalComposer = publicWidget.Widget.extend({ this.$attachmentIds = this.$('.o_portal_chatter_attachment_ids'); this.$attachmentTokens = this.$('.o_portal_chatter_attachment_tokens'); - return this._super.apply(this, arguments); + return this._super.apply(this, arguments).then(function () { + if (self.options.default_attachment_ids) { + self.attachments = self.options.default_attachment_ids || []; + _.each(self.attachments, function(attachment) { + attachment.state = 'done'; + }); + self._updateAttachments(); + } + return Promise.resolve(); + }); }, //-------------------------------------------------------------------------- @@ -108,6 +118,7 @@ var PortalComposer = publicWidget.Widget.extend({ 'access_token': self.options.token, }; ajax.post('/portal/attachment/add', data).then(function (attachment) { + attachment.state = 'pending'; self.attachments.push(attachment); self._updateAttachments(); resolve(); diff --git a/addons/portal/static/src/xml/portal_chatter.xml b/addons/portal/static/src/xml/portal_chatter.xml index 8e30720099b..e475b180974 100644 --- a/addons/portal/static/src/xml/portal_chatter.xml +++ b/addons/portal/static/src/xml/portal_chatter.xml @@ -70,7 +70,7 @@
diff --git a/addons/website_slides/controllers/__init__.py b/addons/website_slides/controllers/__init__.py index 5f151657861..65fe13d06bf 100644 --- a/addons/website_slides/controllers/__init__.py +++ b/addons/website_slides/controllers/__init__.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import mail from . import main -from . import portal +from . import mail diff --git a/addons/website_slides/controllers/mail.py b/addons/website_slides/controllers/mail.py index 5ec41d9de88..0766536d538 100644 --- a/addons/website_slides/controllers/mail.py +++ b/addons/website_slides/controllers/mail.py @@ -1,33 +1,51 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -import logging -from odoo.http import request import werkzeug + from werkzeug.exceptions import NotFound, Forbidden from odoo import http -from odoo.addons.portal.controllers.mail import _check_special_access +from odoo.http import request +from odoo.addons.portal.controllers.mail import _check_special_access, PortalChatter from odoo.tools import plaintext2html -_logger = logging.getLogger(__name__) +class SlidesPortalChatter(PortalChatter): -class SlidesMail(http.Controller): + @http.route(['/mail/chatter_post'], type='http', methods=['POST'], auth='public', website=True) + def portal_chatter_post(self, res_model, res_id, message, **kw): + result = super(SlidesPortalChatter, self).portal_chatter_post(res_model, res_id, message, **kw) + if res_model == 'slide.channel': + rating_value = kw.get('rating_value', False) + slide_channel = request.env[res_model].sudo().browse(int(res_id)) + if rating_value and slide_channel and request.env.user.partner_id.id == int(kw.get('pid')): + # apply karma gain rule only once + request.env.user.add_karma(slide_channel.karma_gen_channel_rank) + return result - @http.route('/slides/mail/update_comment', type='http', auth="user") - def mail_update_message(self, res_id, message, message_id, **post): - - res_model = 'slide.channel' # keep this mecanism intern to slide - message_body = plaintext2html(message) + @http.route([ + '/slides/mail/update_comment', + '/mail/chatter_update', + ], type='http', auth="user") + def mail_update_message(self, res_model, res_id, message, message_id, redirect=None, attachment_ids='', attachment_tokens='', **post): + # keep this mecanism intern to slide currently (saas 12.5) as it is + # considered experimental + if res_model != 'slide.channel': + raise Forbidden() res_id = int(res_id) - message_id = int(message_id) - pid = int(post['pid']) if post.get('pid') else False + attachment_ids = [int(attachment_id) for attachment_id in attachment_ids.split(',') if attachment_id] + attachment_tokens = [attachment_token for attachment_token in attachment_tokens.split(',') if attachment_token] + self._portal_post_check_attachments(attachment_ids, attachment_tokens) + + pid = int(post['pid']) if post.get('pid') else False if not _check_special_access(res_model, res_id, token=post.get('token'), _hash=post.get('hash'), pid=pid): raise Forbidden() - # update mail.message + # fetch and update mail.message + message_id = int(message_id) + message_body = plaintext2html(message) domain = [ ('model', '=', res_model), ('res_id', '=', res_id), @@ -40,7 +58,8 @@ class SlidesMail(http.Controller): if not message: raise NotFound() message.write({ - 'body': message_body + 'body': message_body, + 'attachment_ids': [(4, aid) for aid in attachment_ids], }) # update rating @@ -51,4 +70,6 @@ class SlidesMail(http.Controller): 'rating': float(post['rating_value']) }) - return werkzeug.utils.redirect(request.httprequest.referrer, 302) + # redirect to specified or referrer or simply channel page as fallback + redirect_url = redirect or (request.httprequest.referrer and request.httprequest.referrer + '#review') or '/slides/%s' % res_id + return werkzeug.utils.redirect(redirect_url, 302) diff --git a/addons/website_slides/controllers/main.py b/addons/website_slides/controllers/main.py index 028be99a530..cd363b1f1e5 100644 --- a/addons/website_slides/controllers/main.py +++ b/addons/website_slides/controllers/main.py @@ -2,19 +2,18 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import base64 -import itertools +import json import logging import werkzeug import math -from odoo import http, modules, tools, _ -from odoo.exceptions import AccessError, UserError -from odoo.http import request -from odoo.osv import expression - +from odoo import http, tools, _ from odoo.addons.http_routing.models.ir_http import slug from odoo.addons.website_profile.controllers.main import WebsiteProfile from odoo.addons.website.models.ir_http import sitemap_qs2dom +from odoo.exceptions import AccessError, UserError +from odoo.http import request +from odoo.osv import expression _logger = logging.getLogger(__name__) @@ -419,18 +418,28 @@ class WebsiteSlides(WebsiteProfile): 'enable_slide_upload': 'enable_slide_upload' in kw, } if not request.env.user._is_public(): - last_message_values = request.env['mail.message'].search([ + last_message = request.env['mail.message'].search([ ('model', '=', channel._name), ('res_id', '=', channel.id), ('author_id', '=', request.env.user.partner_id.id), ('message_type', '=', 'comment'), ('website_published', '=', True) - ], order='write_date DESC', limit=1).read(['body', 'rating_value']) - last_message_data = last_message_values[0] if last_message_values else {} + ], order='write_date DESC', limit=1) + if last_message: + last_message_values = last_message.read(['body', 'rating_value', 'attachment_ids'])[0] + last_message_attachment_ids = last_message_values.pop('attachment_ids', []) + if last_message_attachment_ids: + last_message_attachment_ids = json.dumps(request.env['ir.attachment'].browse(last_message_attachment_ids).read( + ['id', 'name', 'mimetype', 'file_size', 'access_token'] + )) + else: + last_message_values = {} + last_message_attachment_ids = [] values.update({ - 'last_message_id': last_message_data.get('id'), - 'last_message': tools.html2plaintext(last_message_data.get('body', '')), - 'last_rating_value': last_message_data.get('rating_value'), + 'last_message_id': last_message_values.get('id'), + 'last_message': tools.html2plaintext(last_message_values.get('body', '')), + 'last_rating_value': last_message_values.get('rating_value'), + 'last_message_attachment_ids': last_message_attachment_ids, }) if channel.can_review: values.update({ diff --git a/addons/website_slides/controllers/portal.py b/addons/website_slides/controllers/portal.py deleted file mode 100644 index ae07dd85f8a..00000000000 --- a/addons/website_slides/controllers/portal.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from odoo import http -from odoo.http import request -from odoo.addons.portal.controllers.mail import PortalChatter - - -class SlidesPortalChatter(PortalChatter): - - @http.route(['/mail/chatter_post'], type='http', methods=['POST'], auth='public', website=True) - def portal_chatter_post(self, res_model, res_id, message, **kw): - result = super(SlidesPortalChatter, self).portal_chatter_post(res_model, res_id, message, **kw) - if res_model == 'slide.channel': - rating_value = kw.get('rating_value', False) - slide_channel = request.env[res_model].sudo().browse(int(res_id)) - if rating_value and slide_channel and request.env.user.partner_id.id == int(kw.get('pid')): - # apply karma gain rule only once - request.env.user.add_karma(slide_channel.karma_gen_channel_rank) - return result diff --git a/addons/website_slides/views/website_slides_templates_course.xml b/addons/website_slides/views/website_slides_templates_course.xml index 494b4c78b37..1b92f589a5f 100644 --- a/addons/website_slides/views/website_slides_templates_course.xml +++ b/addons/website_slides/views/website_slides_templates_course.xml @@ -147,9 +147,10 @@ - + +