diff --git a/cypress/e2e/course_creation.cy.js b/cypress/e2e/course_creation.cy.js index a76bdf8b..3aaf6155 100644 --- a/cypress/e2e/course_creation.cy.js +++ b/cypress/e2e/course_creation.cy.js @@ -21,6 +21,7 @@ describe("Course Creation", () => { cy.wait(1000); cy.get(".edit-header .btn-add-chapter").click(); + cy.wait(500); cy.get("#chapter-title").type("Test Chapter"); cy.get("#chapter-description").type("Test Chapter Description"); cy.button("Save").click(); diff --git a/lms/lms/doctype/lms_quiz/lms_quiz.json b/lms/lms/doctype/lms_quiz/lms_quiz.json index 39e9b282..dbbea28b 100644 --- a/lms/lms/doctype/lms_quiz/lms_quiz.json +++ b/lms/lms/doctype/lms_quiz/lms_quiz.json @@ -8,13 +8,17 @@ "engine": "InnoDB", "field_order": [ "title", + "show_answers", + "column_break_gaac", + "max_attempts", + "show_submission_history", + "section_break_sbjx", "questions", "section_break_3", - "max_attempts", - "time", - "column_break_5", "lesson", - "course" + "column_break_5", + "course", + "time" ], "fields": [ { @@ -66,11 +70,31 @@ { "fieldname": "section_break_3", "fieldtype": "Section Break" + }, + { + "default": "1", + "fieldname": "show_answers", + "fieldtype": "Check", + "label": "Show Answers" + }, + { + "fieldname": "column_break_gaac", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_sbjx", + "fieldtype": "Section Break" + }, + { + "default": "0", + "fieldname": "show_submission_history", + "fieldtype": "Check", + "label": "Show Submission History" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-06-23 12:35:25.204131", + "modified": "2023-07-04 15:26:24.457745", "modified_by": "Administrator", "module": "LMS", "name": "LMS Quiz", diff --git a/lms/lms/doctype/lms_quiz/lms_quiz.py b/lms/lms/doctype/lms_quiz/lms_quiz.py index cfe1bb45..f4c721fe 100644 --- a/lms/lms/doctype/lms_quiz/lms_quiz.py +++ b/lms/lms/doctype/lms_quiz/lms_quiz.py @@ -143,13 +143,17 @@ def quiz_summary(quiz, results): @frappe.whitelist() -def save_quiz(quiz_title, max_attempts=1, quiz=None): +def save_quiz( + quiz_title, max_attempts=1, quiz=None, show_answers=1, show_submission_history=0 +): if not can_create_courses(): return values = { "title": quiz_title, "max_attempts": max_attempts, + "show_answers": show_answers, + "show_submission_history": show_submission_history, } if quiz: @@ -232,15 +236,17 @@ def get_question_details(question): @frappe.whitelist() -def check_answer(question, type, answer): +def check_answer(question, type, answers): + answers = json.loads(answers) if type == "Choices": - return check_choice_answers(question, answer) + return check_choice_answers(question, answers) else: - return check_input_answers(question, answer) + return check_input_answers(question, answers[0]) -def check_choice_answers(question, answer): +def check_choice_answers(question, answers): fields = [] + is_correct = [] for num in range(1, 5): fields.append(f"option_{cstr(num)}") fields.append(f"is_correct_{cstr(num)}") @@ -250,9 +256,12 @@ def check_choice_answers(question, answer): ) for num in range(1, 5): - if question_details[f"option_{num}"] == answer: - return question_details[f"is_correct_{num}"] - return 0 + if question_details[f"option_{num}"] in answers: + is_correct.append(question_details[f"is_correct_{num}"]) + else: + is_correct.append(0) + + return is_correct def check_input_answers(question, answer): 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 3231fc8c..8e815ee3 100644 --- a/lms/lms/doctype/lms_quiz_question/lms_quiz_question.json +++ b/lms/lms/doctype/lms_quiz_question/lms_quiz_question.json @@ -165,6 +165,7 @@ { "fieldname": "type", "fieldtype": "Select", + "in_list_view": 1, "label": "Type", "options": "Choices\nUser Input" }, @@ -206,7 +207,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-06-09 17:09:53.740179", + "modified": "2023-07-04 16:43:49.837134", "modified_by": "Administrator", "module": "LMS", "name": "LMS Quiz Question", diff --git a/lms/plugins.py b/lms/plugins.py index 1a66d87a..69571476 100644 --- a/lms/plugins.py +++ b/lms/plugins.py @@ -114,22 +114,23 @@ def quiz_renderer(quiz_name): "LMS Quiz Submission", {"owner": frappe.session.user, "quiz": quiz_name} ) - all_submissions = frappe.get_all( - "LMS Quiz Submission", - { - "quiz": quiz.name, - "member": frappe.session.user, - }, - ["name", "score", "creation"], - order_by="creation desc", - ) + if quiz.show_submission_history: + all_submissions = frappe.get_all( + "LMS Quiz Submission", + { + "quiz": quiz.name, + "member": frappe.session.user, + }, + ["name", "score", "creation"], + order_by="creation desc", + ) return frappe.render_template( "templates/quiz/quiz.html", { "quiz": quiz, "no_of_attempts": no_of_attempts, - "all_submissions": all_submissions, + "all_submissions": all_submissions if quiz.show_submission_history else None, "hide_quiz": False, }, ) diff --git a/lms/public/css/style.css b/lms/public/css/style.css index 5fd470e0..7037a6f6 100644 --- a/lms/public/css/style.css +++ b/lms/public/css/style.css @@ -242,7 +242,7 @@ textarea.field-input { input[type=checkbox] { appearance: auto; position: relative; - width: var(--checkbox-size)!important; + width: var(--checkbox-size) !important; height: var(--checkbox-size); margin-right: var(--checkbox-right-margin)!important; background-repeat: no-repeat; @@ -252,8 +252,6 @@ input[type=checkbox] { box-shadow: 0 1px 2px #0000001a; border-radius: 4px; -webkit-appearance: none; - -moz-appearance: none; - appearance: none; -webkit-print-color-adjust: exact; } diff --git a/lms/templates/quiz/quiz.html b/lms/templates/quiz/quiz.html index 4ae2b242..41e9cf7f 100644 --- a/lms/templates/quiz/quiz.html +++ b/lms/templates/quiz/quiz.html @@ -24,7 +24,8 @@
-
+
{{ quiz.title }}
@@ -105,10 +106,13 @@
{% endif %} + {% if quiz.show_answers %} -
{% endif %} -{% if all_submissions | length %} +{% if quiz.show_submission_history and all_submissions | length %}
{{ _("All Submissions") }} @@ -131,9 +135,9 @@
-
{{ _("No.") }}
-
{{ _("Date") }}
+
{{ _("No.") }}
{{ _("Score") }}
+
{{ _("Date") }}
@@ -141,9 +145,11 @@ {% for submission in all_submissions %}
-
{{ loop.index }}
-
{{ frappe.utils.format_datetime(submission.creation, "medium") }}
+
{{ loop.index }}
{{ submission.score }}
+
+ {{ frappe.utils.pretty_date(submission.creation) }} +
{% endfor %} diff --git a/lms/templates/quiz/quiz.js b/lms/templates/quiz/quiz.js index 37fd563e..36c2ae08 100644 --- a/lms/templates/quiz/quiz.js +++ b/lms/templates/quiz/quiz.js @@ -1,8 +1,9 @@ frappe.ready(() => { + const self = this; this.quiz_submitted = false; this.answer = []; this.is_correct = []; - const self = this; + this.show_answers = $("#quiz-title").data("show-answers"); localStorage.removeItem($("#quiz-title").data("name")); $(".btn-start-quiz").click((e) => { @@ -21,8 +22,11 @@ frappe.ready(() => { $("#summary").click((e) => { e.preventDefault(); - add_to_local_storage(); - quiz_summary(e); + if (!this.show_answers) check_answer(); + + setTimeout(() => { + quiz_summary(e); + }, 500); }); $("#check").click((e) => { @@ -32,7 +36,8 @@ frappe.ready(() => { $("#next").click((e) => { e.preventDefault(); - add_to_local_storage(); + if (!this.show_answers) check_answer(); + mark_active_question(e); }); @@ -42,21 +47,30 @@ frappe.ready(() => { }); const mark_active_question = (e = undefined) => { - $(".timer").addClass("hide"); - calculate_and_display_time(100); - $(".timer").removeClass("hide"); - + let total_questions = $(".question").length; let current_index = $(".active-question").attr("data-qt-index") || 0; let next_index = parseInt(current_index) + 1; + if (this.show_answers) { + $("#next").addClass("hide"); + } else if (!this.show_answers && next_index == total_questions) { + $("#next").addClass("hide"); + $("#summary").removeClass("hide"); + } + $(".question").addClass("hide").removeClass("active-question"); $(`.question[data-qt-index='${next_index}']`) .removeClass("hide") .addClass("active-question"); + $(".current-question").text(`${next_index}`); $("#check").removeClass("hide").attr("disabled", true); - $("#next").addClass("hide"); + $("#next").attr("disabled", true); $(".explanation").addClass("hide"); + + $(".timer").addClass("hide"); + calculate_and_display_time(100); + $(".timer").removeClass("hide"); initialize_timer(); }; @@ -95,6 +109,7 @@ const initialize_timer = () => { const enable_check = (e) => { if ($(".option:checked").length || $(".possibility").val().trim()) { $("#check").removeAttr("disabled"); + $("#next").removeAttr("disabled"); $(".custom-checkbox").removeClass("active-option"); $(".option:checked") .closest(".custom-checkbox") @@ -145,8 +160,10 @@ const try_quiz_again = (e) => { const check_answer = (e = undefined) => { e && e.preventDefault(); - let answer = $(".active-question textarea"); + let total_questions = $(".question").length; + let current_index = $(".active-question").attr("data-qt-index"); + if (answer.length && !answer.val().trim()) { frappe.throw(__("Please enter your answer")); } @@ -154,73 +171,77 @@ const check_answer = (e = undefined) => { clearInterval(self.timer); $(".timer").addClass("hide"); - let total_questions = $(".question").length; - let current_index = $(".active-question").attr("data-qt-index"); - $(".explanation").removeClass("hide"); $("#check").addClass("hide"); if (current_index == total_questions) { $("#summary").removeClass("hide"); - } else { + } else if (this.show_answers) { $("#next").removeClass("hide"); } parse_options(); }; const parse_options = () => { + let user_answers = []; + let element; let type = $(".active-question").data("type"); if (type == "Choices") { $(".active-question input").each((i, element) => { - is_answer_correct(type, element); + if ($(element).prop("checked")) { + user_answers.push(decodeURIComponent($(element).val())); + } }); + element = $(".active-question input"); } else { - is_answer_correct(type, $(".active-question textarea")); + user_answers.push($(".active-question textarea").val()); + element = $(".active-question textarea"); } + + is_answer_correct(type, user_answers, element); }; -const is_answer_correct = (type, element) => { - let answer = decodeURIComponent($(element).val()); - +const is_answer_correct = (type, user_answers, element) => { frappe.call({ async: false, method: "lms.lms.doctype.lms_quiz.lms_quiz.check_answer", args: { question: $(".active-question").data("name"), type: type, - answer: answer, + answers: user_answers, }, callback: (data) => { type == "Choices" ? parse_choices(element, data.message) : parse_possible_answers(element, data.message); + add_to_local_storage(); }, }); }; -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_choices = (element, is_correct) => { + element.each((i, elem) => { + if ($(elem).prop("checked")) { + self.answer.push(decodeURIComponent($(elem).val())); + self.is_correct.push(is_correct[i]); + if (this.show_answers) + is_correct[i] + ? add_icon(elem, "check") + : add_icon(elem, "wrong"); + } else { + add_icon(elem, "minus-circle"); + } + }); }; const parse_possible_answers = (element, correct) => { self.answer.push(decodeURIComponent($(element).val())); - if (correct) { - self.is_correct.push(1); - show_indicator("success", element); - } else { - self.is_correct.push(0); - show_indicator("failure", element); - } + self.is_correct.push(correct); + if (this.show_answers) + correct + ? show_indicator("success", element) + : show_indicator("failure", element); }; const show_indicator = (class_name, element) => { @@ -253,7 +274,7 @@ const add_to_local_storage = () => { let quiz_stored = JSON.parse(localStorage.getItem(quiz_name)); let quiz_obj = { - question_index: current_index, + question_index: current_index - 1, answer: self.answer.join(), is_correct: self.is_correct, }; diff --git a/lms/www/batch/quiz.html b/lms/www/batch/quiz.html index 25078a62..ad302f78 100644 --- a/lms/www/batch/quiz.html +++ b/lms/www/batch/quiz.html @@ -78,7 +78,7 @@
-
+
{{ _("Title") }}
@@ -102,6 +102,18 @@
+ +
+ {% set show_answers = quiz.show_answers or not quiz.name %} + + +
{% endmacro %} diff --git a/lms/www/batch/quiz.js b/lms/www/batch/quiz.js index 29ba3519..676cf1c0 100644 --- a/lms/www/batch/quiz.js +++ b/lms/www/batch/quiz.js @@ -119,12 +119,19 @@ const edit_question = (e) => { }; const save_quiz = (values) => { + validate_mandatory(); frappe.call({ method: "lms.lms.doctype.lms_quiz.lms_quiz.save_quiz", args: { quiz_title: values.quiz_title, max_attempts: values.max_attempts, quiz: $("#quiz-form").data("name") || "", + show_answers: $("#show-answers").is(":checked") ? 1 : 0, + show_submission_history: $("#show-submission-history").is( + ":checked" + ) + ? 1 + : 0, }, callback: (data) => { frappe.show_alert({ @@ -138,6 +145,17 @@ const save_quiz = (values) => { }); }; +const validate_mandatory = () => { + if (!$("#quiz-title").val()) { + let error = $("p") + .addClass("error-message") + .text(__("Please enter a Quiz Title")); + $(error).insertAfter("#quiz-title"); + $("#quiz-title").focus(); + throw "Title is mandatory"; + } +}; + const save_question = (values) => { frappe.call({ method: "lms.lms.doctype.lms_quiz.lms_quiz.save_question", diff --git a/lms/www/batch/quiz.py b/lms/www/batch/quiz.py index e46292bc..f88aaef5 100644 --- a/lms/www/batch/quiz.py +++ b/lms/www/batch/quiz.py @@ -21,7 +21,10 @@ def get_context(context): fields_arr = ["name", "question", "type"] context.quiz = frappe.db.get_value( - "LMS Quiz", quizname, ["title", "name", "max_attempts"], as_dict=1 + "LMS Quiz", + quizname, + ["title", "name", "max_attempts", "show_answers", "show_submission_history"], + as_dict=1, ) context.quiz.questions = frappe.get_all( "LMS Quiz Question", {"parent": quizname}, fields_arr, order_by="idx"