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_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..577eced363b --- /dev/null +++ b/addons/portal/static/src/js/portal_composer.js @@ -0,0 +1,172 @@ +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 () { + 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'); + 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).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(); + }); + }, + + //-------------------------------------------------------------------------- + // 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) { + attachment.state = 'pending'; + 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/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; + } + } +} 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/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..d9a78f17255 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) @@ -61,7 +61,8 @@ publicWidget.registry.RatingPopupComposer = publicWidget.Widget.extend({ * @override */ start: function () { - var ratingPopup = new RatingPopupComposer(this, this.$el.data()); + var ratingPopupData = this.$el.data(); + var ratingPopup = new RatingPopupComposer(this, ratingPopupData); return Promise.all([ this._super.apply(this, arguments), ratingPopup.appendTo(this.$el) diff --git a/addons/website_rating/views/rating_templates.xml b/addons/website_rating/views/rating_templates.xml index 4eabc7c3b85..03ef04a4412 100644 --- a/addons/website_rating/views/rating_templates.xml +++ b/addons/website_rating/views/rating_templates.xml @@ -23,6 +23,7 @@ t-att-data-default_message="default_message" t-att-data-default_message_id="default_message_id" t-att-data-default_rating_value="default_rating_value" + t-att-data-default_attachment_ids="default_attachment_ids" t-att-data-force_submit_url="force_submit_url" t-att-data-disable_composer="disable_composer">
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 @@ - + +