From 3150cf2510d64d45a703a5681a4e30366ca4190d Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Thu, 23 Mar 2023 22:22:57 +0530 Subject: [PATCH] feat: quiz with user input --- lms/lms/doctype/lms_quiz/lms_quiz.py | 76 +++++++++++++++++-- .../lms_quiz_question/lms_quiz_question.json | 59 +++++++++++++- lms/patches.txt | 1 + lms/patches/v0_0/add_question_type.py | 8 ++ lms/templates/quiz.html | 16 +++- lms/www/batch/learn.js | 75 +++++++++++++----- 6 files changed, 200 insertions(+), 35 deletions(-) create mode 100644 lms/patches/v0_0/add_question_type.py diff --git a/lms/lms/doctype/lms_quiz/lms_quiz.py b/lms/lms/doctype/lms_quiz/lms_quiz.py index 26da9246..bc158433 100644 --- a/lms/lms/doctype/lms_quiz/lms_quiz.py +++ b/lms/lms/doctype/lms_quiz/lms_quiz.py @@ -21,17 +21,39 @@ class LMSQuiz(Document): def validate_correct_answers(self): for question in self.questions: - correct_options = self.get_correct_options(question) + if question.type == "Choices": + self.validate_correct_options(question) + else: + self.validate_possible_answer(question) - if len(correct_options) > 1: - question.multiple = 1 + def validate_correct_options(self, question): + correct_options = self.get_correct_options(question) - if not len(correct_options): - frappe.throw( - _("At least one option must be correct for this question: {0}").format( - frappe.bold(question.question) - ) + if len(correct_options) > 1: + question.multiple = 1 + + if not len(correct_options): + frappe.throw( + _("At least one option must be correct for this question: {0}").format( + frappe.bold(question.question) ) + ) + + def validate_possible_answer(self, question): + possible_answers_fields = [ + "possibility_1", + "possibility_2", + "possibility_3", + "possibility_4", + ] + possible_answers = list(filter(lambda x: question.get(x), possible_answers_fields)) + + if not len(possible_answers): + frappe.throw( + _("Add at least one possible answer for this question: {0}").format( + frappe.bold(question.question) + ) + ) def get_correct_options(self, question): correct_option_fields = [ @@ -140,3 +162,41 @@ def save_quiz(quiz_title, questions, quiz): question_doc.save(ignore_permissions=True) return doc.name + + +@frappe.whitelist() +def check_answer(question, type, answer): + if type == "Choices": + return check_choice_answers(question, answer) + else: + return check_input_answers(question, answer) + + +def check_choice_answers(question, answer): + fields = [] + for num in range(1, 5): + fields.append(f"option_{cstr(num)}") + fields.append(f"is_correct_{cstr(num)}") + + question_details = frappe.db.get_value( + "LMS Quiz Question", question, fields, as_dict=1 + ) + + for num in range(1, 5): + if question_details[f"option_{num}"] == answer: + return question_details[f"is_correct_{num}"] + return 0 + + +def check_input_answers(question, answer): + fields = [] + for num in range(1, 5): + fields.append(f"possibility_{cstr(num)}") + + question_details = frappe.db.get_value( + "LMS Quiz Question", question, fields, as_dict=1 + ) + for num in range(1, 5): + if question_details[f"possibility_{num}"] == answer: + return 1 + return 0 diff --git a/lms/lms/doctype/lms_quiz_question/lms_quiz_question.json b/lms/lms/doctype/lms_quiz_question/lms_quiz_question.json index 924e696a..09856df7 100644 --- a/lms/lms/doctype/lms_quiz_question/lms_quiz_question.json +++ b/lms/lms/doctype/lms_quiz_question/lms_quiz_question.json @@ -6,6 +6,7 @@ "engine": "InnoDB", "field_order": [ "question", + "type", "options_section", "option_1", "is_correct_1", @@ -26,6 +27,13 @@ "is_correct_4", "column_break_20", "explanation_4", + "section_break_mnhr", + "possibility_1", + "possibility_3", + "column_break_vnaj", + "possibility_2", + "possibility_4", + "section_break_c1lf", "multiple" ], "fields": [ @@ -40,13 +48,13 @@ "fieldname": "option_1", "fieldtype": "Data", "label": "Option 1", - "reqd": 1 + "mandatory_depends_on": "eval: doc.type == 'Choices'" }, { "fieldname": "option_2", "fieldtype": "Data", "label": "Option 2", - "reqd": 1 + "mandatory_depends_on": "eval: doc.type == 'Choices'" }, { "fieldname": "option_3", @@ -95,18 +103,22 @@ "read_only": 1 }, { + "depends_on": "eval: doc.type == 'Choices'", "fieldname": "options_section", "fieldtype": "Section Break" }, { + "depends_on": "eval: doc.type == 'Choices'", "fieldname": "column_break_4", "fieldtype": "Section Break" }, { + "depends_on": "eval: doc.type == 'Choices'", "fieldname": "section_break_5", "fieldtype": "Section Break" }, { + "depends_on": "eval: doc.type == 'Choices'", "fieldname": "section_break_11", "fieldtype": "Section Break" }, @@ -149,12 +161,52 @@ { "fieldname": "column_break_20", "fieldtype": "Column Break" + }, + { + "fieldname": "type", + "fieldtype": "Select", + "label": "Type", + "options": "Choices\nUser Input" + }, + { + "depends_on": "eval: doc.type == 'User Input'", + "fieldname": "section_break_mnhr", + "fieldtype": "Section Break" + }, + { + "fieldname": "possibility_1", + "fieldtype": "Small Text", + "label": "Possible Answer 1", + "mandatory_depends_on": "eval: doc.type == 'User Input'" + }, + { + "fieldname": "possibility_2", + "fieldtype": "Small Text", + "label": "Possible Answer 2" + }, + { + "fieldname": "possibility_3", + "fieldtype": "Small Text", + "label": "Possible Answer 3" + }, + { + "fieldname": "possibility_4", + "fieldtype": "Small Text", + "label": "Possible Answer 4" + }, + { + "fieldname": "section_break_c1lf", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_vnaj", + "fieldtype": "Column Break" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-07-19 19:35:28.446236", + "modified": "2023-03-17 18:22:20.324536", "modified_by": "Administrator", "module": "LMS", "name": "LMS Quiz Question", @@ -162,5 +214,6 @@ "permissions": [], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/lms/patches.txt b/lms/patches.txt index f67b6d87..03c7594c 100644 --- a/lms/patches.txt +++ b/lms/patches.txt @@ -48,3 +48,4 @@ lms.patches.v0_0.user_singles_issue #23-11-2022 lms.patches.v0_0.rename_community_to_users #06-01-2023 lms.patches.v0_0.video_embed_link lms.patches.v0_0.rename_exercise_doctype +lms.patches.v0_0.add_question_type \ No newline at end of file diff --git a/lms/patches/v0_0/add_question_type.py b/lms/patches/v0_0/add_question_type.py new file mode 100644 index 00000000..a4411a0a --- /dev/null +++ b/lms/patches/v0_0/add_question_type.py @@ -0,0 +1,8 @@ +import frappe + + +def execute(): + questions = frappe.get_all("LMS Quiz Question", pluck="name") + + for question in questions: + frappe.db.set_value("LMS Quiz Question", question, "type", "Choices") diff --git a/lms/templates/quiz.html b/lms/templates/quiz.html index 8629a2c9..a21ac009 100644 --- a/lms/templates/quiz.html +++ b/lms/templates/quiz.html @@ -46,9 +46,9 @@
{% for question in quiz.questions %} - {% set instruction = _("Choose all answers that apply") if question.multiple else _("Choose 1 answer") %} + {% set instruction = _("Choose all answers that apply") if question.type == "Choices" and question.multiple else _("Choose 1 answer") if question.type == "Choices" else _("Enter the correct answer") %} -
{{ loop.index }}.
@@ -58,6 +58,7 @@
{{ instruction }}
+ {% if question.type == "Choices" %} {% set options = [question.option_1, question.option_2, question.option_3, question.option_4] %} {% for option in options %} {% if option %} @@ -66,8 +67,7 @@
@@ -80,6 +80,14 @@ {% endif %} {% endfor %} + {% else %} +
+
+ +
+
+ {% endif %} +
{% endfor %} diff --git a/lms/www/batch/learn.js b/lms/www/batch/learn.js index 3892861a..c70f07eb 100644 --- a/lms/www/batch/learn.js +++ b/lms/www/batch/learn.js @@ -2,6 +2,8 @@ frappe.ready(() => { this.marked_as_complete = false; this.quiz_submitted = false; this.file_type; + this.answer = []; + this.is_correct = []; let self = this; localStorage.removeItem($("#quiz-title").data("name")); @@ -16,6 +18,10 @@ frappe.ready(() => { enable_check(e); }); + $(".possibility").keyup((e) => { + enable_check(e); + }); + $(window).scroll(() => { let self = this; if ( @@ -37,6 +43,7 @@ frappe.ready(() => { }); $("#next").click((e) => { + add_to_local_storage(); mark_active_question(e); }); @@ -224,29 +231,56 @@ const check_answer = (e = undefined) => { } else { $("#next").removeClass("hide"); } - let [answer, is_correct] = parse_options(); - add_to_local_storage(current_index, answer, is_correct); + parse_options(); }; const parse_options = () => { - let answer = []; - let is_correct = []; + let type = $(".active-question").data("type"); - $(".active-question input").each((i, element) => { - let correct = parseInt($(element).attr("data-correct")); - if ($(element).prop("checked")) { - answer.push(decodeURIComponent($(element).val())); - correct && is_correct.push(1); - correct ? add_icon(element, "check") : add_icon(element, "wrong"); - } else { - correct && is_correct.push(0); - correct - ? add_icon(element, "minus-circle-green") - : add_icon(element, "minus-circle"); - } + if (type == "Choices") { + $(".active-question input").each((i, element) => { + is_answer_correct(type, element); + }); + } else { + is_answer_correct(type, $(".active-question textarea")); + } +}; + +const is_answer_correct = (type, element) => { + let answer = type == "Choices" ? decodeURIComponent($(element).val()) : ""; + + frappe.call({ + async: false, + method: "lms.lms.doctype.lms_quiz.lms_quiz.check_answer", + args: { + question: $(".active-question").data("name"), + type: type, + answer: answer, + }, + callback: (data) => { + type == "Choices" + ? parse_choices(element, data.message) + : parse_possible_answers(e); + }, }); +}; - return [answer, is_correct]; +const parse_choices = (element, correct) => { + if ($(element).prop("checked")) { + self.answer.push(decodeURIComponent($(element).val())); + correct && self.is_correct.push(1); + correct ? add_icon(element, "check") : add_icon(element, "wrong"); + } else { + correct && self.is_correct.push(0); + correct + ? add_icon(element, "minus-circle-green") + : add_icon(element, "minus-circle"); + } +}; + +const parse_possible_answers = () => { + self.answer.push(decodeURIComponent($(element).val())); + correct ? self.is_correct.push(1) : self.is_correct.push(0); }; const add_icon = (element, icon) => { @@ -261,14 +295,15 @@ const add_icon = (element, icon) => { //$(element).parent().empty().html(`
${label}
`); }; -const add_to_local_storage = (current_index, answer, is_correct) => { +const add_to_local_storage = () => { + let current_index = $(".active-question").attr("data-qt-index"); let quiz_name = $("#quiz-title").data("name"); let quiz_stored = JSON.parse(localStorage.getItem(quiz_name)); let quiz_obj = { question_index: current_index, - answer: answer.join(), - is_correct: is_correct, + answer: self.answer.join(), + is_correct: self.is_correct, }; quiz_stored ? quiz_stored.push(quiz_obj) : (quiz_stored = [quiz_obj]);