diff --git a/addons/website_slides/controllers/main.py b/addons/website_slides/controllers/main.py index a75dae3f142..d3902452fe3 100644 --- a/addons/website_slides/controllers/main.py +++ b/addons/website_slides/controllers/main.py @@ -2,7 +2,6 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from ast import literal_eval -from collections import defaultdict import base64 import json @@ -15,7 +14,7 @@ from odoo.addons.http_routing.models.ir_http import slug, unslug from odoo.addons.website.controllers.main import QueryURL from odoo.addons.website.models.ir_http import sitemap_qs2dom from odoo.addons.website_profile.controllers.main import WebsiteProfile -from odoo.exceptions import AccessError, UserError +from odoo.exceptions import AccessError, ValidationError, UserError from odoo.http import request from odoo.osv import expression @@ -879,6 +878,30 @@ class WebsiteSlides(WebsiteProfile): :return: rendered question template """ + + new_question_values = { + 'sequence': sequence, + 'question': question, + 'slide_id': slide_id, + 'answer_ids': [(0, 0, { + 'sequence': answer['sequence'], + 'text_value': answer['text_value'], + 'is_correct': answer['is_correct'], + 'comment': answer['comment'] + }) for answer in answer_ids] + } + + try: + # Attempt to create the question and validate the fields. + # We want to return the error to have a nice display instead of the default mechanism + # of exception handling that shows sticky toasters. + # (Use a 'new' and not a create to avoid having to rollback anything if an error is + # raised) + slide_question = request.env['slide.question'].new(new_question_values) + slide_question._validate_fields(new_question_values.keys()) + except ValidationError as e: + return {'error': e.args[0]} + fetch_res = self._fetch_slide(slide_id) if fetch_res.get('error'): return fetch_res @@ -894,17 +917,7 @@ class WebsiteSlides(WebsiteProfile): ('partner_id', '=', request.env.user.partner_id.id) ]).write({'completed': False}) - slide_question = request.env['slide.question'].create({ - 'sequence': sequence, - 'question': question, - 'slide_id': slide_id, - 'answer_ids': [(0, 0, { - 'sequence': answer['sequence'], - 'text_value': answer['text_value'], - 'is_correct': answer['is_correct'], - 'comment': answer['comment'] - }) for answer in answer_ids] - }) + slide_question = request.env['slide.question'].create(new_question_values) return request.env.ref('website_slides.lesson_content_quiz_question')._render({ 'slide': slide, 'question': slide_question, 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 9e2645aa4e0..1adaa2c440c 100644 --- a/addons/website_slides/static/src/js/slides_course_quiz.js +++ b/addons/website_slides/static/src/js/slides_course_quiz.js @@ -114,21 +114,25 @@ // Private //-------------------------------------------------------------------------- - _alertShow: function (alertCode) { + _showErrorMessage: function (errorCode) { var message = _t('There was an error validating this quiz.'); - if (alertCode === 'slide_quiz_incomplete') { + if (errorCode === 'slide_quiz_incomplete') { message = _t('All questions must be answered !'); - } else if (alertCode === 'slide_quiz_done') { + } else if (errorCode === 'slide_quiz_done') { message = _t('This quiz is already done. Retaking it is not possible.'); - } else if (alertCode === 'public_user') { + } else if (errorCode === 'public_user') { message = _t('You must be logged to submit the quiz.'); } - this.displayNotification({ - type: 'warning', - message: message, - sticky: true - }); + this.$('.o_wslides_js_quiz_submit_error') + .removeClass('d-none') + .find('.o_wslides_js_quiz_submit_error_text') + .text(message); + }, + + _hideErrorMessage: function () { + this.$('.o_wslides_js_quiz_submit_error') + .addClass('d-none'); }, /** @@ -350,8 +354,10 @@ } }); if (data.error) { - this._alertShow(data.error); + this._showErrorMessage(data.error); return; + } else { + this._hideErrorMessage(); } Object.assign(this.quiz, data); const {rankProgress, completed, channel_completion: completion} = this.quiz; @@ -466,6 +472,8 @@ _saveQuizAnswersToSession: function () { var quizAnswers = this._getQuizAnswers(); if (quizAnswers.length === this.quiz.questions.length) { + this._hideErrorMessage(); + return this._rpc({ route: '/slides/slide/quiz/save_to_session', params: { @@ -473,7 +481,7 @@ } }); } else { - this._alertShow('slide_quiz_incomplete'); + this._showErrorMessage('slide_quiz_incomplete'); return Promise.reject('The quiz is incomplete'); } }, diff --git a/addons/website_slides/static/src/js/slides_course_quiz_question_form.js b/addons/website_slides/static/src/js/slides_course_quiz_question_form.js index d2ab881db75..0fed6742850 100644 --- a/addons/website_slides/static/src/js/slides_course_quiz_question_form.js +++ b/addons/website_slides/static/src/js/slides_course_quiz_question_form.js @@ -148,34 +148,40 @@ var QuestionFormWidget = publicWidget.Widget.extend({ * @param options * @private */ - _createOrUpdateQuestion: function (options) { - var self = this; + _createOrUpdateQuestion: async function (options) { var $form = this.$('form'); + if (this._isValidForm($form)) { var values = this._serializeForm($form); - this._rpc({ + var renderedQuestion = await this._rpc({ route: '/slides/slide/quiz/question_add_or_update', params: values - }).then(function (renderedQuestion) { - if (options.update) { - self.trigger_up('display_updated_question', { - newQuestionRenderedTemplate: renderedQuestion, - $editedQuestion: self.$editedQuestion, - questionFormWidget: self, - }); - } else { - self.trigger_up('display_created_question', { - newQuestionRenderedTemplate: renderedQuestion, - questionFormWidget: self - }); - } }); + + if (typeof renderedQuestion === 'object' && renderedQuestion.error) { + this.$('.o_wslides_js_quiz_validation_error') + .removeClass('d-none') + .find('.o_wslides_js_quiz_validation_error_text') + .text(renderedQuestion.error); + } else if (options.update) { + this.$('.o_wslides_js_quiz_validation_error').addClass('d-none'); + this.trigger_up('display_updated_question', { + newQuestionRenderedTemplate: renderedQuestion, + $editedQuestion: this.$editedQuestion, + questionFormWidget: this, + }); + } else { + this.$('.o_wslides_js_quiz_validation_error').addClass('d-none'); + this.trigger_up('display_created_question', { + newQuestionRenderedTemplate: renderedQuestion, + questionFormWidget: this + }); + } } else { - this.displayNotification({ - type: 'warning', - message: _t('Please fill in the question'), - sticky: true - }); + this.$('.o_wslides_js_quiz_validation_error') + .removeClass('d-none') + .find('.o_wslides_js_quiz_validation_error_text') + .text(_t('Please fill in the question')); this.$('.o_wslides_quiz_question input').focus(); } }, diff --git a/addons/website_slides/static/src/scss/website_slides.scss b/addons/website_slides/static/src/scss/website_slides.scss index 4c8ce758f9a..6ec12ef722e 100644 --- a/addons/website_slides/static/src/scss/website_slides.scss +++ b/addons/website_slides/static/src/scss/website_slides.scss @@ -139,6 +139,10 @@ $truncate-limits: 2, 3, 10; i.o_wslides_js_quiz_icon:hover { color: black !important; } + + .o_wslides_js_quiz_validation_error, .o_wslides_js_quiz_submit_error { + display: inline-block; // not using d-inline-block to avoid messing with d-none + } } .o_wslides_js_lesson_quiz_question { diff --git a/addons/website_slides/static/src/xml/slide_quiz.xml b/addons/website_slides/static/src/xml/slide_quiz.xml index c2d6784791a..755668ac866 100644 --- a/addons/website_slides/static/src/xml/slide_quiz.xml +++ b/addons/website_slides/static/src/xml/slide_quiz.xml @@ -105,6 +105,10 @@ + XP +
+ + +
- - +
+ + +
+ + +
+
@@ -66,14 +72,12 @@
- + + Save + + + Cancel +