From 0111ff9c99c1adb2d903b2ea366056bcf9ee8859 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Tue, 17 Oct 2023 20:06:04 +0530 Subject: [PATCH] feat: quiz marks and passing percentage --- .../doctype/course_lesson/course_lesson.py | 8 ++- lms/lms/doctype/lms_quiz/lms_quiz.json | 12 ++-- lms/lms/doctype/lms_quiz/lms_quiz.py | 47 ++++++++++++---- .../lms_quiz_result/lms_quiz_result.json | 14 ++++- .../lms_quiz_submission.json | 39 +++++++++++-- .../lms_quiz_submission.py | 7 ++- lms/plugins.py | 17 +++++- lms/public/css/style.css | 13 +++-- lms/templates/quiz/quiz.html | 15 ++++- lms/templates/quiz/quiz.js | 8 ++- .../assignment_submission.py | 2 +- lms/www/batch/edit.js | 4 +- lms/www/batch/quiz.html | 13 +++-- lms/www/batch/quiz.js | 55 +++++++++++++++++++ lms/www/batch/quiz.py | 3 +- 15 files changed, 210 insertions(+), 47 deletions(-) diff --git a/lms/lms/doctype/course_lesson/course_lesson.py b/lms/lms/doctype/course_lesson/course_lesson.py index b2f2475f..d7b53fd4 100644 --- a/lms/lms/doctype/course_lesson/course_lesson.py +++ b/lms/lms/doctype/course_lesson/course_lesson.py @@ -99,8 +99,14 @@ def save_progress(lesson, course, status): quizzes = [value for name, value in macros if name == "Quiz"] for quiz in quizzes: + passing_percentage = frappe.db.get_value("LMS Quiz", quiz, "passing_percentage") if not frappe.db.exists( - "LMS Quiz Submission", {"quiz": quiz, "owner": frappe.session.user} + "LMS Quiz Submission", + { + "quiz": quiz, + "owner": frappe.session.user, + "percentage": [">=", passing_percentage], + }, ): return 0 diff --git a/lms/lms/doctype/lms_quiz/lms_quiz.json b/lms/lms/doctype/lms_quiz/lms_quiz.json index c5d97764..202667f5 100644 --- a/lms/lms/doctype/lms_quiz/lms_quiz.json +++ b/lms/lms/doctype/lms_quiz/lms_quiz.json @@ -47,7 +47,7 @@ "read_only": 1 }, { - "default": "1", + "default": "0", "fieldname": "max_attempts", "fieldtype": "Int", "label": "Max Attempts" @@ -102,7 +102,9 @@ { "fieldname": "passing_percentage", "fieldtype": "Int", - "label": "Passing Percentage" + "label": "Passing Percentage", + "non_negative": 1, + "reqd": 1 }, { "fieldname": "column_break_rocd", @@ -112,12 +114,14 @@ "fieldname": "total_marks", "fieldtype": "Int", "label": "Total Marks", - "read_only": 1 + "non_negative": 1, + "read_only": 1, + "reqd": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-10-16 17:21:33.932981", + "modified": "2023-10-17 15:25:25.830927", "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 0f2ef9db..fa1fe0e8 100644 --- a/lms/lms/doctype/lms_quiz/lms_quiz.py +++ b/lms/lms/doctype/lms_quiz/lms_quiz.py @@ -17,6 +17,7 @@ from lms.lms.utils import ( class LMSQuiz(Document): def validate(self): self.validate_duplicate_questions() + self.set_total_marks() def validate_duplicate_questions(self): questions = [row.question for row in self.questions] @@ -26,6 +27,13 @@ class LMSQuiz(Document): _("Rows {0} have the duplicate questions.").format(frappe.bold(comma_and(rows))) ) + def set_total_marks(self): + marks = 0 + for question in self.questions: + marks += question.marks + + self.total_marks = marks + def autoname(self): if not self.name: self.name = generate_slug(self.title, "LMS Quiz") @@ -55,27 +63,42 @@ def quiz_summary(quiz, results): for result in results: correct = result["is_correct"][0] - question_name = frappe.db.get_value( - "LMS Quiz Question", - {"parent": quiz, "idx": result["question_index"] + 1}, - ["question"], - ) - result["question_name"] = question_name - result["question"] = frappe.db.get_value("LMS Question", question_name, "question") - for point in result["is_correct"]: correct = correct and point result["is_correct"] = correct - score += correct + + question_details = frappe.db.get_value( + "LMS Quiz Question", + {"parent": quiz, "idx": result["question_index"] + 1}, + ["question", "marks"], + as_dict=1, + ) + + result["question_name"] = question_details.question + result["question"] = frappe.db.get_value( + "LMS Question", question_details.question, "question" + ) + marks = question_details.marks if correct else 0 + + result["marks"] = marks + score += marks + del result["question_index"] + quiz_details = frappe.db.get_value( + "LMS Quiz", quiz, ["total_marks", "passing_percentage"], as_dict=1 + ) + score_out_of = quiz_details.total_marks + percentage = (score / score_out_of) * 100 + submission = frappe.get_doc( { "doctype": "LMS Quiz Submission", "quiz": quiz, "result": results, "score": score, + "score_out_of": score_out_of, "member": frappe.session.user, } ) @@ -83,7 +106,9 @@ def quiz_summary(quiz, results): return { "score": score, + "score_out_of": score_out_of, "submission": submission.name, + "pass": percentage == quiz_details.passing_percentage, } @@ -213,9 +238,7 @@ def check_input_answers(question, answer): 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 - ) + question_details = frappe.db.get_value("LMS Question", question, fields, as_dict=1) for num in range(1, 5): current_possibility = question_details[f"possibility_{num}"] if current_possibility and current_possibility.lower() == answer.lower(): diff --git a/lms/lms/doctype/lms_quiz_result/lms_quiz_result.json b/lms/lms/doctype/lms_quiz_result/lms_quiz_result.json index 72aeaef6..7c8fcfac 100644 --- a/lms/lms/doctype/lms_quiz_result/lms_quiz_result.json +++ b/lms/lms/doctype/lms_quiz_result/lms_quiz_result.json @@ -8,9 +8,10 @@ "question", "section_break_fztv", "question_name", - "is_correct", + "answer", "column_break_flus", - "answer" + "marks", + "is_correct" ], "fields": [ { @@ -48,12 +49,19 @@ { "fieldname": "column_break_flus", "fieldtype": "Column Break" + }, + { + "fieldname": "marks", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Marks", + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-10-16 15:25:03.380843", + "modified": "2023-10-17 11:55:25.641214", "modified_by": "Administrator", "module": "LMS", "name": "LMS Quiz Result", diff --git a/lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.json b/lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.json index 80ca9ffd..735d87a0 100644 --- a/lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.json +++ b/lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.json @@ -6,11 +6,15 @@ "engine": "InnoDB", "field_order": [ "quiz", - "score", "course", "column_break_3", "member", "member_name", + "section_break_dkpn", + "score", + "score_out_of", + "column_break_gkip", + "percentage", "section_break_6", "result" ], @@ -31,9 +35,11 @@ }, { "fieldname": "score", - "fieldtype": "Data", + "fieldtype": "Int", "in_list_view": 1, - "label": "Score" + "label": "Score", + "read_only": 1, + "reqd": 1 }, { "fieldname": "member", @@ -65,12 +71,37 @@ "label": "Course", "options": "LMS Course", "read_only": 1 + }, + { + "fetch_from": "quiz.total_marks", + "fieldname": "score_out_of", + "fieldtype": "Int", + "label": "Score Out Of", + "non_negative": 1, + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "section_break_dkpn", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_gkip", + "fieldtype": "Column Break" + }, + { + "fieldname": "percentage", + "fieldtype": "Int", + "label": "Percentage", + "non_negative": 1, + "read_only": 1, + "reqd": 1 } ], "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2022-11-15 15:27:07.770945", + "modified": "2023-10-17 13:07:27.979974", "modified_by": "Administrator", "module": "LMS", "name": "LMS Quiz Submission", diff --git a/lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.py b/lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.py index d8eeba65..e481d57e 100644 --- a/lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.py +++ b/lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.py @@ -6,4 +6,9 @@ from frappe.model.document import Document class LMSQuizSubmission(Document): - pass + def before_insert(self): + self.set_percentage() + + def set_percentage(self): + if self.score and self.score_out_of: + self.percentage = (self.score / self.score_out_of) * 100 diff --git a/lms/plugins.py b/lms/plugins.py index 00569484..e3241244 100644 --- a/lms/plugins.py +++ b/lms/plugins.py @@ -112,7 +112,14 @@ def quiz_renderer(quiz_name): quiz = frappe.db.get_value( "LMS Quiz", quiz_name, - ["name", "title", "max_attempts", "show_answers", "show_submission_history"], + [ + "name", + "title", + "max_attempts", + "show_answers", + "show_submission_history", + "passing_percentage", + ], as_dict=True, ) quiz.questions = [] @@ -124,11 +131,15 @@ def quiz_renderer(quiz_name): fields.append(f"possibility_{num}") questions = frappe.get_all( - "LMS Quiz Question", {"parent": quiz.name}, pluck="question", order_by="idx" + "LMS Quiz Question", + filters={"parent": quiz.name}, + fields=["question", "marks"], + order_by="idx", ) for question in questions: - details = frappe.db.get_value("LMS Question", question, fields, as_dict=1) + details = frappe.db.get_value("LMS Question", question.question, fields, as_dict=1) + details["marks"] = question.marks quiz.questions.append(details) no_of_attempts = frappe.db.count( diff --git a/lms/public/css/style.css b/lms/public/css/style.css index c62f3de4..aa02192b 100644 --- a/lms/public/css/style.css +++ b/lms/public/css/style.css @@ -785,12 +785,13 @@ input[type=checkbox] { } .breadcrumb { - display: flex; - align-items: center; - font-size: var(--text-base); - line-height: 20px; - color: var(--gray-900); - padding: 0; + display: flex; + align-items: center; + font-size: var(--text-base); + line-height: 20px; + color: var(--gray-900); + padding: 0; + border-radius: 0; } .course-details-outline { diff --git a/lms/templates/quiz/quiz.html b/lms/templates/quiz/quiz.html index 082f7d38..84caaf50 100644 --- a/lms/templates/quiz/quiz.html +++ b/lms/templates/quiz/quiz.html @@ -6,6 +6,15 @@ {{ _("This quiz consists of {0} questions.").format(quiz.questions | length) }} + {% if quiz.passing_percentage %} +
  • + {{ _("You will have to get {0}% correct answers in order to pass the quiz.").format(quiz.passing_percentage) }} +
  • +
  • + {{ _("Without passing the quiz you won't be able to complete the lesson.") }} +
  • + {% endif %} + {% if quiz.max_attempts %} {% set suffix = "times" if quiz.max_attempts > 1 else "time" %}
  • @@ -18,8 +27,7 @@ {{ _("The quiz has a time limit. For each question you will be given {0} seconds.").format(quiz.time) }}
  • {% endif %} - - +
    @@ -50,6 +58,9 @@
    +
    + {{ question.marks }} {{ _("Marks") }} +
    {{ _("Question ") }}{{ loop.index }}: {{ instruction }}
    diff --git a/lms/templates/quiz/quiz.js b/lms/templates/quiz/quiz.js index 1de0b05a..ed37a030 100644 --- a/lms/templates/quiz/quiz.js +++ b/lms/templates/quiz/quiz.js @@ -120,7 +120,6 @@ const enable_check = (e) => { const quiz_summary = (e = undefined) => { e && e.preventDefault(); let quiz_name = $("#quiz-title").data("name"); - let total_questions = $(".question").length; let self = this; frappe.call({ @@ -136,13 +135,16 @@ const quiz_summary = (e = undefined) => { $("#quiz-form").prepend( `
    ${__("Your score is")} ${data.message.score} - ${__("out of")} ${total_questions} + ${__("out of")} ${data.message.score_out_of}
    ` ); $("#try-again").attr("data-submission", data.message.submission); $("#try-again").removeClass("hide"); self.quiz_submitted = true; - if (this.hasOwnProperty("marked_as_complete")) { + if ( + this.hasOwnProperty("marked_as_complete") && + data.message.pass + ) { mark_progress(); } }, diff --git a/lms/www/assignment_submission/assignment_submission.py b/lms/www/assignment_submission/assignment_submission.py index 93ecb2f7..d1631a88 100644 --- a/lms/www/assignment_submission/assignment_submission.py +++ b/lms/www/assignment_submission/assignment_submission.py @@ -7,7 +7,7 @@ def get_context(context): context.no_cache = 1 if frappe.session.user == "Guest": - raise frappe.PermissionError(_("You don't have permission to access this page.")) + raise frappe.PermissionError(_("Please login to submit the assignment.")) context.is_moderator = has_course_moderator_role() submission = frappe.form_dict["submission"] diff --git a/lms/www/batch/edit.js b/lms/www/batch/edit.js index 65318ccc..13010356 100644 --- a/lms/www/batch/edit.js +++ b/lms/www/batch/edit.js @@ -429,9 +429,9 @@ class Quiz { } render_quiz(quiz) { - return ``; + `; } validate(savedData) { diff --git a/lms/www/batch/quiz.html b/lms/www/batch/quiz.html index ad302f78..6c829989 100644 --- a/lms/www/batch/quiz.html +++ b/lms/www/batch/quiz.html @@ -21,11 +21,12 @@
    {{ _("Questions") }}
    -
    +
    + @@ -109,7 +110,7 @@ {{ _("Show Answers") }} -