feat: quiz question from UI

This commit is contained in:
Jannat Patel
2023-10-18 23:21:49 +05:30
parent 0111ff9c99
commit 68b2dd6147
8 changed files with 178 additions and 73 deletions

View File

@@ -194,7 +194,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2023-10-16 11:39:39.757008",
"modified": "2023-10-18 21:58:42.653317",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Question",
@@ -212,6 +212,30 @@
"role": "System Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Moderator",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Course Creator",
"share": 1,
"write": 1
}
],
"sort_field": "modified",

View File

@@ -4,6 +4,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
from lms.lms.utils import has_course_instructor_role, has_course_moderator_role
class LMSQuestion(Document):
@@ -46,3 +47,18 @@ def get_correct_options(question):
"is_correct_4",
]
return list(filter(lambda x: question.get(x) == 1, correct_option_fields))
@frappe.whitelist()
def get_question_details(question):
if not has_course_instructor_role() or not has_course_moderator_role():
return
fields = ["question", "type", "name"]
for i in range(1, 5):
fields.append(f"option_{i}")
fields.append(f"is_correct_{i}")
fields.append(f"explanation_{i}")
fields.append(f"possibility_{i}")
return frappe.db.get_value("LMS Question", question, fields, as_dict=1)

View File

@@ -121,7 +121,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2023-10-17 15:25:25.830927",
"modified": "2023-10-18 22:50:58.252350",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Quiz",
@@ -150,6 +150,18 @@
"role": "Moderator",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Course Creator",
"share": 1,
"write": 1
}
],
"show_title_field_in_link": 1,

View File

@@ -114,13 +114,19 @@ def quiz_summary(quiz, results):
@frappe.whitelist()
def save_quiz(
quiz_title, max_attempts=1, quiz=None, show_answers=1, show_submission_history=0
quiz_title,
passing_percentage,
max_attempts=0,
quiz=None,
show_answers=1,
show_submission_history=0,
):
if not has_course_moderator_role() or not has_course_instructor_role():
return
values = {
"title": quiz_title,
"passing_percentage": passing_percentage,
"max_attempts": max_attempts,
"show_answers": show_answers,
"show_submission_history": show_submission_history,
@@ -132,38 +138,27 @@ def save_quiz(
else:
doc = frappe.new_doc("LMS Quiz")
doc.update(values)
doc.save(ignore_permissions=True)
doc.save()
return doc.name
@frappe.whitelist()
def save_question(quiz, values, index):
values = frappe._dict(json.loads(values))
for value in values:
validate_correct_answers(value)
validate_correct_answers(values)
if values.get("name"):
doc = frappe.get_doc("LMS Quiz Question", values.get("name"))
doc = frappe.get_doc("LMS Question", values.get("name"))
else:
doc = frappe.new_doc("LMS Quiz Question")
doc = frappe.new_doc("LMS Question")
doc.update(
{
"question": values["question"],
"question": values.question,
"type": values["type"],
}
)
if not values.get("name"):
doc.update(
{
"parent": quiz,
"parenttype": "LMS Quiz",
"parentfield": "questions",
"idx": index,
}
)
for num in range(1, 5):
if values.get(f"option_{num}"):
doc.update(
@@ -187,9 +182,9 @@ def save_question(quiz, values, index):
}
)
doc.save(ignore_permissions=True)
doc.save()
return quiz
return doc.name
@frappe.whitelist()

View File

@@ -2474,4 +2474,8 @@ select {
.modal-body .ql-container {
max-height: unset !important;
}
.questions-table .row-index {
display: none;
}

View File

@@ -18,18 +18,10 @@
{{ QuizDetails(quiz) }}
{% if quiz.questions %}
<div class="field-group">
<div class="field-label mb-1">
{{ _("Questions") }}
</div>
<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> -->
<button class="btn btn-secondary btn-sm btn-add-question mt-4">
{{ _("Add Question") }}
</button>
<!-- <button class="btn btn-secondary btn-sm btn-add-question mt-4">
{{ _("New Question") }}
</button> -->
</div>
{% endif %}
@@ -60,11 +52,6 @@
</div>
<div class="align-self-center">
{% if quiz.name %}
<button class="btn btn-secondary btn-sm btn-add-question mr-2">
{{ _("Add Question") }}
</button>
{% endif %}
<button class="btn btn-primary btn-sm btn-save-quiz">
{{ _("Save") }}
</button>
@@ -99,11 +86,23 @@
{{ _("Enter the maximum number of times a user can attempt this quiz") }}
</div>
<div>
{% set max_attempts = quiz.max_attempts if quiz.name else 1 %}
{% set max_attempts = quiz.max_attempts if quiz.name else 0 %}
<input type="number" class="field-input" id="max-attempts" value="{{ max_attempts }}">
</div>
</div>
<div class="field-group">
<div class="field-label reqd">
{{ _("Passing Percentage") }}
</div>
<div class="field-description">
{{ _("Minimum percentage required to pass this quiz.") }}
</div>
<div>
<input type="number" class="field-input" id="passing-percentage" value="{{ quiz.passing_percentage }}">
</div>
</div>
<div class="field-group vertically-center">
{% set show_answers = quiz.show_answers or not quiz.name %}
<label for="show-answers" class="vertically-center mb-0">

View File

@@ -6,18 +6,20 @@ frappe.ready(() => {
}
$(".btn-save-quiz").click((e) => {
save_quiz({
quiz_title: $("#quiz-title").val(),
max_attempts: $("#max-attempts").val(),
});
save_quiz();
});
$(".question-row").click((e) => {
edit_question(e);
});
$(".btn-add-question").click((e) => {
/* $(".btn-add-question").click((e) => {
show_question_modal();
}); */
$(document).on("click", ".questions-table .link-btn", (e) => {
e.preventDefault();
fetch_question_data(e);
});
});
@@ -37,6 +39,8 @@ const show_question_modal = (values = {}) => {
};
const get_question_fields = (values = {}) => {
if (!values.question) values = {};
let dialog_fields = [
{
fieldtype: "Text Editor",
@@ -72,6 +76,7 @@ const get_question_fields = (values = {}) => {
if (num <= 2) option.mandatory_depends_on = "eval:doc.type=='Choices'";
dialog_fields.push(option);
console.log(dialog_fields);
dialog_fields.push({
fieldtype: "Data",
@@ -129,8 +134,9 @@ const save_quiz = (values) => {
frappe.call({
method: "lms.lms.doctype.lms_quiz.lms_quiz.save_quiz",
args: {
quiz_title: values.quiz_title,
max_attempts: values.max_attempts,
quiz_title: $("#quiz-title").val(),
max_attempts: $("#max-attempts").val(),
passing_percentage: $("#passing-percentage").val(),
quiz: $("#quiz-form").data("name") || "",
show_answers: $("#show-answers").is(":checked") ? 1 : 0,
show_submission_history: $("#show-submission-history").is(
@@ -152,13 +158,27 @@ 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";
let fields = ["#quiz-title", "#passing-percentage"];
fields.forEach((field, idx) => {
if (!$(field).val()) {
let error = $("p")
.addClass("error-message")
.text(__("Please enter a value"));
$(error).insertAfter(field);
scroll_to_element($(field));
throw "This field is mandatory";
}
});
};
const scroll_to_element = (element) => {
if ($(element).length) {
$([document.documentElement, document.body]).animate(
{
scrollTop: $(element).offset().top - 100,
},
1000
);
}
};
@@ -173,13 +193,21 @@ const save_question = (values) => {
callback: (data) => {
if (data.message) this.question_dialog.hide();
frappe.show_alert({
message: __("Saved"),
indicator: "green",
});
setTimeout(() => {
window.location.reload();
}, 1000);
if (values.name) {
frappe.show_alert({
message: __("Saved"),
indicator: "green",
});
setTimeout(() => {
window.location.reload();
}, 1000);
} else {
let details = {
question: data.message,
};
index = this.table.get_value("questions").length;
add_question_row(details, index);
}
},
});
};
@@ -191,6 +219,7 @@ const create_questions_table = () => {
fieldname: "questions",
fieldtype: "Table",
in_place_edit: 1,
label: __("Questions"),
fields: [
{
fieldname: "question",
@@ -215,20 +244,39 @@ const create_questions_table = () => {
$(".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();
quiz_questions.forEach((question, idx) => {
add_question_row(question, idx);
});
this.table.fields_dict["questions"].grid.add_custom_button(
"New Question",
show_question_modal,
"bottom"
);
};
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);
const add_question_row = (question, idx) => {
this.table.fields_dict["questions"].grid.add_new_row();
this.table.get_value("questions")[idx] = {
question: question.question,
marks: question.marks,
};
this.table.refresh();
};
const fetch_question_data = (e) => {
let question_name = $(e.currentTarget)
.find(".btn-open")
.attr("href")
.split("/")[3];
frappe.call({
method: "lms.lms.doctype.lms_question.lms_question.get_question_details",
args: {
question: question_name,
},
callback: (data) => {
show_question_modal(data.message);
},
});
};

View File

@@ -22,7 +22,14 @@ def get_context(context):
context.quiz = frappe.db.get_value(
"LMS Quiz",
quizname,
["title", "name", "max_attempts", "show_answers", "show_submission_history"],
[
"title",
"name",
"max_attempts",
"passing_percentage",
"show_answers",
"show_submission_history",
],
as_dict=1,
)