feat: quiz marks and passing percentage
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user