diff --git a/addons/mass_mailing_slides/views/slide_channel_views.xml b/addons/mass_mailing_slides/views/slide_channel_views.xml
index ddc7c38af22..9b7f35b202c 100644
--- a/addons/mass_mailing_slides/views/slide_channel_views.xml
+++ b/addons/mass_mailing_slides/views/slide_channel_views.xml
@@ -5,7 +5,7 @@
slide.channel
-
+
diff --git a/addons/website_slides/models/slide_channel.py b/addons/website_slides/models/slide_channel.py
index 1c8f7b43485..626edb66c31 100644
--- a/addons/website_slides/models/slide_channel.py
+++ b/addons/website_slides/models/slide_channel.py
@@ -113,16 +113,16 @@ class Channel(models.Model):
('most_viewed', 'Most Viewed')],
string="Featuring Policy", default='latest', required=True)
access_token = fields.Char("Security Token", copy=False, default=_default_access_token)
- nbr_presentation = fields.Integer('Number of Presentations', compute='_compute_slides_statistics', store=True)
- nbr_document = fields.Integer('Number of Documents', compute='_compute_slides_statistics', store=True)
- nbr_video = fields.Integer('Number of Videos', compute='_compute_slides_statistics', store=True)
- nbr_infographic = fields.Integer('Number of Infographics', compute='_compute_slides_statistics', store=True)
- nbr_webpage = fields.Integer("Number of Webpages", compute='_compute_slides_statistics', store=True)
+ nbr_presentation = fields.Integer('Presentations', compute='_compute_slides_statistics', store=True)
+ nbr_document = fields.Integer('Documents', compute='_compute_slides_statistics', store=True)
+ nbr_video = fields.Integer('Videos', compute='_compute_slides_statistics', store=True)
+ nbr_infographic = fields.Integer('Infographics', compute='_compute_slides_statistics', store=True)
+ nbr_webpage = fields.Integer("Webpages", compute='_compute_slides_statistics', store=True)
nbr_quiz = fields.Integer("Number of Quizs", compute='_compute_slides_statistics', store=True)
- total_slides = fields.Integer('# Slides', compute='_compute_slides_statistics', store=True)
- total_views = fields.Integer('# Views', compute='_compute_slides_statistics', store=True)
- total_votes = fields.Integer('# Votes', compute='_compute_slides_statistics', store=True)
- total_time = fields.Float('# Hours', compute='_compute_slides_statistics', digits=(10, 4), store=True)
+ total_slides = fields.Integer('Content', compute='_compute_slides_statistics', store=True)
+ total_views = fields.Integer('Visits', compute='_compute_slides_statistics', store=True)
+ total_votes = fields.Integer('Votes', compute='_compute_slides_statistics', store=True)
+ total_time = fields.Float('Duration', compute='_compute_slides_statistics', digits=(10, 2), store=True)
rating_avg_stars = fields.Float("Rating Average (Stars)", compute='_compute_rating_stats', digits=(16, 1))
# configuration
allow_comment = fields.Boolean(
@@ -131,7 +131,7 @@ class Channel(models.Model):
" * like content and post comments on documentation course;\n"
" * post comment and review on training course;")
publish_template_id = fields.Many2one(
- 'mail.template', string='Published Template',
+ 'mail.template', string='New Content Email',
help="Email template to send slide publication through email",
default=lambda self: self.env['ir.model.data'].xmlid_to_res_id('website_slides.slide_template_published'))
share_template_id = fields.Many2one(
@@ -169,9 +169,9 @@ class Channel(models.Model):
karma_gen_channel_rank = fields.Integer(string='Course ranked', default=5)
karma_gen_channel_finish = fields.Integer(string='Course finished', default=10)
# Karma based actions
- karma_review = fields.Integer('Add a review', default=10, help="Karma needed to add a review on the course")
- karma_slide_comment = fields.Integer('Add a comment', default=3, help="Karma needed to add a comment on a slide of this course")
- karma_slide_vote = fields.Integer('Vote on slide', default=3, help="Karma needed to like/dislike a slide of this course.")
+ karma_review = fields.Integer('Add Review', default=10, help="Karma needed to add a review on the course")
+ karma_slide_comment = fields.Integer('Add Comment', default=3, help="Karma needed to add a comment on a slide of this course")
+ karma_slide_vote = fields.Integer('Vote', default=3, help="Karma needed to like/dislike a slide of this course.")
can_review = fields.Boolean('Can Review', compute='_compute_action_rights')
can_comment = fields.Boolean('Can Comment', compute='_compute_action_rights')
can_vote = fields.Boolean('Can Vote', compute='_compute_action_rights')
diff --git a/addons/website_slides/models/slide_slide.py b/addons/website_slides/models/slide_slide.py
index e9ebe039886..4292665d870 100644
--- a/addons/website_slides/models/slide_slide.py
+++ b/addons/website_slides/models/slide_slide.py
@@ -105,7 +105,7 @@ class Slide(models.Model):
channel_id = fields.Many2one('slide.channel', string="Channel", required=True)
tag_ids = fields.Many2many('slide.tag', 'rel_slide_tag', 'slide_id', 'tag_id', string='Tags')
is_preview = fields.Boolean('Is Preview', default=False, help="The course is accessible by anyone : the users don't need to join the channel to access the content of the course.")
- completion_time = fields.Float('# Hours', default=1, digits=(10, 4))
+ completion_time = fields.Float('Duration', default=1, digits=(10, 2))
# Categories
is_category = fields.Boolean('Is a category', default=False)
category_id = fields.Many2one('slide.slide', string="Category", compute="_compute_category_id", store=True)
@@ -152,7 +152,7 @@ class Slide(models.Model):
embedcount_ids = fields.One2many('slide.embed', 'slide_id', string="Embed Count")
slide_views = fields.Integer('# of Website Views', store=True, compute="_compute_slide_views")
public_views = fields.Integer('# of Public Views')
- total_views = fields.Integer("Total # Views", default="0", compute='_compute_total', store=True)
+ total_views = fields.Integer("Views", default="0", compute='_compute_total', store=True)
# comments
comments_count = fields.Integer('Number of comments', compute="_compute_comments_count")
# channel
diff --git a/addons/website_slides/static/src/js/rating_field_backend.js b/addons/website_slides/static/src/js/rating_field_backend.js
new file mode 100644
index 00000000000..2c6d8ca38dd
--- /dev/null
+++ b/addons/website_slides/static/src/js/rating_field_backend.js
@@ -0,0 +1,43 @@
+odoo.define('website_slides.ratingField', function (require) {
+"use strict";
+
+var basicFields = require('web.basic_fields');
+var fieldRegistry = require('web.field_registry');
+
+var core = require('web.core');
+
+var QWeb = core.qweb;
+
+var FieldFloatRating = basicFields.FieldFloat.extend({
+ xmlDependencies: !basicFields.FieldFloat.prototype.xmlDependencies ?
+ ['/website_rating/static/src/xml/website_mail.xml'] : basicFields.FieldFloat.prototype.xmlDependencies.concat(
+ ['/website_rating/static/src/xml/website_mail.xml']
+ ),
+ description: "",
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ * @private
+ */
+ _render: function () {
+ var self = this;
+
+ return Promise.resolve(this._super()).then(function () {
+ self.$el.html(QWeb.render('website_rating.rating_stars_static', {
+ 'val': self.value / 2,
+ 'inline_mode': true
+ }));
+ });
+ },
+});
+
+fieldRegistry.add('field_float_rating', FieldFloatRating);
+
+return {
+ FieldFloatRating: FieldFloatRating,
+};
+
+});
diff --git a/addons/website_slides/static/src/js/slide_category_one2many.js b/addons/website_slides/static/src/js/slide_category_one2many.js
new file mode 100644
index 00000000000..ed270c840dc
--- /dev/null
+++ b/addons/website_slides/static/src/js/slide_category_one2many.js
@@ -0,0 +1,180 @@
+odoo.define('survey.slide_category_one2many', function (require){
+"use strict";
+
+var Context = require('web.Context');
+var FieldOne2Many = require('web.relational_fields').FieldOne2Many;
+var FieldRegistry = require('web.field_registry');
+var ListRenderer = require('web.ListRenderer');
+
+var SectionListRenderer = ListRenderer.extend({
+ init: function (parent, state, params) {
+ this.sectionFieldName = "is_category";
+ this._super.apply(this, arguments);
+ },
+ _checkIfRecordIsSection: function (id){
+ var record = this._findRecordById(id);
+ return record && record.data[this.sectionFieldName];
+ },
+ _findRecordById: function (id){
+ return _.find(this.state.data, function (record){
+ return record.id === id;
+ });
+ },
+ /**
+ * Allows to hide specific field in case the record is a section
+ * and, in this case, makes the 'title' field take the space of all the other
+ * fields
+ * @private
+ * @override
+ * @param {*} record
+ * @param {*} node
+ * @param {*} index
+ * @param {*} options
+ */
+ _renderBodyCell: function (record, node, index, options){
+ var $cell = this._super.apply(this, arguments);
+
+ var isSection = record.data[this.sectionFieldName];
+
+ if (isSection){
+ if (node.attrs.widget === "handle"){
+ return $cell;
+ } else if (node.attrs.name === "name"){
+ var nbrColumns = this._getNumberOfCols();
+ if (this.handleField){
+ nbrColumns--;
+ }
+ if (this.addTrashIcon){
+ nbrColumns--;
+ }
+ $cell.attr('colspan', nbrColumns);
+ } else {
+ $cell.removeClass('o_invisible_modifier');
+ return $cell.addClass('o_hidden');
+ }
+ }
+ return $cell;
+ },
+ /**
+ * Adds specific classes to rows that are sections
+ * to apply custom css on them
+ * @private
+ * @override
+ * @param {*} record
+ * @param {*} index
+ */
+ _renderRow: function (record, index){
+ var $row = this._super.apply(this, arguments);
+ if (record.data[this.sectionFieldName]) {
+ $row.addClass("o_is_section");
+ }
+ return $row;
+ },
+ /**
+ * Adding this class after the view is rendered allows
+ * us to limit the custom css scope to this particular case
+ * and no other
+ * @private
+ * @override
+ */
+ _renderView: function (){
+ var def = this._super.apply(this, arguments);
+ var self = this;
+ return def.then(function () {
+ self.$('table.o_list_table').addClass('o_section_list_view');
+ });
+ },
+ // Handlers
+ /**
+ * Overriden to allow different behaviours depending on
+ * the row the user clicked on.
+ * If the row is a section: edit inline
+ * else use a normal modal
+ * @private
+ * @override
+ * @param {*} ev
+ */
+ _onRowClicked: function (ev){
+ var parent = this.getParent();
+ var recordId = $(ev.currentTarget).data('id');
+ var is_section = this._checkIfRecordIsSection(recordId);
+ if (is_section && parent.mode === "edit"){
+ this.editable = "bottom";
+ } else {
+ this.editable = null;
+ }
+ this._super.apply(this, arguments);
+ },
+ /**
+ * Overriden to allow different behaviours depending on
+ * the cell the user clicked on.
+ * If the cell is part of a section: edit inline
+ * else use a normal edit modal
+ * @private
+ * @override
+ * @param {*} ev
+ */
+ _onCellClick: function (ev){
+ var parent = this.getParent();
+ var recordId = $(ev.currentTarget.parentElement).data('id');
+ var is_section = this._checkIfRecordIsSection(recordId);
+ if (is_section && parent.mode === "edit"){
+ this.editable = "bottom";
+ } else {
+ this.editable = null;
+ this.unselectRow();
+ }
+ this._super.apply(this, arguments);
+ },
+ /**
+ * In this case, navigating in the list caused issues.
+ * For example, editing a section then pressing enter would trigger
+ * the inline edition of the next element in the list. Which is not desired
+ * if the next element ends up being a question and not a section
+ * @override
+ * @param {*} ev
+ */
+ _onNavigationMove: function (ev){
+ this.unselectRow();
+ },
+});
+
+var SectionFieldOne2Many = FieldOne2Many.extend({
+ init: function (parent, name, record, options){
+ this._super.apply(this, arguments);
+ this.sectionFieldName = "is_category";
+ this.rendered = false;
+ },
+ /**
+ * Overriden to use our custom renderer
+ * @private
+ * @override
+ */
+ _getRenderer: function (){
+ if (this.view.arch.tag === 'tree'){
+ return SectionListRenderer;
+ }
+ return this._super.apply(this, arguments);
+ },
+ /**
+ * Overriden to allow different behaviours depending on
+ * the object we want to add. Adding a section would be done inline
+ * while adding a question would render a modal.
+ * @private
+ * @override
+ * @param {*} ev
+ */
+ _onAddRecord: function (ev) {
+ var context_str = ev.data.context && ev.data.context[0];
+ var context = new Context(context_str).eval();
+ if (context['default_' + this.sectionFieldName]){
+ this.editable = "bottom";
+ } else {
+ this.editable = null;
+ }
+ this._super.apply(this, arguments);
+ },
+});
+
+FieldRegistry.add('slide_category_one2many', SectionFieldOne2Many);
+});
\ No newline at end of file
diff --git a/addons/website_slides/static/src/scss/category_backend.scss b/addons/website_slides/static/src/scss/category_backend.scss
new file mode 100644
index 00000000000..99b017738ca
--- /dev/null
+++ b/addons/website_slides/static/src/scss/category_backend.scss
@@ -0,0 +1,6 @@
+table.o_section_list_view tr.o_data_row.o_is_section {
+ font-weight: bold;
+ background-color: #DDD;
+ border-top: 1px solid #BBB;
+ border-bottom: 1px solid #BBB;
+}
\ No newline at end of file
diff --git a/addons/website_slides/views/assets.xml b/addons/website_slides/views/assets.xml
index 62129e58e6b..1ae8232624b 100644
--- a/addons/website_slides/views/assets.xml
+++ b/addons/website_slides/views/assets.xml
@@ -5,6 +5,9 @@
+
+
+
diff --git a/addons/website_slides/views/slide_channel_views.xml b/addons/website_slides/views/slide_channel_views.xml
index 8aac4c74526..50261342db7 100644
--- a/addons/website_slides/views/slide_channel_views.xml
+++ b/addons/website_slides/views/slide_channel_views.xml
@@ -14,12 +14,30 @@
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
-
-
-
+
+
+
-
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
+
-
+
+
+
-
-
-
+
+
+
+
+
+ Hours
+
diff --git a/addons/website_slides/views/slide_slide_views.xml b/addons/website_slides/views/slide_slide_views.xml
index 00a52993060..1ca203f77ad 100644
--- a/addons/website_slides/views/slide_slide_views.xml
+++ b/addons/website_slides/views/slide_slide_views.xml
@@ -51,8 +51,8 @@
attrs="{'readonly': [('slide_type', 'in', ('document', 'presentation', 'video'))], 'invisible': [('is_category', '=', True)]}"
/>
-
-
+
+
@@ -90,7 +90,7 @@
-
+
diff --git a/addons/website_slides_forum/models/slide_channel.py b/addons/website_slides_forum/models/slide_channel.py
index 9b8018a1af5..fd8a23d142a 100644
--- a/addons/website_slides_forum/models/slide_channel.py
+++ b/addons/website_slides_forum/models/slide_channel.py
@@ -8,7 +8,19 @@ class Channel(models.Model):
_inherit = 'slide.channel'
forum_id = fields.Many2one('forum.forum', 'Course Forum')
+ forum_total_posts = fields.Integer('Number of active forum posts', related="forum_id.total_posts")
_sql_constraints = [
('forum_uniq', 'unique (forum_id)', "Only one forum per slide channel!"),
]
+
+ def action_redirect_to_forum(self):
+ self.ensure_one()
+ action = self.env.ref('website_forum.action_forum_post').read()[0]
+ action['view_mode'] = 'tree'
+ action['context'] = {
+ 'create': False
+ }
+ action['domain'] = [('forum_id', '=', self.forum_id.id)]
+
+ return action
diff --git a/addons/website_slides_forum/views/slide_channel_views.xml b/addons/website_slides_forum/views/slide_channel_views.xml
index 0dab8d637f9..29a56ef13c2 100644
--- a/addons/website_slides_forum/views/slide_channel_views.xml
+++ b/addons/website_slides_forum/views/slide_channel_views.xml
@@ -5,8 +5,17 @@
slide.channel
+
+
+
-
+
diff --git a/addons/website_slides_survey/models/slide_channel.py b/addons/website_slides_survey/models/slide_channel.py
index 2e09ea05cd8..1323b98d3b7 100644
--- a/addons/website_slides_survey/models/slide_channel.py
+++ b/addons/website_slides_survey/models/slide_channel.py
@@ -8,5 +8,3 @@ class Channel(models.Model):
_inherit = 'slide.channel'
nbr_certification = fields.Integer("Number of Certifications", compute='_compute_slides_statistics', store=True)
-
-
diff --git a/addons/website_slides_survey/models/slide_slide.py b/addons/website_slides_survey/models/slide_slide.py
index 8181812e28e..11092336989 100644
--- a/addons/website_slides_survey/models/slide_slide.py
+++ b/addons/website_slides_survey/models/slide_slide.py
@@ -45,6 +45,18 @@ class Slide(models.Model):
('check_certification_preview', "CHECK(slide_type != 'certification' OR is_preview = False)", "A slide of type certification cannot be previewed."),
]
+ @api.onchange('survey_id')
+ def _on_change_survey_id(self):
+ if self.survey_id:
+ self.slide_type = 'certification'
+
+ @api.model
+ def create(self, values):
+ rec = super(Slide, self).create(values)
+ if rec.survey_id:
+ rec.slide_type = 'certification'
+ return rec
+
def _generate_certification_url(self):
""" get a map of certification url for certification slide from `self`. The url will come from the survey user input:
1/ existing and not done user_input for member of the course
diff --git a/addons/website_slides_survey/views/slide_channel_views.xml b/addons/website_slides_survey/views/slide_channel_views.xml
index e3e094a41bd..417e9006ba0 100644
--- a/addons/website_slides_survey/views/slide_channel_views.xml
+++ b/addons/website_slides_survey/views/slide_channel_views.xml
@@ -5,8 +5,18 @@
slide.channel
-
-
+
+ Finished
+ Certified
+
+
+
+
+
+
+
+
+