feat: quiz marks and passing percentage

This commit is contained in:
Jannat Patel
2023-10-17 20:06:04 +05:30
parent 12bec14c92
commit 0111ff9c99
15 changed files with 210 additions and 47 deletions

View File

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

View File

@@ -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",

View File

@@ -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():

View File

@@ -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",

View File

@@ -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",

View File

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

View File

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

View File

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

View File

@@ -6,6 +6,15 @@
{{ _("This quiz consists of {0} questions.").format(quiz.questions | length) }}
</li>
{% if quiz.passing_percentage %}
<li>
{{ _("You will have to get {0}% correct answers in order to pass the quiz.").format(quiz.passing_percentage) }}
</li>
<li>
{{ _("Without passing the quiz you won't be able to complete the lesson.") }}
</li>
{% endif %}
{% if quiz.max_attempts %}
{% set suffix = "times" if quiz.max_attempts > 1 else "time" %}
<li>
@@ -18,8 +27,7 @@
{{ _("The quiz has a time limit. For each question you will be given {0} seconds.").format(quiz.time) }}
</li>
{% endif %}
</ul>
</ul>
<div id="start-banner" class="common-card-style column-card align-items-center">
@@ -50,6 +58,9 @@
<div class="question hide" data-name="{{ question.name }}" data-type="{{ question.type }}"
data-multi="{{ question.multiple }}" data-qt-index="{{ loop.index }}">
<div>
<div class="pull-right font-weight-bold">
{{ question.marks }} {{ _("Marks") }}
</div>
<div class="question-number">
{{ _("Question ") }}{{ loop.index }}: {{ instruction }}
</div>

View File

@@ -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(
`<div class="summary bold-heading text-center">
${__("Your score is")} ${data.message.score}
${__("out of")} ${total_questions}
${__("out of")} ${data.message.score_out_of}
</div>`
);
$("#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();
}
},

View File

@@ -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"]

View File

@@ -429,9 +429,9 @@ class Quiz {
}
render_quiz(quiz) {
return `<div class="common-card-style p-2 my-2 bold-heading">
return `<a class="common-card-style p-20 my-2 justify-center bold-heading" target="_blank" href=/quizzes/${quiz}>
Quiz: ${quiz}
</div>`;
</a>`;
}
validate(savedData) {

View File

@@ -21,11 +21,12 @@
<div class="field-label mb-1">
{{ _("Questions") }}
</div>
<div class="common-card-style column-card px-3 py-0">
<div class="questions-table"></div>
<!-- <div class="common-card-style column-card px-3 py-0">
{% for question in quiz.questions %}
{{ Question(question, loop.index) }}
{% endfor %}
</div>
</div> -->
<button class="btn btn-secondary btn-sm btn-add-question mt-4">
{{ _("Add Question") }}
</button>
@@ -109,7 +110,7 @@
<input type="checkbox" id="show-answers" {% if show_answers %} checked {% endif %}>
{{ _("Show Answers") }}
</label>
<label for="upcoming" class="vertically-center mb-0 ml-20">
<label for="show-submission-history" class="vertically-center mb-0 ml-20">
<input type="checkbox" id="show-submission-history" {% if quiz.show_submission_history %} checked {% endif %}>
{{ _("Show Submission History") }}
</label>
@@ -151,5 +152,9 @@
{%- block script %}
{{ super() }}
{{ include_script('controls.bundle.js') }}
{% if has_course_instructor_role() or has_course_moderator_role() %}
<script>
const quiz_questions = {{ quiz.questions }}
</script>
{% endif %}
{% endblock %}

View File

@@ -1,4 +1,10 @@
frappe.ready(() => {
if ($(".questions-table").length) {
frappe.require("controls.bundle.js", () => {
create_questions_table();
});
}
$(".btn-save-quiz").click((e) => {
save_quiz({
quiz_title: $("#quiz-title").val(),
@@ -177,3 +183,52 @@ const save_question = (values) => {
},
});
};
const create_questions_table = () => {
this.table = new frappe.ui.FieldGroup({
fields: [
{
fieldname: "questions",
fieldtype: "Table",
in_place_edit: 1,
fields: [
{
fieldname: "question",
fieldtype: "Link",
label: "Question",
options: "LMS Question",
in_list_view: 1,
only_select: 1,
},
{
fieldname: "marks",
fieldtype: "Int",
label: "Marks",
in_list_view: 1,
},
],
},
],
body: $(".questions-table").get(0),
});
this.table.make();
$(".questions-table .form-section:last").removeClass("empty-section");
$(".questions-table .frappe-control").removeClass("hide-control");
$(".questions-table .form-column").addClass("p-0");
add_question_rows();
};
const add_question_rows = () => {
console.log("add rows");
/* let questions_rows = []
quiz_questions.forEach((question, idx) => {
let row = {};
this.table.fields_dict["questions"].grid.add_new_row();
row["question"] = question.question;
row["marks"] = question.marks;
questions_rows.push(row)
});
console.log(questions_rows) */
this.table.set_value("questions", quiz_questions);
this.table.refresh();
};

View File

@@ -18,7 +18,6 @@ def get_context(context):
if quizname == "new-quiz":
context.quiz = frappe._dict()
else:
fields_arr = ["name", "question", "type"]
context.quiz = frappe.db.get_value(
"LMS Quiz",
@@ -26,6 +25,8 @@ def get_context(context):
["title", "name", "max_attempts", "show_answers", "show_submission_history"],
as_dict=1,
)
fields_arr = ["name", "question", "marks"]
context.quiz.questions = frappe.get_all(
"LMS Quiz Question", {"parent": quizname}, fields_arr, order_by="idx"
)