Skip to content
Snippets Groups Projects
Commit e42ee496 authored by Patrick Hoste's avatar Patrick Hoste Committed by Thibault Delavallée
Browse files

[IMP] website_slides: add comments on answers showed after answering a quiz


PURPOSE :

A quiz after a course content is a good way to check if the user gets the previous lesson.
Despite, the user/student may not understand why his answer was wrong/correct.
In this task we'll allow the teacher to add a comment next to the asnwer of a quiz.

SPECIFICATION :

The quiz creator will now be able to comment the answers to inform the frontend user why
his answer is wrong/correct. He will be able to add the comments both in backend and frontend.

Task ID : 2072566
PR : #41188

Signed-off-by: default avatarThibault Delavallee (tde) <tde@openerp.com>
parent 9b534649
No related branches found
No related tags found
No related merge requests found
Showing with 149 additions and 62 deletions
......@@ -133,7 +133,8 @@ class WebsiteSlides(WebsiteProfile):
'answer_ids': [{
'id': answer.id,
'text_value': answer.text_value,
'is_correct': answer.is_correct if slide_completed or request.website.is_publisher() else None
'is_correct': answer.is_correct if slide_completed or request.website.is_publisher() else None,
'comment': answer.comment if request.website.is_publisher else None
} for answer in question.sudo().answer_ids],
} for question in slide.question_ids]
}
......@@ -753,7 +754,8 @@ class WebsiteSlides(WebsiteProfile):
'answer_ids': [(0, 0, {
'sequence': answer['sequence'],
'text_value': answer['text_value'],
'is_correct': answer['is_correct']
'is_correct': answer['is_correct'],
'comment': answer['comment']
}) for answer in answer_ids]
})
return request.env.ref('website_slides.lesson_content_quiz_question').render({
......@@ -798,7 +800,6 @@ class WebsiteSlides(WebsiteProfile):
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_partner_info(slide, quiz_done=True)
......@@ -815,8 +816,12 @@ class WebsiteSlides(WebsiteProfile):
'level_up': rank_progress['previous_rank']['lower_bound'] != rank_progress['new_rank']['lower_bound']
})
return {
'goodAnswers': user_good_answers.ids,
'badAnswers': user_bad_answers.ids,
'answers': {
answer.question_id.id: {
'is_correct': answer.is_correct,
'comment': answer.comment
} for answer in user_answers
},
'completed': slide.user_membership_id.sudo().completed,
'channel_completion': slide.channel_id.completion,
'quizKarmaWon': quiz_info['quiz_karma_won'],
......
......@@ -83,18 +83,21 @@
<field name="text_value">A fruit</field>
<field name="sequence">1</field>
<field name="is_correct" eval="True"/>
<field name="comment">Correct ! A strawberry is a fruit because it's the product of a tree.</field>
<field name="question_id" ref="slide_slide_demo_0_4_question_0"/>
</record>
<record id="slide_slide_demo_0_4_question_0_1" model="slide.answer">
<field name="text_value">A vegetable</field>
<field name="sequence">2</field>
<field name="is_correct" eval="False"/>
<field name="comment">Incorrect ! A strawberry is not a vegetable.</field>
<field name="question_id" ref="slide_slide_demo_0_4_question_0"/>
</record>
<record id="slide_slide_demo_0_4_question_0_2" model="slide.answer">
<field name="text_value">A table</field>
<field name="sequence">3</field>
<field name="is_correct" eval="False"/>
<field name="comment">Incorrect ! A table is a piece of furniture.</field>
<field name="question_id" ref="slide_slide_demo_0_4_question_0"/>
</record>
<record id="slide_slide_demo_0_4_question_1" model="slide.question">
......@@ -106,12 +109,14 @@
<field name="text_value">A shovel</field>
<field name="sequence">1</field>
<field name="is_correct" eval="True"/>
<field name="comment">Correct ! A shovel is the perfect tool to dig a hole.</field>
<field name="question_id" ref="slide_slide_demo_0_4_question_1"/>
</record>
<record id="slide_slide_demo_0_4_question_1_1" model="slide.answer">
<field name="text_value">A spoon</field>
<field name="sequence">2</field>
<field name="is_correct" eval="False"/>
<field name="comment">Incorrect ! Good luck digging a hole with a spoon...</field>
<field name="question_id" ref="slide_slide_demo_0_4_question_1"/>
</record>
......@@ -270,12 +275,14 @@
<field name="text_value">Yes</field>
<field name="sequence">1</field>
<field name="is_correct" eval="True"/>
<field name="comment">Correct !</field>
<field name="question_id" ref="slide_slide_demo_1_4_question_0"/>
</record>
<record id="slide_slide_demo_1_4_question_0_1" model="slide.answer">
<field name="text_value">No</field>
<field name="sequence">2</field>
<field name="is_correct" eval="False"/>
<field name="comment">Incorrect !</field>
<field name="question_id" ref="slide_slide_demo_1_4_question_0"/>
</record>
<record id="slide_slide_demo_1_4_question_1" model="slide.question">
......@@ -287,18 +294,21 @@
<field name="text_value">Yes</field>
<field name="sequence">1</field>
<field name="is_correct" eval="True"/>
<field name="comment">Correct ! Congratulations you have time to loose</field>
<field name="question_id" ref="slide_slide_demo_1_4_question_1"/>
</record>
<record id="slide_slide_demo_1_4_question_1_1" model="slide.answer">
<field name="text_value">No</field>
<field name="sequence">2</field>
<field name="is_correct" eval="False"/>
<field name="comment">Incorrect ! You really should read it.</field>
<field name="question_id" ref="slide_slide_demo_1_4_question_1"/>
</record>
<record id="slide_slide_demo_1_4_question_1_2" model="slide.answer">
<field name="text_value">What was the question again ?</field>
<field name="sequence">3</field>
<field name="is_correct" eval="False"/>
<field name="comment">Incorrect ! Seriously ?</field>
<field name="question_id" ref="slide_slide_demo_1_4_question_1"/>
</record>
<record id="slide_slide_demo_1_5" model="slide.slide">
......@@ -392,12 +402,14 @@
<field name="text_value">Yes</field>
<field name="sequence">1</field>
<field name="is_correct" eval="False"/>
<field name="comment">Incorrect !</field>
<field name="question_id" ref="slide_slide_demo_2_0_question_0"/>
</record>
<record id="slide_slide_demo_2_0_question_0_1" model="slide.answer">
<field name="text_value">No</field>
<field name="sequence">2</field>
<field name="is_correct" eval="True"/>
<field name="comment">Correct !</field>
<field name="question_id" ref="slide_slide_demo_2_0_question_0"/>
</record>
<record id="slide_slide_demo_2_0_question_1" model="slide.question">
......@@ -409,18 +421,21 @@
<field name="text_value">Yes</field>
<field name="sequence">1</field>
<field name="is_correct" eval="False"/>
<field name="comment">Incorrect !</field>
<field name="question_id" ref="slide_slide_demo_2_0_question_1"/>
</record>
<record id="slide_slide_demo_2_0_question_1_1" model="slide.answer">
<field name="text_value">No</field>
<field name="sequence">2</field>
<field name="is_correct" eval="True"/>
<field name="comment">Correct !</field>
<field name="question_id" ref="slide_slide_demo_2_0_question_1"/>
</record>
<record id="slide_slide_demo_2_0_question_1_2" model="slide.answer">
<field name="text_value">And also bananas</field>
<field name="sequence">3</field>
<field name="is_correct" eval="False"/>
<field name="comment">Incorrect ! of course not ...</field>
<field name="question_id" ref="slide_slide_demo_2_0_question_1"/>
</record>
<record id="slide_slide_demo_2_1" model="slide.slide">
......@@ -666,12 +681,14 @@
<field name="text_value">Yes</field>
<field name="sequence">1</field>
<field name="is_correct" eval="True"/>
<field name="comment">Correct ! You did it !</field>
<field name="question_id" ref="slide_slide_demo_5_3_question_0"/>
</record>
<record id="slide_slide_demo_5_3_question_0_1" model="slide.answer">
<field name="text_value">No</field>
<field name="sequence">2</field>
<field name="is_correct" eval="False"/>
<field name="comment">Incorrect ! You better think twice...</field>
<field name="question_id" ref="slide_slide_demo_5_3_question_0"/>
</record>
......
......@@ -56,3 +56,4 @@ class SlideAnswer(models.Model):
question_id = fields.Many2one('slide.question', string="Question", required=True, ondelete='cascade')
text_value = fields.Char("Answer", required=True, translate=True)
is_correct = fields.Boolean("Is correct answer")
comment = fields.Text("Comment", translate=True, help='This comment will be displayed to the user if he selects this answer')
......@@ -88,8 +88,6 @@ odoo.define('website_slides.quiz', function (require) {
start: function() {
var self = this;
return this._super.apply(this, arguments).then(function () {
self._renderAnswers();
self._renderAnswersHighlighting();
self._renderValidationInfo();
self._bindSortable();
self._checkLocationHref();
......@@ -207,8 +205,9 @@ odoo.define('website_slides.quiz', function (require) {
* @private
* Decorate the answers according to state
*/
_renderAnswers: function () {
_disableAnswers: function () {
var self = this;
this.$('.o_wslides_js_lesson_quiz_question').addClass('completed-disabled');
this.$('input[type=radio]').each(function () {
$(this).prop('disabled', self.slide.readonly || self.slide.completed);
});
......@@ -217,29 +216,37 @@ odoo.define('website_slides.quiz', function (require) {
/**
* @private
* Decorate the answer inputs according to the correction
* and adds the answer comment if any
*/
_renderAnswersHighlighting: function () {
_renderAnswersHighlightingAndComments: function () {
var self = this;
this.$('a.o_wslides_quiz_answer').each(function () {
var $answer = $(this);
var answerId = $answer.data('answerId');
if (_.contains(self.quiz.goodAnswers, answerId)) {
$answer.removeClass('list-group-item-danger').addClass('list-group-item-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('list-group-item-success').addClass('list-group-item-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 {
if (!self.slide.completed) {
this.$('.o_wslides_js_lesson_quiz_question').each(function () {
var $question = $(this);
var questionId = $question.data('questionId');
var isCorrect = self.quiz.answers[questionId].is_correct;
$question.find('a.o_wslides_quiz_answer ').each(function () {
var $answer = $(this);
if ($answer.find('input[type=radio]')[0].checked) {
if (isCorrect) {
$answer.removeClass('list-group-item-danger').addClass('list-group-item-success');
$answer.find('i.fa').addClass('d-none');
$answer.find('i.fa-check-circle').removeClass('d-none');
} else {
$answer.removeClass('list-group-item-success').addClass('list-group-item-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('list-group-item-danger list-group-item-success');
$answer.find('i.fa').addClass('d-none');
$answer.find('i.fa-circle').removeClass('d-none');
}
});
var comment = self.quiz.answers[questionId].comment;
if (comment) {
$question.find('.o_wslides_quiz_answer_info').removeClass('d-none');
$question.find('.o_wslides_quiz_answer_comment').text(comment);
}
});
},
......@@ -273,6 +280,7 @@ odoo.define('website_slides.quiz', function (require) {
} else {
self.quiz = _.extend(self.quiz, data);
if (data.completed) {
self._disableAnswers();
new SlideQuizFinishModal(self, {
quiz: self.quiz,
hasNext: self.slide.hasNext,
......@@ -282,7 +290,7 @@ odoo.define('website_slides.quiz', function (require) {
self.trigger_up('slide_completed', {slide: self.slide, completion: data.channel_completion});
}
self._hideEditOptions();
self._renderAnswersHighlighting();
self._renderAnswersHighlightingAndComments();
self._renderValidationInfo();
}
});
......@@ -301,7 +309,8 @@ odoo.define('website_slides.quiz', function (require) {
answers.push({
'id': $(this).data('answerId'),
'text_value': $(this).data('text'),
'is_correct': $(this).data('isCorrect')
'is_correct': $(this).data('isCorrect'),
'comment': $(this).data('comment')
});
});
return {
......
......@@ -18,8 +18,11 @@ var QuestionFormWidget = publicWidget.Widget.extend({
events: {
'click .o_wslides_js_quiz_validate_question': '_validateQuestion',
'click .o_wslides_js_quiz_cancel_question': '_cancelValidation',
'click .o_wslides_js_quiz_comment_answer': '_toggleAnswerLineComment',
'click .o_wslides_js_quiz_add_answer': '_addAnswerLine',
'click .o_wslides_js_quiz_remove_answer': '_removeAnswerLine',
'click .o_wslides_js_quiz_remove_answer_comment': '_removeAnswerLineComment',
'change .o_wslides_js_quiz_answer_comment > input[type=text]': '_onCommentChanged'
},
/**
......@@ -51,6 +54,34 @@ var QuestionFormWidget = publicWidget.Widget.extend({
// Handlers
//--------------------------------------------------------------------------
/**
*
* @param commentInput
* @private
*/
_onCommentChanged: function (event) {
var input = event.currentTarget;
var commentIcon = $(input).closest('.o_wslides_js_quiz_answer').find('.o_wslides_js_quiz_comment_answer');
if (input.value.trim() !== '') {
commentIcon.addClass('text-primary');
commentIcon.removeClass('text-muted');
} else {
commentIcon.addClass('text-muted');
commentIcon.removeClass('text-primary');
}
},
/**
* Toggle the input for commenting the answer line which will be
* seen by the frontend user when submitting the quiz.
* @param ev
* @private
*/
_toggleAnswerLineComment: function (ev) {
var commentLine = $(ev.currentTarget).closest('.o_wslides_js_quiz_answer').find('.o_wslides_js_quiz_answer_comment').toggleClass('d-none');
commentLine.find('input[type=text]').focus();
},
/**
* Adds a new answer line after the element the user clicked on
* e.g. If there is 3 answer lines and the user click on the add
......@@ -74,6 +105,16 @@ var QuestionFormWidget = publicWidget.Widget.extend({
}
},
/**
*
* @param ev
* @private
*/
_removeAnswerLineComment: function (ev) {
var commentLine = $(ev.currentTarget).closest('.o_wslides_js_quiz_answer_comment').addClass('d-none');
commentLine.find('input[type=text]').val('').change();
},
/**
* Handler when user click on 'Save & New', 'Save & Close'
* or 'Update' buttons.
......@@ -165,12 +206,13 @@ var QuestionFormWidget = publicWidget.Widget.extend({
var answers = [];
var sequence = 1;
$form.find('.o_wslides_js_quiz_answer').each(function () {
var value = $(this).find('input[type=text]').val();
var value = $(this).find('.o_wslides_js_quiz_answer_value').val();
if (value.trim() !== "") {
var answer = {
'sequence': sequence++,
'text_value': value,
'is_correct': $(this).find('input[type=radio]').prop('checked') === true
'is_correct': $(this).find('input[type=radio]').prop('checked') === true,
'comment': $(this).find('.o_wslides_js_quiz_answer_comment > input[type=text]').val().trim()
};
answers.push(answer);
}
......
......@@ -83,12 +83,14 @@ $o-wslides-fs-side-width: 300px;
}
}
.o_wsildes_quiz_question_input {
.o_wslides_js_quiz_is_correct > label,
.o_wslides_js_quiz_add_answer,
.o_wslides_js_quiz_remove_answer {
.o_wslides_js_lesson_quiz {
i.o_wslides_js_quiz_icon {
cursor: pointer;
}
i.o_wslides_js_quiz_icon:hover {
color: black !important;
}
}
.o_wslides_js_lesson_quiz_question {
......@@ -105,20 +107,6 @@ $o-wslides-fs-side-width: 300px;
pointer-events: none;
}
.o_wslides_js_quiz_edit_question,
.o_wslides_js_quiz_delete_question {
cursor: pointer;
}
.o_wslides_js_quiz_sequence_handler {
cursor: pointer;
opacity: 0.4;
}
.o_wslides_js_quiz_sequence_handler:hover {
opacity: 1;
}
&.completed-disabled{
pointer-events: none;
}
......@@ -127,7 +115,7 @@ $o-wslides-fs-side-width: 300px;
a.o_wslides_js_quiz_is_correct {
color: black;
input:checked + i.fa-check-circle-o {
color: $primary;
color: $primary !important;
}
}
......
......@@ -28,6 +28,10 @@
<span t-esc="answer.text_value"/>
</a>
</t>
<div class="o_wslides_quiz_answer_info list-group-item list-group-item-info d-none">
<i class="fa fa-info-circle"/>
<span class="o_wslides_quiz_answer_comment"/>
</div>
</div>
</div>
<div t-if="!widget.slide.completed" class="o_wslides_js_lesson_quiz_validation border-top pt-3"/>
......
......@@ -34,16 +34,32 @@
<t t-name="slide.quiz.answer.line">
<div class="o_wslides_js_quiz_answer row align-items-center mb-1" t-attf-data-answer-id="#{answer ? answer.id : ''}" >
<a class="o_wslides_js_quiz_is_correct col-1 text-center" title="Select one good answer">
<label class="my-0">
<input t-if="answer and answer.is_correct" class="d-none" type="radio" name="radio" checked="true" />
<input t-else="" class="d-none" type="radio" name="radio" />
<i class="fa fa-lg fa-check-circle-o" />
</label>
</a>
<input type="text" class="form-control col-9" placeholder="Enter your answer" t-attf-value="#{answer ? answer.text_value : ''}"/>
<i class="o_wslides_js_quiz_add_answer fa fa-lg fa-plus-circle p-2" title="Add another answer" />
<i class="o_wslides_js_quiz_remove_answer fa fa-lg fa-trash-o p-2" title="Remove this answer" />
<div class="col offset-1">
<div class="row align-items-center">
<div class="input-group col-9 p-0">
<input type="text" class="o_wslides_js_quiz_answer_value form-control" placeholder="Enter your answer" t-attf-value="#{answer ? answer.text_value : ''}"/>
<div class="input-group-append">
<div class="input-group-text">
<a class="o_wslides_js_quiz_is_correct" title="This is the correct answer">
<label class="my-0">
<input t-if="answer and answer.is_correct" class="d-none" type="radio" name="radio" checked="true" />
<input t-else="" class="d-none" type="radio" name="radio" />
<i class="o_wslides_js_quiz_icon fa fa-lg fa-check-circle-o text-muted" />
</label>
</a>
</div>
</div>
</div>
<i t-attf-class="o_wslides_js_quiz_icon o_wslides_js_quiz_comment_answer fa fa-lg fa-info-circle p-2 #{answer &amp;&amp; answer.comment ? 'text-primary' : 'text-muted'}" title="Add comment on this answer" />
<i class="o_wslides_js_quiz_icon o_wslides_js_quiz_add_answer fa fa-lg fa-plus-circle p-2 text-muted" title="Add an answer below this one" />
<i class="o_wslides_js_quiz_icon o_wslides_js_quiz_remove_answer fa fa-lg fa-trash-o p-2 text-muted" title="Remove this answer" />
</div>
<div class="o_wslides_js_quiz_answer_comment row align-items-center d-none">
<input type="text" class="form-control col-8 offset-1 mt-1" placeholder="This is the correct answer, congratulation"
t-attf-value="#{answer ? answer.comment : ''}" />
<i class="o_wslides_js_quiz_icon o_wslides_js_quiz_remove_answer_comment fa fa-lg fa-trash-o p-2 text-muted" title="Remove the answer comment" />
</div>
</div>
</div>
</t>
......
......@@ -16,6 +16,7 @@
<tree editable="bottom" create="true" delete="true">
<field name="text_value"/>
<field name="is_correct"/>
<field name="comment"/>
</tree>
</field>
</sheet>
......
......@@ -442,21 +442,21 @@
<div class="row d-flex mb-2 mx-0">
<div class="h4">
<small class="text-muted">
<i class="o_wslides_js_quiz_sequence_handler fa fa-bars mr-1" t-if="slide.channel_id.can_upload and not slide_completed" />
<i class="o_wslides_js_quiz_icon o_wslides_js_quiz_sequence_handler fa fa-bars mr-1 text-muted" t-if="slide.channel_id.can_upload and not slide_completed" />
<t t-if="question_index != NoneType"><span class="o_wslides_quiz_question_sequence" t-esc="question_index+1"/>.</t>
<t t-else=""><span class="o_wslides_quiz_question_sequence" t-esc="question['sequence']"/>.</t>
</small>
<span t-esc="question['question']"/>
</div>
<div class="ml-auto o_wslides_js_quiz_edit_del" t-if="slide.channel_id.can_upload and not slide_completed" >
<i class="o_wslides_js_quiz_edit_question fa fa-pencil-square-o p-1"></i>
<i class="o_wslides_js_quiz_delete_question fa fa-trash p-1"></i>
<i class="o_wslides_js_quiz_icon o_wslides_js_quiz_edit_question fa fa-pencil-square-o p-1 text-muted"></i>
<i class="o_wslides_js_quiz_icon o_wslides_js_quiz_delete_question fa fa-trash p-1 text-muted"></i>
</div>
</div>
<div class="list-group">
<t t-foreach="question['answer_ids']" t-as="answer">
<a t-att-data-answer-id="answer['id']" href="#"
t-att-data-text="answer['text_value']" t-att-data-is-correct="answer['is_correct']"
t-att-data-text="answer['text_value']" t-att-data-is-correct="answer['is_correct']" t-att-data-comment="answer['comment']"
t-att-class="'o_wslides_quiz_answer list-group-item list-group-item-action d-flex align-items-center %s' % ('list-group-item-success' if slide_completed and answer['is_correct'] else '')">
<label class="my-0 d-flex align-items-center justify-content-center mr-2">
<input type="radio"
......@@ -471,6 +471,10 @@
<span t-esc="answer['text_value']"/>
</a>
</t>
<div class="o_wslides_quiz_answer_info list-group-item list-group-item-info d-none">
<i class="fa fa-info-circle"/>
<span class="o_wslides_quiz_answer_comment"/>
</div>
</div>
</div>
</template>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment