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 @@
+