From 3393dd0030207ed420a808cc7c23550b1e1ea376 Mon Sep 17 00:00:00 2001
From: jem-odoo <jem@openerp.com>
Date: Wed, 20 Feb 2019 12:15:39 +0000
Subject: [PATCH] [REF] website_slides: redesign quiz widget

This commit makes the quiz widget a real odoo widget that triggers up some
events. The widget is now only responsible of

 * fetching quiz data (if not given)
 * decorating the anwsers according to the result
 * displaying message (error or modal)

To do so, some code move/rewrite was needed

 * extract and factorize some template
 * add 'sudo' on technical model to avoid access rights error
 * factorize some python method from controller to model
 * remove some CSS classes to lighten the code
 * fixing access model error
 * make quiz widget handle the display of error message
 * give browse record for template rendering, rather than slug
 * cleaning some spaghetti code in Fullscreen widget
 * ...

Commit linked to task ID 1941250 and PR #31584.

Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
---
 addons/website_slides/controllers/main.py     | 160 +++---
 addons/website_slides/models/slide_slide.py   |  45 +-
 .../src/js/slides_course_fullscreen_player.js | 100 ++--
 .../static/src/js/slides_course_quiz.js       | 471 ++++++++++++------
 .../src/scss/slides_slide_fullscreen.scss     |  41 +-
 .../static/src/scss/website_slides.scss       |  29 ++
 .../static/src/scss/website_slides_quiz.scss  | 237 ---------
 .../static/src/xml/slide_quiz.xml             |  87 ++++
 .../src/xml/website_slides_fullscreen.xml     |  72 +--
 addons/website_slides/views/assets.xml        |   1 -
 .../views/website_slides_templates_lesson.xml |  96 ++--
 ...ite_slides_templates_lesson_fullscreen.xml |   3 +-
 .../models/slide_slide.py                     |   4 +-
 13 files changed, 694 insertions(+), 652 deletions(-)
 delete mode 100644 addons/website_slides/static/src/scss/website_slides_quiz.scss
 create mode 100644 addons/website_slides/static/src/xml/slide_quiz.xml

diff --git a/addons/website_slides/controllers/main.py b/addons/website_slides/controllers/main.py
index 5b915c582f37..bdfb0d49f885 100644
--- a/addons/website_slides/controllers/main.py
+++ b/addons/website_slides/controllers/main.py
@@ -51,7 +51,7 @@ class WebsiteSlides(WebsiteProfile):
             return {'error': 'slide_access'}
         return {'slide': slide}
 
-    def _set_viewed_slide(self, slide):
+    def _set_viewed_slide(self, slide, quiz_attempts_inc=False):
         if request.env.user._is_public() or not slide.website_published or not slide.channel_id.is_member:
             viewed_slides = request.session.setdefault('viewed_slides', list())
             if slide.id not in viewed_slides:
@@ -59,12 +59,12 @@ class WebsiteSlides(WebsiteProfile):
                 viewed_slides.append(slide.id)
                 request.session['viewed_slides'] = viewed_slides
         else:
-            slide.action_set_viewed()
+            slide.action_set_viewed(quiz_attempts_inc=quiz_attempts_inc)
         return True
 
-    def _set_completed_slide(self, slide):
+    def _set_completed_slide(self, slide, quiz_attempts_inc=False):
         if slide.website_published and slide.channel_id.is_member:
-            slide.action_set_completed()
+            slide.action_set_completed(quiz_attempts_inc=quiz_attempts_inc)
         return True
 
     def _get_slide_detail(self, slide):
@@ -104,11 +104,29 @@ class WebsiteSlides(WebsiteProfile):
                 'message_post_pid': request.env.user.partner_id.id,
             })
 
+        if slide.question_ids:
+            values.update(self._get_slide_quiz_info(slide))
+
         return values
 
-    def _get_quiz_points(self, slide, attempt_count):
-        possible_points = [slide.quiz_first_attempt_reward,slide.quiz_second_attempt_reward,slide.quiz_third_attempt_reward, slide.quiz_fourth_attempt_reward]
-        return possible_points[attempt_count] if attempt_count < len(possible_points) else possible_points[-1]
+    def _get_slide_quiz_info(self, slide, quiz_done=False):
+        gains = [slide.quiz_first_attempt_reward,
+                 slide.quiz_second_attempt_reward,
+                 slide.quiz_third_attempt_reward,
+                 slide.quiz_fourth_attempt_reward]
+        result = {
+            'quiz_karma_max': gains[0],  # what could be gained if succeed at first try
+            'quiz_karma_gain': gains[0],  # what would be gained at next test
+            'quiz_karma_won': 0,  # what has been gained
+            'quiz_attempts_count': 0,  # number of attempts
+        }
+        if slide.user_membership_id:
+            if slide.user_membership_id.quiz_attempts_count:
+                result['quiz_karma_gain'] = gains[slide.user_membership_id.quiz_attempts_count] if slide.user_membership_id.quiz_attempts_count <= len(gains) else gains[-1]
+                result['quiz_attempts_count'] = slide.user_membership_id.quiz_attempts_count
+            if quiz_done or slide.user_membership_id.completed:
+                result['quiz_karma_won'] = gains[slide.user_membership_id.quiz_attempts_count-1] if slide.user_membership_id.quiz_attempts_count < len(gains) else gains[-1]
+        return result
 
     # CHANNEL UTILITIES
     # --------------------------------------------------
@@ -513,6 +531,7 @@ class WebsiteSlides(WebsiteProfile):
         self._set_viewed_slide(slide)
 
         values = self._get_slide_detail(slide)
+        values['channel_progress'] = self._get_channel_progress(slide.channel_id)
 
         if 'fullscreen' in kwargs:
             return request.render("website_slides.slide_fullscreen", values)
@@ -569,9 +588,10 @@ class WebsiteSlides(WebsiteProfile):
         }
 
     @http.route('/slides/slide/<model("slide.slide"):slide>/set_completed', website=True, type="http", auth="user")
-    def slide_set_completed_and_redirect(self, slide, next_slide=None):
+    def slide_set_completed_and_redirect(self, slide, next_slide_id=None):
         self._set_completed_slide(slide)
-        return werkzeug.utils.redirect("/slides/slide/%s" % (next_slide if next_slide else slide.id))
+        next_slide = request.env['slide.slide'].browse(next_slide_id) if next_slide_id else None
+        return werkzeug.utils.redirect("/slides/slide/%s" % (slug(next_slide) if next_slide else slug(slide)))
 
     @http.route('/slides/slide/set_completed', website=True, type="json", auth="public")
     def slide_set_completed(self, slide_id):
@@ -624,68 +644,70 @@ class WebsiteSlides(WebsiteProfile):
     # QUIZZ SECTION
     # --------------------------------------------------
 
-    @http.route('/slide/quiz/get', type="json", auth="public", website=True)
-    def get_quiz(self, **kw):
-        if 'slide_id' in kw:
-            slide = request.env['slide.slide'].browse(kw['slide_id'])
-            slide_partner = request.env['slide.slide.partner'].search([('slide_id', '=', slide.id), ('partner_id', '=', request.env.user.partner_id.id)])
-            possible_points = [slide.quiz_first_attempt_reward,slide.quiz_second_attempt_reward,slide.quiz_third_attempt_reward, slide.quiz_fourth_attempt_reward]
-            points = 0
-            if slide_partner.quiz_attempts_count < len(possible_points):
-                points = possible_points[slide_partner.quiz_attempts_count]
-            else:
-                points = possible_points[len(possible_points)-1]
-            res = {
-                'questions':[
-                    {'title': question.question,
-                        'id': question.id,
-                        'answers': [{'text': answer.text_value, 'correct':answer.is_correct,'id': answer.id} for answer in question.answer_ids]
-                        } for question in slide.question_ids
-                    ],
-                'nb_attempts': slide_partner.quiz_attempts_count if slide_partner else 0,
-                'possible_rewards': possible_points,
-                'reward': points
-            }
-            return res
-
-    @http.route('/slide/quiz/submit', type="json", auth="user", website=True)
-    def submit_quiz(self, slide_id, answer_ids,**kw):
-        slide = request.env['slide.slide'].browse(slide_id)
-        good_answers = request.env['slide.answer'].search([('id', 'in', answer_ids), ('is_correct', '=', True)])
-        bad_answers = request.env['slide.answer'].browse(answer_ids) - good_answers
-        slide_partner = request.env['slide.slide.partner'].search([('slide_id', '=', slide_id), ('partner_id', '=', request.env.user.partner_id.id)])
-        points = 0
-        if not slide_partner:
-            slide.action_set_viewed()
-        if not slide_partner.completed:
-            points = self._get_quiz_points(slide, slide_partner.quiz_attempts_count)
-            slide_partner.sudo().write({
-                'quiz_attempts_count': slide_partner.quiz_attempts_count if not bad_answers else slide_partner.quiz_attempts_count + 1,
-                'points_won': points if not bad_answers else 0,
-                'completed': not bad_answers
-            })
-            user = {}
-            if not bad_answers:
-                request.env.user.sudo().add_karma(points)
-                lower_bound = request.env.user.rank_id.karma_min
-                upper_bound = request.env.user.next_rank_id.karma_min
-                user= {
-                        'lower_bound': lower_bound,
-                        'upper_bound': upper_bound,
-                        'karma': request.env.user.karma,
-                        'progress_bar_percentage': 100 * ((request.env.user.karma - lower_bound) / (upper_bound - lower_bound))
-                    }
-            return {
-                'goodAnswers': [good_answer.id for good_answer in good_answers],
-                'badAnswers': [bad_answer.id for bad_answer in bad_answers],
-                'passed': not bad_answers,
-                'points': points if not bad_answers else 0,
-                'attempts_count': slide_partner.quiz_attempts_count if slide_partner else 0,
-                'channel_completion': slide.channel_id.completion,
-                'user': user
+    @http.route('/slides/slide/quiz/get', type="json", auth="public", website=True)
+    def slide_quiz_get(self, slide_id):
+        fetch_res = self._fetch_slide(slide_id)
+        if fetch_res.get('error'):
+            return fetch_res
+        slide = fetch_res['slide']
+        quiz_info = self._get_slide_quiz_info(slide)
+        return {
+            'questions': [{
+                'id': question.id,
+                'question': question.question,
+                'answers': [{
+                    'id': answer.id,
+                    'text_value': answer.text_value,
+                    'is_correct': answer.is_correct,
+                } for answer in question.answer_ids],
+            } for question in slide.question_ids],
+            'quizAttemptsCount': quiz_info['quiz_attempts_count'],
+            'quizKarmaGain': quiz_info['quiz_karma_gain'],
+            'quizKarmaWon': quiz_info['quiz_karma_won'],
+        }
+
+    @http.route('/slides/slide/quiz/submit', type="json", auth="user", website=True)
+    def slide_quiz_submit(self, slide_id, answer_ids):
+        fetch_res = self._fetch_slide(slide_id)
+        if fetch_res.get('error'):
+            return fetch_res
+        slide = fetch_res['slide']
+
+        if slide.user_membership_id.completed:
+            return {'error': 'slide_quiz_done'}
+
+        all_questions = request.env['slide.question'].sudo().search([('slide_id', '=', slide.id)])
+
+        user_answers = request.env['slide.answer'].sudo().search([('id', 'in', answer_ids)])
+        if user_answers.mapped('question_id') != all_questions:
+            return {'error': 'slide_quiz_incomplete'}
+
+        user_bad_answers = user_answers.filtered(lambda answer: not answer.is_correct)
+        user_good_answers = user_answers - user_bad_answers
+
+        self._set_viewed_slide(slide, quiz_attempts_inc=True)
+        quiz_info = self._get_slide_quiz_info(slide, quiz_done=True)
+
+        rank_progress = {}
+        if not user_bad_answers:
+            slide._action_set_quiz_done()
+            lower_bound = request.env.user.rank_id.karma_min
+            upper_bound = request.env.user.next_rank_id.karma_min
+            rank_progress = {
+                'lowerBound': lower_bound,
+                'upperBound': upper_bound,
+                'currentKarma': request.env.user.karma,
+                'motivational': request.env.user.next_rank_id.description_motivational,
+                'progress': 100 * ((request.env.user.karma - lower_bound) / (upper_bound - lower_bound))
             }
         return {
-            'error': "You already passed this quiz"
+            'goodAnswers': user_good_answers.ids,
+            'badAnswers': user_bad_answers.ids,
+            'completed': not user_bad_answers,
+            'quizKarmaWon': quiz_info['quiz_karma_won'],
+            'quizKarmaGain': quiz_info['quiz_karma_gain'],
+            'quizAttemptsCount': quiz_info['quiz_attempts_count'],
+            'rankProgress': rank_progress
         }
 
     # --------------------------------------------------
diff --git a/addons/website_slides/models/slide_slide.py b/addons/website_slides/models/slide_slide.py
index 6f2bb6feac5a..5990a6dc30ef 100644
--- a/addons/website_slides/models/slide_slide.py
+++ b/addons/website_slides/models/slide_slide.py
@@ -427,52 +427,83 @@ class Slide(models.Model):
             })
             self.env.user.add_karma(new_slide.channel_id.karma_gen_slide_vote)
 
-    def action_set_viewed(self):
+    def action_set_viewed(self, quiz_attempts_inc=False):
         if not all(slide.channel_id.is_member for slide in self):
             raise UserError(_('You cannot mark a slide as viewed if you are not among its members.'))
 
-        return bool(self._action_set_viewed(self.env.user.partner_id))
+        return bool(self._action_set_viewed(self.env.user.partner_id, quiz_attempts_inc=quiz_attempts_inc))
 
-    def _action_set_viewed(self, target_partner):
+    def _action_set_viewed(self, target_partner, quiz_attempts_inc=False):
         self_sudo = self.sudo()
         SlidePartnerSudo = self.env['slide.slide.partner'].sudo()
         existing_sudo = SlidePartnerSudo.search([
             ('slide_id', 'in', self.ids),
             ('partner_id', '=', target_partner.id)
         ])
+        if quiz_attempts_inc:
+            for exsting_slide in existing_sudo:
+                exsting_slide.write({
+                    'quiz_attempts_count': exsting_slide.quiz_attempts_count + 1
+                })
 
         new_slides = self_sudo - existing_sudo.mapped('slide_id')
         return SlidePartnerSudo.create([{
             'slide_id': new_slide.id,
             'channel_id': new_slide.channel_id.id,
             'partner_id': target_partner.id,
+            'quiz_attempts_count': 1 if quiz_attempts_inc else 0,
             'vote': 0} for new_slide in new_slides])
 
-    def action_set_completed(self):
+    def action_set_completed(self, quiz_attempts_inc=False):
         if not all(slide.channel_id.is_member for slide in self):
             raise UserError(_('You cannot mark a slide as completed if you are not among its members.'))
 
-        return self._action_set_completed(self.env.user.partner_id)
+        return self._action_set_completed(self.env.user.partner_id, quiz_attempts_inc=quiz_attempts_inc)
 
-    def _action_set_completed(self, target_partner):
+    def _action_set_completed(self, target_partner, quiz_attempts_inc=False):
         self_sudo = self.sudo()
         SlidePartnerSudo = self.env['slide.slide.partner'].sudo()
         existing_sudo = SlidePartnerSudo.search([
             ('slide_id', 'in', self.ids),
             ('partner_id', '=', target_partner.id)
         ])
-        existing_sudo.write({'completed': True})
+        if quiz_attempts_inc:
+            for existing_slide in existing_sudo:
+                existing_slide.write({
+                    'completed': True,
+                    'quiz_attempts_count': existing_slide.quiz_attempts_count + 1
+                })
+        else:
+            existing_sudo.write({'completed': True})
 
         new_slides = self_sudo - existing_sudo.mapped('slide_id')
         SlidePartnerSudo.create([{
             'slide_id': new_slide.id,
             'channel_id': new_slide.channel_id.id,
             'partner_id': target_partner.id,
+            'quiz_attempts_count': 1 if quiz_attempts_inc else 0,
             'vote': 0,
             'completed': True} for new_slide in new_slides])
 
         return True
 
+    def _action_set_quiz_done(self):
+        if not all(slide.channel_id.is_member for slide in self):
+            raise UserError(_('You cannot mark a slide quiz as completed if you are not among its members.'))
+
+        points = 0
+        for slide in self:
+            if not slide.user_membership_id or slide.user_membership_id.completed or not slide.user_membership_id.quiz_attempts_count:
+                continue
+
+            gains = [slide.quiz_first_attempt_reward,
+                     slide.quiz_second_attempt_reward,
+                     slide.quiz_third_attempt_reward,
+                     slide.quiz_fourth_attempt_reward]
+            points += gains[slide.user_membership_id.quiz_attempts_count-1] if slide.user_membership_id.quiz_attempts_count <= len(gains) else gains[-1]
+
+        return self.env.user.sudo().add_karma(points)
+
     # --------------------------------------------------
     # Parsing methods
     # --------------------------------------------------
diff --git a/addons/website_slides/static/src/js/slides_course_fullscreen_player.js b/addons/website_slides/static/src/js/slides_course_fullscreen_player.js
index df8e57bf4819..b7165e92d4de 100644
--- a/addons/website_slides/static/src/js/slides_course_fullscreen_player.js
+++ b/addons/website_slides/static/src/js/slides_course_fullscreen_player.js
@@ -17,6 +17,10 @@ odoo.define('website_slides.fullscreen', function (require) {
 
 
     var Fullscreen = Widget.extend({
+        custom_events: {
+            quiz_next_slide: '_goToNextSlide',
+            quiz_completed: '_onQuizCompleted'
+        },
         /**
         * @override
         * @param {Object} el
@@ -36,11 +40,11 @@ odoo.define('website_slides.fullscreen', function (require) {
             this.activetab = undefined;
             this.player = undefined;
             this.goToQuiz = false;
-            this.answeredQuestions = [];
             this.slideTitle = undefined;
             return this._super.apply(this,arguments);
         },
         start: function (){
+            var self = this;
             this.url = window.location.pathname;
             this.urlToSmallScreen = this.url.replace('/fullscreen','');
             this._getSlides();
@@ -58,46 +62,50 @@ odoo.define('website_slides.fullscreen', function (require) {
          */
         _renderPlayer: function (){
             var self = this;
-            var embed_url;
-            if (self.slide.slide_type !== 'webpage' || self.slide.htmlContent){
-                if ((self.slide.slide_type === "quiz" || self.slide.has_quiz) && !self.slide.quiz){
-                    self._fetchQuiz();
-                } else {
-                    embed_url = $(this.slide.embed_code).attr('src');
-                    if (self.slide.slide_type === "video"){
+
+            var def = $.Deferred();
+            if (this.slide.slide_type === 'webpage') {
+                this._fetchHtmlContent().then(function () {
+                    self._renderWebpage();
+                    def.resolve();
+                });
+            } else {
+                if (this.slide.slide_type !== 'quiz') {
+                    // no RPC, get slide data from existing DOM
+                    var embed_url = $(this.slide.embed_code).attr('src');
+                    if (this.slide.slide_type === "video"){
                         embed_url = "https://" + embed_url + "&rel=0&autoplay=1&enablejsapi=1&origin=" + window.location.origin;
                     }
                     $('.o_wslides_fs_player').html(QWeb.render('website.slides.fullscreen', {
                         slide: self.slide,
                         nextSlide: self.nextSlide,
                         questions: self.slide.quiz ? self.slide.quiz.questions: '',
-                        reward: self.slide.quiz ? self.slide.quiz.nb_attempts < 3 ? self.slide.quiz.possible_rewards[self.slide.quiz.nb_attempts] : self.slide.quiz.possible_rewards[3]: self.slide.maxPoints,
                         embed_url: embed_url,
                         question_count: self.slide.quiz ? self.slide.quiz.questions.length : '',
                         letters: self.slide.quiz ? self.letters : '',
                         showMiniQuiz: self.goToQuiz
                     }));
-                    if (self.slide.slide_type === "video"){
-                      self._renderYoutubeIframe();
-                    }
-                    if (self.slide.slide_type === 'webpage'){
-                        self._renderWebpage();
-                    }
-                    if ((self.slide.slide_type === "presentation" || self.slide.slide_type === "document" || self.slide.slide_type === "infographic" || self.slide.slide_type === "webpage") && !self.slide.quiz){
-                        self._setSlideStateAsDone();
-                    }
-                    if ((self.slide.quiz && self.slide.slide_type === "quiz") || self.goToQuiz){
-                        self._renderQuiz();
+
+                    if(this.slide.slide_type === "video"){
+                      this._renderYoutubeIframe();
                     }
+                    def.resolve();
                 }
-            } else {
-                self._fetchHtmlContent();
             }
-            self._renderTitle();
+
+            if (this.slide.slide_type === "quiz" || this.goToQuiz) {
+                this._renderQuiz().then(function() {
+                    def.resolve();
+                });
+            }
+
+            return def.then(function() {
+                self._renderTitle();
+            });
         },
         _renderYoutubeIframe: function (){
             var self = this;
-              /**
+            /**
              * Due to issues of synchronization between the youtube api script and the widget's instanciation.
              */
             try {
@@ -114,18 +122,15 @@ odoo.define('website_slides.fullscreen', function (require) {
             var self = this;
             $(self.slide.htmlContent).appendTo('.o_wslides_fs_webpage_content');
         },
+
         _renderQuiz: function (){
-            var self = this;
-            var Quiz = new QuizWidget(this, self.slide, self.nextSlide);
-            Quiz.appendTo('.o_wslides_fs_player');
-            $('.next-slide').click(function (){
-                self._goToNextSlide();
-            });
-            $('.back-to-video').click(function (){
-                self.goToQuiz = false;
-                self._renderPlayer();
+            var quizSlideData = _.extend(this.slide, {
+                completed: this.slide.done,
             });
+            var Quiz = new QuizWidget(this, quizSlideData);
+            return Quiz.appendTo('.o_wslides_fs_player');
         },
+
         _renderTitle: function (){
             var self = this;
             $('.o_wslides_fs_slide_title').empty().html(QWeb.render('website.course.fullscreen.title', {
@@ -168,7 +173,7 @@ odoo.define('website_slides.fullscreen', function (require) {
                         var totalTime = event.target.getDuration();
                         if (totalTime && currentTime > totalTime - 30){
                             clearInterval(self.tid);
-                            if (!self.slide.has_quiz && !self.slide.done){
+                            if (!self.slide.hasQuiz && !self.slide.done){
                                 self.slide.done = true;
                                 self._setSlideStateAsDone();
                             }
@@ -194,26 +199,11 @@ odoo.define('website_slides.fullscreen', function (require) {
                 this._getActiveSlide();
             }
         },
+
         /**
          * @private
-         * @param {object} slide
-         * Fetch the quiz for a particular slide
          */
-        _fetchQuiz: function (){
-            var self = this;
-            return self._rpc({
-                route:"/slide/quiz/get",
-                params: {
-                    'slide_id': self.slide.id
-                }
-            }).then(function (data){
-                if (data){
-                    self.slide.quiz = data;
-                    self._renderPlayer();
-                }
-            });
-        },
-        _fetchHtmlContent: function (){
+        _fetchHtmlContent: function(){
             var self = this;
             return self._rpc({
                 route: 'slides/slide/get_html_content',
@@ -223,7 +213,6 @@ odoo.define('website_slides.fullscreen', function (require) {
             }).then(function (data){
                 if (data.html_content) {
                     self.slide.htmlContent = data.html_content;
-                    self._renderPlayer();
                 }
             });
         },
@@ -269,7 +258,7 @@ odoo.define('website_slides.fullscreen', function (require) {
             var self = this;
             clearInterval(self.tid);
             self.player = undefined;
-            self.goToQuiz = self.slide.has_quiz && !self.goToQuiz && self.slide.slide_type !== 'quiz';
+            self.goToQuiz = self.slide.hasQuiz && !self.goToQuiz && self.slide.slide_type !== 'quiz';
             if (self.nextSlide && !self.goToQuiz){
                 self.slide = self.nextSlide;
                 self.index++;
@@ -369,7 +358,7 @@ odoo.define('website_slides.fullscreen', function (require) {
         },
         _onMiniQuizClick: function (ev){
             var self = this;
-            self.index = parseInt($(ev.currentTarget).attr('index'));
+            self.index = parseInt($(ev.currentTarget).attr('index')) || 0;
             self.slide = self.slides[self.index];
             self.goToQuiz = true;
             self._setPreviousAndNextSlides();
@@ -377,6 +366,9 @@ odoo.define('website_slides.fullscreen', function (require) {
             self._setActiveTab();
             self._updateUrl();
             history.pushState(null,'' ,self.url);
+        },
+        _onQuizCompleted: function (ev) {
+            this._setSlideStateAsDone();
         },
          /**
         * @private
diff --git a/addons/website_slides/static/src/js/slides_course_quiz.js b/addons/website_slides/static/src/js/slides_course_quiz.js
index dd921d5a17f9..319ca629bf2c 100644
--- a/addons/website_slides/static/src/js/slides_course_quiz.js
+++ b/addons/website_slides/static/src/js/slides_course_quiz.js
@@ -5,203 +5,346 @@ odoo.define('website_slides.quiz', function (require) {
     var Widget = require('web.Widget');
 
     var QWeb = core.qweb;
+    var _t = core._t;
 
-    var Quiz= Widget.extend({
-         /**
+    /**
+     * This widget is responsible of displaying quiz questions and propositions. Submitting the quiz will fetch the
+     * correction and decorate the answers according to the result. Error message or modal can be displayed.
+     *
+     * This widget can be attached to DOM rendered server-side by `website_slides.slide_type_quiz` or
+     * used client side (Fullscreen).
+     *
+     * Triggered events are :
+     * - quiz_next_slide: need to go to the next slide, when quiz is done. Event data contains the current slide id.
+     * - quiz_completed: when the quiz is passed and completed by the user. Event data contains completion
+     *      percentage and current slide id.
+     */
+    var Quiz = Widget.extend({
+        template: 'slide.slide.quiz',
+        xmlDependencies: ['/website_slides/static/src/xml/slide_quiz.xml'],
+        events: {
+            "click .o_wslides_quiz_answer": '_onAnswerClick',
+            "click .o_wslides_js_lesson_quiz_submit": '_onSubmitQuiz',
+            "click .o_wslides_quiz_btn": '_onClickNext',
+            "click .o_wslides_quiz_continue": '_onClickNext'
+        },
+
+        /**
         * @override
-        * @param {Object} el
-        * @param {Object} data holding all the slide elements needed for the quiz
-        * It will either come from the fullscreen widget or the sAnimation at the end of this file
+        * @param {Object} parent
+        * @param {Object} slide_data holding all the classic slide informations
+        * @param {Object} quiz_data : optional quiz data to display. If not given, will be fetched. (questions and answers).
         */
-        init: function (el, data, nextSlide){
-            this.slide = data;
-            this.nextSlide = nextSlide;
-            this.answeredQuestions = [];
-            this.fullscreen = el;
-            return this._super.apply(this,arguments);
-        },
-        start: function (){
-            var self = this;
-            self._bindQuizEvents();
-            /**
-             * If the quiz is rendered by the server instead of the fullscreen widget,
-             * questions and their answers will have to be created manually from attributes
-             */
-            if (self.slide.quiz.questions.length === 0){
-                this._setQuestions();
-            }
+        init: function (parent, slide_data, quiz_data) {
+            this.slide = _.defaults(slide_data, {
+                id: 0,
+                name: '',
+                hasNext: false,
+                completed: false,
+                readonly: false,
+            });
+            this.quiz = quiz_data || false;
+            this.readonly = slide_data.readonly || false;
             return this._super.apply(this, arguments);
         },
-        _renderSuccessModal: function (data){
-            var self =this;
-            $('.o_wslides_fs_quiz').append(QWeb.render('website.course.quiz.success', {
-                data: data,
-                nextSlide: self.nextSlide
-            }));
-            $('.submit-quiz').remove();
-            $('.next-slide').css('display', 'inline-block');
-            $('.next-slide').click(function (){
-                self.fullscreen._goToNextSlide();
-            });
-            $('.o_wslides_quiz_success_modal_close').click(function (){
-                $('.o_wslides_quiz_success_modal').remove();
-                $('.o_wslides_quiz_modal_background').remove();
-            });
-            $(".o_wslides_quiz_modal_background").click(function (ev){
-                $(ev.currentTarget).remove();
-                $('.o_wslides_quiz_success_modal').remove();
+
+        /**
+         * @override
+         */
+        willStart: function () {
+            var def = $.Deferred();
+            if (this.quiz) {
+                def.resolve();
+            } else {
+                def = this._fetchQuiz();
+            }
+            return $.when(this._super.apply(this, arguments), def);
+        },
+
+        /**
+         * @override
+         */
+        start: function() {
+            var self = this;
+            return this._super.apply(this, arguments).then(function ()  {
+                self._renderAnswers();
+                self._renderAnswersHighlighting();
+                self._renderValidationInfo();
             });
         },
+
         //--------------------------------------------------------------------------
         // Private
         //--------------------------------------------------------------------------
-        /**
-        * @private
-        * In case the quiz is rendered by the server and the data don't come from the fullscreen widget,
-        * questions and their answers will have to be set here by using attributes
-        */
-        _setQuestions: function (){
-            var self = this;
-            $('.o_wslides_quiz_question').each(function (){
-                self.slide.quiz.questions.push({
-                    id: parseInt($(this).attr('id')),
-                    title: $(this).attr('title'),
-                    answers: []
-                });
-            });
-            for (var i = 0; i < self.slide.quiz.questions.length; i++){
-                self._setAnswersForQuestion(self.slide.quiz.questions[i]);
+
+        _alertHide: function () {
+            this.$('.o_wslides_js_lesson_quiz_error').addClass('d-none');
+        },
+
+        _alertShow: function (alert_code) {
+            var message = _t('There was an error validating this quiz.');
+            if (! alert_code || alert_code === 'slide_quiz_incomplete') {
+                message = _t('All questions must be answered !');
+            }
+            else if (alert_code === 'slide_quiz_done') {
+                message = _t('This quiz is already done. Retaking it is not possible.');
             }
+            this.$('.o_wslides_js_lesson_quiz_error span').html(message);
+            this.$('.o_wslides_js_lesson_quiz_error').removeClass('d-none');
         },
-        _setAnswersForQuestion: function (question){
-            $('.o_wslides_quiz_answer[question_id='+question.id+']').each(function (){
-                question.answers.push({
-                    id: parseInt($(this).attr('id')),
-                    text: $(this).attr('text_value'),
-                    is_correct: $(this).attr('is_correct')
-                });
+
+        /*
+         * @private
+         * Fetch the quiz for a particular slide
+         */
+        _fetchQuiz: function () {
+            var self = this;
+            return self._rpc({
+                route:'/slides/slide/quiz/get',
+                params: {
+                    'slide_id': self.slide.id,
+                }
+            }).then(function (quiz_data) {
+                self.quiz = quiz_data;
             });
         },
-        _updateProgressbar: function (){
+
+        /**
+         * @private
+         * Decorate the answers according to state
+         */
+        _renderAnswers: function () {
             var self = this;
-            var completion = self.channelCompletion <= 100 ? self.channelCompletion : 100;
-            $('.o_wslides_fs_sidebar_progress_gauge').css('width', completion + "%" );
-            $('.o_wslides_progress_percentage').text(completion);
+            this.$('input[type=radio]').each(function () {
+                console.log($(this));
+                $(this).prop('disabled', self.slide.readonly);
+            });
         },
-        _bindQuizEvents: function (){
+
+        /**
+         * @private
+         * Decorate the answer inputs according to the correction
+         */
+        _renderAnswersHighlighting: function () {
             var self = this;
-            if (!self.slide.done){
-                $('.o_wslides_quiz_answer').each(function (){
-                    $(this).click(self._onAnswerClick.bind(self));
-                });
+            this.$('li.o_wslides_quiz_answer').each(function () {
+                var $answer = $(this);
+                var answerId = $answer.data('answerId');
+                if (_.contains(self.quiz.goodAnswers, answerId)) {
+                    $answer.removeClass('border-danger').addClass('border border-success');
+                    $answer.find('i.fa').addClass('d-none');
+                    $answer.find('i.fa-check-circle').removeClass('d-none');
+                }
+                else if (_.contains(self.quiz.badAnswers, answerId)) {
+                    $answer.removeClass('border-success').addClass('border border-danger');
+                    $answer.find('i.fa').addClass('d-none');
+                    $answer.find('i.fa-times-circle').removeClass('d-none');
+                    $answer.find('label input').prop('checked', false);
+                }
+                else {
+                    $answer.removeClass('border border-danger border-success');
+                    $answer.find('i.fa').addClass('d-none');
+                    $answer.find('i.fa-circle').removeClass('d-none');
+                }
+            });
+        },
+
+        /**
+         * @private
+         * When the quiz is done and succeed, a congratulation modal appears.
+         */
+        _renderSuccessModal: function () {
+            var $modal = this.$('#slides_quiz_modal');
+            if (!$modal.length) {
+                this.$el.append(QWeb.render('slide.slide.quiz.finish', {'widget': this}));
+                $modal = this.$('#slides_quiz_modal');
             }
-            $('.submit-quiz').click(self._onSubmitQuiz.bind(self));
+            $modal.modal({
+                'show': true,
+            });
+            $modal.on('hidden.bs.modal', function () {
+                $modal.remove();
+            });
+        },
+
+        /*
+         * @private
+         * Update validation box (karma, buttons) according to widget state
+         */
+        _renderValidationInfo: function () {
+            var $validationElem = this.$('.o_wslides_js_lesson_quiz_validation');
+            $validationElem.html(
+                QWeb.render('slide.slide.quiz.validation', {'widget': this})
+            );
+        },
+
+        /**
+         * Set the slide as completed and done. Trigger up the completion.
+         *
+         * @param {Integer} completion
+         */
+        _setCompleted: function () {
+            this.trigger_up('quiz_completed', {
+                'slideId': this.slide.id,
+            });
         },
-        _highlightAnswers: function (answers){
+        /*
+         * Submit the given answer, and display the result
+         *
+         * @param Array checkedAnswerIds: list of checked answers
+         */
+        _submitQuiz: function (checkedAnswerIds) {
             var self = this;
-            self.answeredQuestions = [];
-            for (var i = 0; i < answers.goodAnswers.length; i++){
-                $('#answer'+ answers.goodAnswers[i] +'').addClass('o_wslides_quiz_good_answer');
-                $('#answer'+ answers.goodAnswers[i] +' .o_wslides_quiz_radio_box span').replaceWith($('<i class="fa fa-check-circle"></i>'));
-                var questionID =$('#answer'+ answers.goodAnswers[i] +' .o_wslides_quiz_radio_box input').attr('question_id');
-                self.answeredQuestions.push(questionID);
-                $('.o_wslides_quiz_answer[question_id='+questionID+']:not(.o_wslides_quiz_good_answer)').addClass('o_wslides_quiz_ignored_answer');
-                $('.o_wslides_quiz_answer[question_id='+questionID+']').each(function (){
-                    $(this).unbind('click');
-                });
-                $('input[question_id='+questionID+']').each(function (){
-                    $(this).prop('disabled',true);
-                });
-            }
-            for (i = 0; i < answers.badAnswers.length; i++){
-                $('#answer'+ answers.badAnswers[i]).removeClass('o_wslides_quiz_good_answer')
-                    .addClass('o_wslides_quiz_bad_answer')
-                    .unbind('click');
-                $('#answer'+ answers.badAnswers[i] +' .o_wslides_quiz_radio_box span').replaceWith($('<i class="fa fa-times "></i>'));
-                $('#answer'+ answers.badAnswers[i] +' .o_wslides_quiz_radio_box input').prop('checked', false);
-            }
+            return this._rpc({
+                route: '/slides/slide/quiz/submit',
+                params: {
+                    slide_id: self.slide.id,
+                    answer_ids: checkedAnswerIds,
+                }
+            }).then(function(data){
+                if (data.error) {
+                    self._alertShow(data.error);
+                } else {
+                    self.slide.completed = data.completed;
+                    self.quiz = _.extend(self.quiz, data);
+                    self._renderAnswersHighlighting();
+                    self._renderValidationInfo();
+                    if (self.slide.completed) {
+                        self._renderSuccessModal(data);
+                        self._setCompleted();
+                    }
+                }
+            });
         },
+
         //--------------------------------------------------------------------------
         // Handlers
         //--------------------------------------------------------------------------
-        _onAnswerClick: function (ev){
-            var self = this;
-            var target = $(ev.currentTarget);
-            if ((self.answeredQuestions.indexOf(target.attr('question_id'))) === -1){
-                var id = target.attr('id');
-                var question_id = target.attr('question_id');
-                $('.o_wslides_quiz_answer[question_id='+question_id+']').removeClass('o_wslides_quiz_good_answer');
-                $('#'+id+' input[type=radio]').prop('checked', true);
+
+        /**
+         * When clicking on an answer, this one should be marked as "checked".
+         *
+         * @private
+         * @param OdooEvent ev
+         */
+        _onAnswerClick: function (ev) {
+            if (! this.slide.readonly) {
+                $(ev.currentTarget).find('input[type=radio]').prop('checked', true);
             }
+            this._alertHide();
         },
-        _onSubmitQuiz: function (){
-            var self = this;
-            var inputs = $('input[type=radio]:checked');
+        /**
+         * Triggering a event to switch to next slide
+         *
+         * @private
+         * @param OdooEvent ev
+         */
+        _onClickNext: function (ev) {
+            if (this.slide.hasNext) {
+                this.trigger_up('quiz_next_slide', {
+                    'slideId': this.slide.id,
+                });
+            }
+        },
+        /**
+         * Submit a quiz and get the correction. It will display messages
+         * according to quiz result.
+         *
+         * @private
+         * @param OdooEvent ev
+         */
+        _onSubmitQuiz: function (ev) {
+            var inputs = this.$('input[type=radio]:checked');
             var values = [];
             for (var i = 0; i < inputs.length; i++){
                 values.push(parseInt($(inputs[i]).val()));
             }
-            if (values.length === self.slide.quiz.questions.length){
-                $('.quiz-danger').remove();
-                self._rpc({
-                    route: "/slide/quiz/submit",
-                    params: {
-                        slide_id: self.slide.id,
-                        answer_ids: values,
-                        quiz_id: self.slide.quiz_id
-                    }
-                }).then(function (data){
-                    self._highlightAnswers(data);
-                    self.slide.quiz.nb_attempts = data.attempts_count;
-                    if (data.passed){
-                        self.channelCompletion = data.channel_completion;
-                        self._updateProgressbar();
-                        $('#check-'+self.slide.id).replaceWith($('<i class="check-done o_wslides_slide_completed fa fa-check-circle"></i>'));
-                        self.slide.done = true;
-                        self._renderSuccessModal(data);
-                    }
-                    else {
-                        var points = self.slide.quiz.nb_attempts < self.slide.quiz.possible_rewards.length ? self.slide.quiz.possible_rewards[self.slide.quiz.nb_attempts] : self.slide.quiz.possible_rewards[self.slide.quiz.possible_rewards.length-1];
-                        $('#quiz-points').text(points);
-                    }
-                });
+
+            if (values.length === this.quiz.questions.length){
+                this._alertHide();
+                this._submitQuiz(values);
             } else {
-                $('#quiz_buttons').append($('<p class="quiz-danger text-danger mt-1">All questions must be answered !</p>'));
+                this._alertShow();
             }
         },
-});
-
-    sAnimations.registry.websiteSlidesQuizNoFullscreen = Widget.extend({
-        selector: '.o_w_slides_quiz_no_fullscreen',
-        xmlDependencies: ['/website_slides/static/src/xml/website_slides_fullscreen.xml'],
-        init: function (el){
-            this._super.apply(this, arguments);
-        },
-        start: function (){
-            this._super.apply(this, arguments);
-            var slideID = parseInt(this.$el.attr('slide_id'),10);
-            var slideDone = this.$el.attr('slide_done');
-            var nbAttempts = parseInt(this.$el.attr('nb_attempts'), 10);
-            var firstAttemptReward = this.$el.attr('first_reward');
-            var secondAttemptReward = this.$el.attr('second_reward');
-            var thirdAttemptReward = this.$el.attr('third_reward');
-            var fourthAttemptReward = this.$el.attr('fourth_reward');
-            var possibleRewards = [firstAttemptReward,secondAttemptReward,thirdAttemptReward,fourthAttemptReward];
-            var data = {
-                id: slideID,
-                done: slideDone,
-                quiz: {
-                    questions: [],
-                    nb_attempts: nbAttempts,
-                    possible_rewards: possibleRewards,
-                    reward: nbAttempts < possibleRewards.length ? possibleRewards[nbAttempts] : possibleRewards[possibleRewards.length-1]
-                }
-            };
-            if (!slideDone){
-                var NewQuiz = new Quiz(this, data);
-                NewQuiz.appendTo(".o_w_slides_quiz_no_fullscreen");
-            }
-        }
+    });
+
+    sAnimations.registry.websiteSlidesQuizNoFullscreen = sAnimations.Class.extend({
+        selector: '.o_wslides_js_lesson_quiz',
+        custom_events: {
+            quiz_next_slide: '_onQuizNextSlide',
+            quiz_completed: '_onQuizCompleted',
+        },
+
+        //----------------------------------------------------------------------
+        // Public
+        //----------------------------------------------------------------------
+
+        /**
+         * @override
+         * @param {Object} parent
+         */
+        start: function () {
+            var self = this;
+            var defs = [this._super.apply(this, arguments)];
+            $('.o_wslides_js_lesson_quiz').each(function () {
+                var slideData = $(this).data();
+                slideData.quizData = {
+                    questions: self._extractQuestionsAndAnswers(),
+                    quizKarmaMax: slideData.quizKarmaMax,
+                    quizKarmaWon: slideData.quizKarmaWon,
+                    quizKarmaGain: slideData.quizKarmaGain,
+                    quizAttemptsCount: slideData.quizAttemptsCount,
+                };
+                defs.push(new Quiz(self, slideData, slideData.quizData).attachTo($(this)));
+            });
+            return $.when.apply($, defs);
+        },
+
+        //----------------------------------------------------------------------
+        // Handlers
+        //---------------------------------------------------------------------
+
+        _onQuizCompleted: function (slideId) {
+            console.log('set completed', slideId);
+        },
+
+        _onQuizNextSlide: function () {
+            var url = this.$el.data('next-slide-url');
+            window.location.replace(url);
+        },
+
+        //----------------------------------------------------------------------
+        // Private
+        //---------------------------------------------------------------------
+
+        /**
+         * Extract data from exiting DOM rendered server-side, to have the list of questions with their
+         * relative answers.
+         * This method should return the same format as /slide/quiz/get controller.
+         *
+         * @return {Array<Object>} list of questions with answers
+         */
+        _extractQuestionsAndAnswers: function() {
+            var questions = [];
+            this.$('.o_wslides_js_lesson_quiz_question').each(function () {
+                var $question = $(this);
+                var answers = [];
+                $question.find('.o_wslides_quiz_answer').each(function () {
+                    var $answer = $(this);
+                    answers.push({
+                        id: $answer.data('answerId'),
+                        text: $answer.data('text'),
+                    });
+                });
+                questions.push({
+                    id: $question.data('questionId'),
+                    title: $question.data('title'),
+                    answers: answers,
+                });
+            });
+            return questions;
+        },
     });
 
     return Quiz;
diff --git a/addons/website_slides/static/src/scss/slides_slide_fullscreen.scss b/addons/website_slides/static/src/scss/slides_slide_fullscreen.scss
index f6612d5d04f9..08ef8bc299b9 100644
--- a/addons/website_slides/static/src/scss/slides_slide_fullscreen.scss
+++ b/addons/website_slides/static/src/scss/slides_slide_fullscreen.scss
@@ -268,5 +268,44 @@
     .o_wslides_fs_player_no_sidebar {
         max-width: 100%;
         width: 100%;
-    }  
+    }
+
+    // quizz
+    .o_wslides_fs_quiz_container {
+        background-color: $gray-200;
+        height: 100%;
+        overflow: scroll;
+        overflow-x: hidden;
+        width: 100%;
+
+        .o_wslides_js_lesson_quiz_question {
+            li {
+                font-size: 1.2rem;
+            }
+
+            .o_wslides_quiz_answer {
+
+                &:hover {
+                    cursor: pointer;
+                }
+
+                label {
+                    font-size: 1.4rem;
+
+                    i.fa-circle {
+                    }
+
+                    input:checked + i.fa-circle {
+                        color: $primary !important;
+                        overflow: hidden;
+                    }
+                }
+            }
+        }
+    }
+
+    // Tools
+    .bg-brand {
+        background-color: $o-enterprise-color;
+    }
 }
diff --git a/addons/website_slides/static/src/scss/website_slides.scss b/addons/website_slides/static/src/scss/website_slides.scss
index c8d35376e17b..35ffa9547d42 100644
--- a/addons/website_slides/static/src/scss/website_slides.scss
+++ b/addons/website_slides/static/src/scss/website_slides.scss
@@ -42,6 +42,31 @@ $gray-50: #f4f4f4 !default;
         color: $link-color;
     }
 
+    .o_wslides_js_lesson_quiz_question {
+        li {
+            font-size: 1.2rem;
+        }
+
+        .o_wslides_quiz_answer {
+
+            &:hover {
+                cursor: pointer;
+            }
+
+            label {
+                font-size: 1.4rem;
+
+                i.fa-circle {
+                }
+
+                input:checked + i.fa-circle {
+                    color: $primary !important;
+                    overflow: hidden;
+                }
+            }
+        }
+    }
+
     // tools
     // ****************************************
 
@@ -53,6 +78,10 @@ $gray-50: #f4f4f4 !default;
         opacity: 0.5;
     }
 
+    .bg-brand {
+        background-color: $o-enterprise-color;
+    }
+
     .progress {
         overflow: visible;
         margin-bottom: 0em;
diff --git a/addons/website_slides/static/src/scss/website_slides_quiz.scss b/addons/website_slides/static/src/scss/website_slides_quiz.scss
deleted file mode 100644
index cca013c0be9b..000000000000
--- a/addons/website_slides/static/src/scss/website_slides_quiz.scss
+++ /dev/null
@@ -1,237 +0,0 @@
-
-    .o_wslides_fs_quiz {
-        background-color: #F6F6F6;
-        width: 100%;
-        height: 100%;
-        overflow: scroll;
-        overflow-x: hidden;
-        padding-bottom: 50px;
-
-    }
-
-    .o_wslides_quiz_question {
-        margin-top: 50px;
-        width: 100%;
-        .o_wslides_quiz_question_title {
-            font-size: 1.5rem;
-            margin-bottom: 20px;
-            .o_wslides_quiz_question_title_number {
-                color: #8C8C8C;
-                font-size: 1.3rem;
-                font-weight: bold;
-            }
-
-            .o_wslides_quiz_question_title_text {
-                margin-left: 2px;
-                color: #2F2F2F;
-            }
-        }
-
-        .o_wslides_quiz_question_answers{
-            background-color: #fff;
-            list-style: none;
-            padding: 0;
-
-            .o_wslides_quiz_answer {
-                padding: 10px 20px;
-                border-bottom: 1px solid #B6B6B6;
-                font-size: 1.2rem;
-                display: flex;
-                align-items: center;
-
-                label.o_wslides_quiz_radio_box{
-                    background-color: #f6f6f6;
-                    width: 35px;
-                    height: 35px;
-                    display: inline-flex;
-                    border-radius: 50%;
-                    overflow: hidden;
-                    justify-content: center;
-                    align-items: center;
-                    margin: 0;
-                    margin-right: 20px;
-                    font-size: 1.2rem;
-                    outline: none;
-                    border: none;
-                    padding: 0;
-                }
-
-                label.o_wslides_quiz_radio_box input{
-                    display:none;
-                    outline: none;
-                    border: none;
-                }
-
-
-                label.o_wslides_quiz_radio_box span{
-                    width: 18px;
-                    height: 18px;
-                    margin: 0;
-                }
-
-                label.o_wslides_quiz_radio_box input:checked + span{
-                    background-color: $primary;
-                    border-radius: 50%;
-                    overflow: hidden;
-                }
-
-                label.o_wslides_quiz_radio_box input:checked{
-                    background-color: #C7EAE9;
-                }
-            }
-
-            .o_wslides_quiz_answer:not(.o_wslides_quiz_good_answer,.o_wslides_quiz_bad_answer,.o_wslides_quiz_ignored_answer):hover {
-                cursor: pointer;
-            }
-
-            .o_wslides_quiz_good_answer{
-                border: 2px solid #28A745;
-                label.o_wslides_quiz_radio_box input:checked + i{
-                    height: 100%;
-                    width: 100%;
-                    background-color: #fff;
-                    color: #28A745;
-                    display: flex;
-                    justify-content: center;
-                    align-items: center;
-                    font-size: 1.4rem;
-                    border: 10px solid #28A745;
-                    border-radius: 50px;
-                }
-            }
-
-            .o_wslides_quiz_bad_answer{
-                border: 2px solid #A94442;
-
-                    label.o_wslides_quiz_radio_box i{
-                        background-color: #A94442;
-                        color: white;
-                        height: 100%;
-                        width: 100%;
-                        font-size: 1rem;
-                        display: flex;
-                        justify-content: center;
-                        align-items: center;
-                    }
-            }
-
-
-            .quiz-ignored-answer {
-                color: $text-muted;
-            }
-        }
-    }
-
-    .o_wslides_quiz_modal_background{
-        position: absolute;
-        top: 0;
-        left: 0;
-        background-color: rgba(0,0,0,.2);
-        width: 100%;
-        height: 100%;
-
-    }
-
-    .o_wslides_quiz_success_modal {
-        display: flex;
-        position: absolute;
-        width: 40%;
-        max-width: 600px;
-        height: 450px;
-        background-color: #fff;
-        top: 50%;
-        left: 50%;
-        transform: translateX(-50%) translateY(-50%);
-        z-index: 2000;
-    }
-
-    .o_wslides_quiz_success_modal_left_panel {
-        width: 50%;
-        background-color: #855B79;
-
-    }
-
-    .o_wslides_quiz_success_modal_right_panel{
-        width: 50%;
-        display: flex;
-        flex-direction: column;
-        padding: 10px 20px 10px 50px;
-        position: relative;
-
-        .o_wslides_quiz_success_modal_close {
-            position: absolute;
-            top: 10px;
-            right: 15px;
-            text-transform: uppercase;
-            color: #787878;
-            font-size: 1.2rem;
-        }
-
-        .o_wslides_quiz_success_modal_close:hover{
-            cursor: pointer;
-        }
-
-        h1 {
-            color: #21272B;
-            font-weight: bold;
-            font-size: 2.3rem;
-            padding: 20px 0;
-            letter-spacing: 1px;
-        }
-
-        .o_wslides_quiz_success_progress_bar{
-            height: 10px;
-            width: 100%;
-            background-color: rgba(0,0,0,.1);
-
-            .o_wslides_quiz_success_progress_gauge{
-                width: 75%;
-                height: 100%;
-                background-color: #01ADAB;
-            }
-        }
-
-        .o_wslides_quiz_success_progress_bounds{
-            display: flex;
-            justify-content: space-between;
-        }
-
-        .o_wslides_quiz_success_progress_bounds{
-            font-weight: bold;
-            span:first-child {
-                color: #019F9D;
-            }
-            span:last-child{
-                color: #787878;
-            }
-        }
-
-        .o_wslides_quiz_success_reward {
-            height: 80px;
-            width: 100%;
-            border: 1px dashed #ccc;
-            margin-top: 70px;
-        }
-
-        .o_wslides_quiz_success_button{
-            flex: 1;
-            display: flex;
-            justify-content: flex-end;
-            align-items: flex-end;
-
-            a {
-                color: #787878;
-                text-transform: uppercase;
-                background-color: #fff;
-                border-radius: 3px;
-                border: 1px solid #C4C4C4;
-                font-weight: bold;
-                padding: 5px 15px;
-                text-align: center;
-            }
-
-            a:hover {
-                cursor: pointer;
-            }
-        }
-    }
\ No newline at end of file
diff --git a/addons/website_slides/static/src/xml/slide_quiz.xml b/addons/website_slides/static/src/xml/slide_quiz.xml
new file mode 100644
index 000000000000..6d778be9b2c3
--- /dev/null
+++ b/addons/website_slides/static/src/xml/slide_quiz.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates xml:space="preserve">
+    <t t-name="slide.slide.quiz">
+        <div class="o_wslides_fs_quiz_container">
+            <div class="mx-5">
+                <div t-foreach="widget.quiz.questions" t-as="question"
+                    class="o_wslides_js_lesson_quiz_question mt-3" t-att-data-question-id="question.id" t-att-data-title="question.question">
+                    <div class="h3">
+                        <small class="text-muted"><span t-esc="question_index+1"/>. </small> <span t-esc="question.question"/>
+                    </div>
+                    <ul class="bg-white list-unstyled">
+                        <t t-foreach="question.answers" t-as="answer">
+                            <li t-att-data-answer-id="answer.id"
+                                t-att-data-text="answer.text_value"
+                                class="o_wslides_quiz_answer pt-2 pb-2 border-bottom rounded d-flex align-items-center">
+
+                                <label class="mb-0 d-flex align-items-center justify-content-center mr-3 ml-3">
+                                    <input type="radio"
+                                        t-att-name="question.id"
+                                        t-att-value="answer.id"
+                                        class="d-none"/>
+                                    <i class="fa fa-circle text-muted"></i>
+                                    <i class="fa fa-times-circle text-danger d-none"></i>
+                                    <i class="fa fa-check-circle text-success d-none"></i>
+                                </label>
+                                <span t-esc="answer.text_value"/>
+                            </li>
+                        </t>
+                    </ul>
+                </div>
+                <div class="alert alert-danger o_wslides_js_lesson_quiz_error d-none" role="alert">
+                    <span></span>
+                    <button type="button" class="close" data-dismiss="alert" aria-label="Close">
+                        <span aria-hidden="true">&amp;times;</span>
+                    </button>
+                </div>
+                <div class="o_wslides_js_lesson_quiz_validation"/>
+            </div>
+        </div>
+    </t>
+
+    <t t-name="slide.slide.quiz.validation">
+        <t t-if="widget.readonly">
+            <button class="btn btn-primary" role="button" title="Join" aria-label="Join" disabled="disabled">Join course to take quiz</button>
+            <span role="button" title="Succeed and gain karma" aria-label="Succeed and gain karma"
+                class="badge badge-pill badge-warning text-white font-weight-bold ml-3 px-2 py-1">+ <t t-esc="widget.quiz.quizKarmaGain"/> XP</span>
+        </t>
+        <t t-else="">
+            <button t-if="! widget.slide.completed" role="button" title="Check answers" aria-label="Check answers"
+                class="btn btn-primary text-uppercase font-weight-bold o_wslides_js_lesson_quiz_submit">Check your answers</button>
+            <button t-if="widget.slide.completed" role="button" title="Quiz done" aria-label="Quiz done" disabled="disabled"
+                class="btn btn-primary text-uppercase font-weight-bold">Done !</button>
+            <span t-if="! widget.slide.completed"  role="button" title="Succeed and gain karma" aria-label="Succeed and gain karma"
+                class="badge badge-pill badge-warning text-white font-weight-bold ml-3 px-2 py-1">+ <t t-esc="widget.quiz.quizKarmaGain"/> XP</span>
+            <span t-if="widget.slide.completed" role="button" title="Gained karma" aria-label="Gained karma"
+                class="badge badge-pill badge-success text-white font-weight-bold ml-3 px-2 py-1">+ <t t-esc="widget.quiz.quizKarmaWon"/> XP</span>
+            <button t-if="widget.slide.completed &amp;&amp; widget.slide.hasNext"
+                class="btn btn-primary ml-3 o_wslides_quiz_continue">Continue</button>
+        </t>
+    </t>
+
+    <t t-name="slide.slide.quiz.finish">
+        <div class="modal o_wslides_quiz_modal" tabindex="-1" role="dialog" id="slides_quiz_modal">
+            <div class="modal-dialog" role="document">
+                <div class="modal-content">
+                    <div class="modal-body d-flex p-0">
+                        <div class="w-50 bg-brand"></div>
+                        <div class="w-50 d-flex flex-column p-3">
+                            <h1>Amazing!</h1>
+                            <div class="pb-3">
+                                <span class="pb-2">You gained <span class="badge badge-pill badge-success text-white font-weight-bold"><t t-esc="widget.quiz.quizKarmaWon"/> XP !</span> !</span>
+                                <div class="progress o_wslide_progress_bar">
+                                    <div class="progress-bar" role="progressbar" t-att-aria-valuenow="widget.quiz.rankProgress.progress" aria-valuemin="0" aria-valuemax="100" t-attf-style="width: #{widget.quiz.rankProgress.progress}%"/>
+                                </div>
+                                <small t-if="widget.quiz.rankProgress.lowerBound" class="float-left"><t t-esc="widget.quiz.rankProgress.lowerBound"/></small>
+                                <small t-if="widget.quiz.rankProgress.upperBound" class="float-right"><t t-esc="widget.quiz.rankProgress.upperBound"/></small>
+                            </div>
+                            <div class="pb-3" t-raw="widget.quiz.rankProgress.motivational"/>
+                            <button t-if="widget.slide.hasNext" type="button" class="btn btn-light o_wslides_quiz_btn align-self-end">Next <i class="fa fa-chevron-right"/></button>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </t>
+
+</templates>
diff --git a/addons/website_slides/static/src/xml/website_slides_fullscreen.xml b/addons/website_slides/static/src/xml/website_slides_fullscreen.xml
index 1817d085f71d..b364f486420b 100644
--- a/addons/website_slides/static/src/xml/website_slides_fullscreen.xml
+++ b/addons/website_slides/static/src/xml/website_slides_fullscreen.xml
@@ -6,56 +6,12 @@
             <div t-if="['presentation', 'document'].indexOf(slide.slide_type) !== -1 &amp;&amp; !showMiniQuiz" class="embed-responsive embed-responsive-4by3 embed-responsive-item ">
                 <iframe t-att-src="embed_url" class="o_wslides_iframe_viewer" allowFullScreen="true" height="315" width="420" frameborder="0"></iframe>
             </div>
-            <div style="width:100%;height:100%;text-align:center;" t-if="slide.slide_type == 'infographic' &amp;&amp; !showMiniQuiz">
+            <div t-if="slide.slide_type == 'infographic' &amp;&amp; !showMiniQuiz" style="width:100%;height:100%;text-align:center;">
                 <img t-attf-src="/web/image/slide.slide/#{slide.id}/image" class="img-fluid" style="height: 100%" alt="Slide image"/>
             </div>
-            <div t-if="(slide.slide_type == 'quiz' || showMiniQuiz) &amp;&amp; questions " class="o_wslides_fs_quiz">
-                <div>
-                    <t t-call="website.slide.quiz"/>
-                    <div id="quiz_buttons" class="container">
-                        <button t-if="!slide.done" class="btn btn-primary submit-quiz" >Check your answers</button>
-                        <button t-if="embed_url" class="btn btn-primary back-to-video" >Watch again</button>
-                        <button t-if="slide.done &amp;&amp; nextSlide" class="btn btn-primary next-slide" >Continue</button>
-                    </div>
-                </div>
-            </div>
-            <div t-if="slide.slide_type == 'webpage'" class="o_wslides_fs_webpage">
-                    <div class="o_wslides_fs_webpage_content container"/>
-            </div>
         <div class="o_wslides_fs_content"/>
     </t>
 
-    <t t-name="website.slide.quiz">
-        <div class="container quiz-questions">
-            <t t-set="i" t-value="1"/>
-            <t t-foreach="questions" t-as="question">
-                <t t-set="j" t-value="0"/>
-                <div class="o_wslides_quiz_question">
-                    <div>
-                        <div class="o_wslides_quiz_question_title">
-                            <span class="o_wslides_quiz_question_title_number"><span t-esc="i"/>. </span>
-                            <span class="o_wslides_quiz_question_title_text"  t-esc="question.title"/>
-                        </div>
-                        <ul class="o_wslides_quiz_question_answers">
-                            <t t-foreach="question.answers" t-as="answer">
-                                <li t-attf-id="answer#{answer.id}" t-att-question_id="question.id" t-att-class="(slide.done &amp;&amp; answer.correct) ? 'o_wslides_quiz_answer o_wslides_quiz_good_answer': slide.done ? 'o_wslides_quiz_answer o_wslides_quiz_ignored_answer' : 'o_wslides_quiz_answer'">
-                                    <label class="o_wslides_quiz_radio_box">
-                                        <input t-att-answer_id="answer.id" type="radio" t-att-name="question.id" t-att-question_id="question.id" t-att-value="answer.id" t-att-checked="(slide.done &amp;&amp; answer.correct) ? 'checked' : undefined" t-att-disabled="slide.done"/>
-                                        <span t-if="!(slide.done &amp;&amp; answer.correct)"/>
-                                        <i t-if="(slide.done &amp;&amp; answer.correct)" class="fa fa-check-circle"></i>
-                                    </label>
-                                        <span t-esc="answer.text"/>
-                                </li>
-                                <t t-set="j" t-value="j+1"/>
-                            </t>
-                        </ul>
-                    </div>
-                </div>
-                <t t-set="i" t-value="i+1"/>
-            </t>
-        </div>
-    </t>
-
     <t t-name="website.course.player.infos">
         <div class="text-box">
             <h1><t t-esc="slide.name"/></h1>
@@ -67,32 +23,6 @@
         </div>
     </t>
 
-    <t t-name="website.course.quiz.success">
-        <div class="o_wslides_quiz_modal_background">
-        </div>
-        <div class="o_wslides_quiz_success_modal">
-            <div class="o_wslides_quiz_success_modal_left_panel"></div>
-            <div class="o_wslides_quiz_success_modal_right_panel">
-                <div class="o_wslides_quiz_success_modal_close"><i class="fa fa-times"></i></div>
-                <h1>Amazing!</h1>
-                <div class="o_wslides_quiz_success_xp">
-                    <p>You gained <span t-esc="data.points"/> points!</p>
-                    <div class="o_wslides_quiz_success_progress_bar">
-                        <div class="o_wslides_quiz_success_progress_gauge" t-att-style="'width:'+data.user.progress_bar_percentage+'%'"/>
-                    </div>
-                    <div class="o_wslides_quiz_success_progress_bounds">
-                        <span t-esc="data.user.lower_bound"/>
-                        <span t-esc="data.user.upper_bound"/>
-                    </div>
-                </div>
-                <div class="o_wslides_quiz_success_reward"></div>
-                <div t-if="nextSlide" class="o_wslides_quiz_success_button">
-                    <a class="next-slide">Next &gt;</a>
-                </div>
-            </div>
-        </div>
-    </t>
-
     <t t-name="website.course.fullscreen.title">
         <t t-if="!miniQuiz">
             <i t-if="slide.slide_type == 'document'" class="fa fa-file-pdf-o mr-2"></i>
diff --git a/addons/website_slides/views/assets.xml b/addons/website_slides/views/assets.xml
index eb000518491e..742bac7c8ea1 100644
--- a/addons/website_slides/views/assets.xml
+++ b/addons/website_slides/views/assets.xml
@@ -8,7 +8,6 @@
                 <link rel="stylesheet" type="text/scss" href="/website_slides/static/src/scss/website_slides_profile.scss"/>
                 <link rel="stylesheet" type="text/scss" href="/website_slides/static/src/scss/slide_slide.scss" t-ignore="true"/>
                 <link rel="stylesheet" type="text/scss" href="/website_slides/static/src/scss/slides_slide_fullscreen.scss" t-ignore="true"/>
-                <link rel="stylesheet" type="text/scss" href="/website_slides/static/src/scss/website_slides_quiz.scss"/>
             </xpath>
             <xpath expr="//script[last()]" position="after">
                 <script type="text/javascript" src="/website_slides/static/src/js/slides.js"/>
diff --git a/addons/website_slides/views/website_slides_templates_lesson.xml b/addons/website_slides/views/website_slides_templates_lesson.xml
index e6df3294289c..12e9fad96114 100644
--- a/addons/website_slides/views/website_slides_templates_lesson.xml
+++ b/addons/website_slides/views/website_slides_templates_lesson.xml
@@ -59,12 +59,12 @@
                         <a class="o_wslides_slide_button" t-attf-href="/slides/slide/#{slug(previous_slide)}">Prev</a>
                     </t>
                     <a t-if="slide.slide_type in ('webpage', 'video', 'document', 'iconographic') and not slide.question_ids and (slide.id in user_progress and  not user_progress[slide.id].completed)" 
-                        t-att-href="'/slides/slide/%s/set_completed?%s' % (slide.id, 'next_slide=%s' % next_slide.id if next_slide else '')"
+                        t-att-href="'/slides/slide/%s/set_completed?%s' % (slide.id, 'next_slide_id=%s' % next_slide.id if next_slide else '')"
                         class="o_wslides_slide_button_done">Set Done</a>
                     <t t-if="next_slide">
                         <a class="o_wslides_slide_button" t-attf-href="/slides/slide/#{slug(next_slide)}">Next</a>
                     </t>
-                    <a t-if="slide.channel_id.channel_type == 'training'" t-attf-href="/slides/slide/#{slug(slide)}?fullscreen=1" class="o_wslides_slide_button_fullscreen ml-2"><i class="fa fa-desktop mr-2"></i>fullscreen</a>
+                    <a t-if="slide.channel_id.channel_type == 'training'" t-attf-href="/slides/slide/#{slug(slide)}?fullscreen=1" class="o_wslides_slide_button_fullscreen ml-2"><i class="fa fa-desktop mr-2"></i>Fullscreen</a>
                 </div>
             </div>
         <div class="o_wslides_slide_header_container mt16">
@@ -83,49 +83,7 @@
                         <div t-if="slide.slide_type == 'webpage'" class="border border-light rounded">
                             <div t-field="slide.html_content"/>
                         </div>
-                        <div t-if="slide.question_ids">
-                            <div class=".o_wslides_fs_quiz o_w_slides_quiz_no_fullscreen mt-2"
-                                t-attf-slide_id="#{slide.id}"
-                                t-attf-slide_done="#{slide.id in user_progress and user_progress[slide.id].completed}"
-                                t-attf-nb_attempts="#{user_progress[slide.id].quiz_attempts_count if slide.id in user_progress else ''}"
-                                t-attf-first_reward="#{slide.quiz_first_attempt_reward}"
-                                t-attf-second_reward="#{slide.quiz_second_attempt_reward}"
-                                t-attf-third_reward="#{slide.quiz_third_attempt_reward}"
-                                t-attf-fourth_reward="#{slide.quiz_fourth_attempt_reward}"
-                                t-if="slide.question_ids">
-                            <div>
-                                <t t-set="i" t-value="1"/>
-                                <t t-foreach="slide.question_ids" t-as="question">
-                                    <t t-set="j" t-value="0"/>
-                                    <div class="o_wslides_quiz_question" t-attf-id="#{question.id}" t-attf-title="#{question.question}">
-                                        <div>
-                                            <div class="o_wslides_quiz_question_title">
-                                                <span class="o_wslides_quiz_question_title_number"><span t-esc="i"/>. </span>
-                                                <span class="o_wslides_quiz_question_title_text"  t-esc="question.question"/>
-                                            </div>
-                                            <ul class="o_wslides_quiz_question_answers">
-                                                <t t-foreach="question.answer_ids" t-as="answer">
-                                                    <li t-attf-id="answer#{answer.id}" t-att-question_id="question.id" t-attf-text_value="#{answer.text_value}" t-attf-class="#{'o_wslides_quiz_answer o_wslides_quiz_good_answer' if slide.id in user_progress and user_progress[slide.id].completed and answer.is_correct else 'o_wslides_quiz_answer o_wslides_quiz_ignored_answer' if slide.id in user_progress and user_progress[slide.id].completed else 'o_wslides_quiz_answer'}">
-                                                        <label class="o_wslides_quiz_radio_box">
-                                                            <input t-att-answer_id="answer.id" type="radio" t-att-name="question.id" t-att-question_id="question.id" t-att-value="answer.id" t-att-checked="'checked' if slide.id in user_progress and user_progress[slide.id].completed and answer.is_correct else False" t-att-disabled="slide.id in user_progress and user_progress[slide.id].completed"/>
-                                                            <span t-if="not slide.id in user_progress or not (user_progress[slide.id].completed and answer.is_correct)"/>
-                                                            <i t-if="slide.id in user_progress and user_progress[slide.id].completed and answer.is_correct" class="fa fa-check-circle"></i>
-                                                        </label>
-                                                            <span t-esc="answer.text_value"/>
-                                                    </li>
-                                                    <t t-set="j" t-value="j+1"/>
-                                                </t>
-                                            </ul>
-                                        </div>
-                                    </div>
-                                    <t t-set="i" t-value="i+1"/>
-                                </t>
-                                <button t-if="not slide.id in user_progress or not user_progress[slide.id].completed" class="btn btn-primary submit-quiz" >Check your answers</button>
-                                <a t-if="next_slide" t-attf-style="#{'display: none' if not slide.id in user_progress or not user_progress[slide.id].completed else ''}" class="btn btn-primary next-slide" t-attf-href="/slides/slide/#{next_slide}">Continue</a>
-                            </div>
-
-                        </div>
-                        </div>
+                        <t t-if="slide.question_ids" t-call="website_slides.lesson_content_quiz"/>
                         <div class="row mt-3">
                             <div class="col-lg-6">
                                 <div clas="row">
@@ -462,6 +420,54 @@
     </ul>
 </template>
 
+<!-- Slide sub-tempalte: render a quiz serverside. Should be sync with JS qweb template "slide.slide.quiz" -->
+<template id="lesson_content_quiz" name="Lesson: Quiz specific content">
+    <div class="o_wslides_js_lesson_quiz"
+        t-att-data-id="slide.id"
+        t-att-data-name="slide.name"
+        t-att-data-slide-type="slide.slide_type"
+        t-att-data-readonly="not slide.id in user_progress"
+        t-att-data-completed="slide.id in user_progress and user_progress[slide.id].completed"
+        t-att-data-quiz-attempts-count="quiz_attempts_count"
+        t-att-data-quiz-karma-max="quiz_karma_max"
+        t-att-data-quiz-karma-gain="quiz_karma_gain"
+        t-att-data-quiz-karma-won="quiz_karma_won"
+        t-att-data-has-next="1 if next_slide else 0"
+        t-att-data-next-slide-url="'/slides/slide/%s' % (slug(next_slide)) if next_slide else None">
+        <div t-foreach="slide.question_ids" t-as="question"
+            class="o_wslides_js_lesson_quiz_question mt-3" t-att-data-question-id="question.id" t-att-data-title="question.question">
+            <div class="h3">
+                <small class="text-muted"><span t-esc="question_index+1"/>. </small> <span t-esc="question.question"/>
+            </div>
+            <ul class="bg-white list-unstyled">
+                <t t-foreach="question.answer_ids" t-as="answer">
+                    <li t-att-data-answer-id="answer.id"
+                        t-att-data-text="answer.text_value"
+                        class="o_wslides_quiz_answer pt-2 pb-2 border-bottom rounded d-flex align-items-center">
+                        <label class="mb-0 d-flex align-items-center justify-content-center mr-3 ml-3">
+                            <input type="radio"
+                                t-att-name="question.id"
+                                t-att-value="answer.id"
+                                class="d-none"/>
+                            <i class="fa fa-circle text-muted"></i>
+                            <i class="fa fa-times-circle text-danger d-none"></i>
+                            <i class="fa fa-check-circle text-success d-none"></i>
+                        </label>
+                        <span t-esc="answer.text_value"/>
+                    </li>
+                </t>
+            </ul>
+        </div>
+        <div class="alert alert-danger o_wslides_js_lesson_quiz_error d-none" role="alert">
+            <span></span>
+            <button type="button" class="close" data-dismiss="alert" aria-label="Close">
+                <span aria-hidden="true">&amp;times;</span>
+            </button>
+        </div>
+        <div class="o_wslides_js_lesson_quiz_validation"/>
+    </div>
+</template>
+
 <!-- Slide sub-template: display an item in a list of related slides (Related, Most Viewed, ...) -->
 <template id="related_slides" name="Related Slide">
     <li class="media mt-3">
diff --git a/addons/website_slides/views/website_slides_templates_lesson_fullscreen.xml b/addons/website_slides/views/website_slides_templates_lesson_fullscreen.xml
index 8b7a649d107b..a505f8c78e02 100644
--- a/addons/website_slides/views/website_slides_templates_lesson_fullscreen.xml
+++ b/addons/website_slides/views/website_slides_templates_lesson_fullscreen.xml
@@ -82,8 +82,9 @@
                             t-att-data-slug="slug(course_slide)"
                             t-att-data-done="course_slide.id in user_progress and user_progress[course_slide.id].completed"
                             t-att-data-id="course_slide.id"
+                            t-att-data-readonly="not course_slide.channel_id.is_member"
                             t-att-data-name="course_slide.name"
-                            t-attf-data-has_quiz="#{True if course_slide.question_ids else False}"
+                            t-att-data-has-quiz="True if course_slide.question_ids else False"
                             t-att-data-slide_type="course_slide.slide_type"
                             t-att-data-embed_code="course_slide.embed_code"
                             t-attf-class="o_wslides_fs_sidebar_slide_tab #{'active' if slide.id == course_slide.id else ''} d-flex justify-content-between">
diff --git a/addons/website_slides_survey/models/slide_slide.py b/addons/website_slides_survey/models/slide_slide.py
index 5165589533fd..731e7a37f4e5 100644
--- a/addons/website_slides_survey/models/slide_slide.py
+++ b/addons/website_slides_survey/models/slide_slide.py
@@ -32,10 +32,10 @@ class Slide(models.Model):
         ('check_certification_preview', "CHECK(slide_type != 'certification' OR is_preview = False)", "A slide of type certification cannot be previewed."),
     ]
 
-    def _action_set_viewed(self, target_partner):
+    def _action_set_viewed(self, target_partner, quiz_attempts_inc=False):
         """ If the slide viewed is a certification, we initialize the first survey.user_input
         for the current partner. """
-        new_slide_partners = super(Slide, self)._action_set_viewed(target_partner)
+        new_slide_partners = super(Slide, self)._action_set_viewed(target_partner, quiz_attempts_inc=quiz_attempts_inc)
         certification_slides = self.search([
             ('id', 'in', new_slide_partners.mapped('slide_id').ids),
             ('slide_type', '=', 'certification'),
-- 
GitLab