Merge branch 'main' into 644
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
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ const set_timetable = (frm) => {
|
||||
"start_time",
|
||||
"end_time",
|
||||
"duration",
|
||||
"milestone",
|
||||
],
|
||||
filters: {
|
||||
parent: frm.doc.timetable_template,
|
||||
@@ -82,6 +83,7 @@ const add_timetable_rows = (frm, timetable) => {
|
||||
.format("HH:mm")
|
||||
: null;
|
||||
child.duration = row.duration;
|
||||
child.milestone = row.milestone;
|
||||
});
|
||||
frm.refresh_field("timetable");
|
||||
|
||||
|
||||
@@ -12,9 +12,6 @@ from frappe.utils import (
|
||||
cint,
|
||||
format_date,
|
||||
format_datetime,
|
||||
add_to_date,
|
||||
getdate,
|
||||
get_datetime,
|
||||
)
|
||||
from lms.lms.utils import get_lessons, get_lesson_index, get_lesson_url
|
||||
from lms.www.utils import get_quiz_details, get_assignment_details
|
||||
@@ -325,7 +322,17 @@ def get_batch_timetable(batch):
|
||||
timetable = frappe.get_all(
|
||||
"LMS Batch Timetable",
|
||||
filters={"parent": batch},
|
||||
fields=["reference_doctype", "reference_docname", "date", "start_time", "end_time"],
|
||||
fields=[
|
||||
"reference_doctype",
|
||||
"reference_docname",
|
||||
"date",
|
||||
"start_time",
|
||||
"end_time",
|
||||
"milestone",
|
||||
"name",
|
||||
"idx",
|
||||
"parent",
|
||||
],
|
||||
order_by="date",
|
||||
)
|
||||
|
||||
@@ -362,20 +369,26 @@ def get_timetable_details(timetable):
|
||||
assessment = frappe._dict({"assessment_name": entry.reference_docname})
|
||||
|
||||
if entry.reference_doctype == "Course Lesson":
|
||||
entry.icon = "icon-list"
|
||||
course = frappe.db.get_value(
|
||||
entry.reference_doctype, entry.reference_docname, "course"
|
||||
)
|
||||
entry.url = get_lesson_url(course, get_lesson_index(entry.reference_docname))
|
||||
|
||||
entry.completed = (
|
||||
True
|
||||
if frappe.db.exists(
|
||||
"LMS Course Progress",
|
||||
{"lesson": entry.reference_docname, "member": frappe.session.user},
|
||||
)
|
||||
else False
|
||||
)
|
||||
|
||||
elif entry.reference_doctype == "LMS Quiz":
|
||||
entry.icon = "icon-quiz"
|
||||
entry.url = "/quizzes"
|
||||
details = get_quiz_details(assessment, frappe.session.user)
|
||||
entry.update(details)
|
||||
|
||||
elif entry.reference_doctype == "LMS Assignment":
|
||||
entry.icon = "icon-quiz"
|
||||
details = get_assignment_details(assessment, frappe.session.user)
|
||||
entry.update(details)
|
||||
|
||||
@@ -384,11 +397,37 @@ def get_timetable_details(timetable):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def send_email_to_students(batch, subject, message):
|
||||
frappe.only_for("Moderator")
|
||||
students = frappe.get_all("Batch Student", {"parent": batch}, pluck="student")
|
||||
frappe.sendmail(
|
||||
recipients=students,
|
||||
subject=subject,
|
||||
message=message,
|
||||
def is_milestone_complete(idx, batch):
|
||||
previous_rows = frappe.get_all(
|
||||
"LMS Batch Timetable",
|
||||
filters={"parent": batch, "idx": ["<", cint(idx)]},
|
||||
fields=["reference_doctype", "reference_docname", "idx"],
|
||||
order_by="idx",
|
||||
)
|
||||
|
||||
for row in previous_rows:
|
||||
if row.reference_doctype == "Course Lesson":
|
||||
if not frappe.db.exists(
|
||||
"LMS Course Progress",
|
||||
{"member": frappe.session.user, "lesson": row.reference_docname},
|
||||
):
|
||||
return False
|
||||
|
||||
if row.reference_doctype == "LMS Quiz":
|
||||
passing_percentage = frappe.db.get_value(
|
||||
row.reference_doctype, row.reference_docname, "passing_percentage"
|
||||
)
|
||||
if not frappe.db.exists(
|
||||
"LMS Quiz Submission",
|
||||
{"quiz": row.reference_docname, "member": frappe.session.user},
|
||||
):
|
||||
return False
|
||||
|
||||
if row.reference_doctype == "LMS Assignment":
|
||||
if not frappe.db.exists(
|
||||
"LMS Assignment Submission",
|
||||
{"assignment": row.reference_docname, "member": frappe.session.user},
|
||||
):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
"column_break_merq",
|
||||
"start_time",
|
||||
"end_time",
|
||||
"duration"
|
||||
"duration",
|
||||
"milestone"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -69,12 +70,17 @@
|
||||
"fieldname": "day",
|
||||
"fieldtype": "Int",
|
||||
"label": "Day"
|
||||
},
|
||||
{
|
||||
"fieldname": "milestone",
|
||||
"fieldtype": "Check",
|
||||
"label": "Milestone"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-03 17:40:31.530181",
|
||||
"modified": "2023-10-20 11:58:01.782921",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Batch Timetable",
|
||||
|
||||
@@ -10,6 +10,14 @@ frappe.ui.form.on("LMS Certificate", {
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("template", function (doc) {
|
||||
return {
|
||||
filters: {
|
||||
doc_type: "LMS Certificate",
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
refresh: (frm) => {
|
||||
if (frm.doc.name)
|
||||
|
||||
@@ -8,11 +8,12 @@
|
||||
"course",
|
||||
"member",
|
||||
"member_name",
|
||||
"published",
|
||||
"template",
|
||||
"column_break_3",
|
||||
"issue_date",
|
||||
"expiry_date",
|
||||
"batch_name"
|
||||
"batch_name",
|
||||
"published"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -67,11 +68,18 @@
|
||||
"fieldname": "published",
|
||||
"fieldtype": "Check",
|
||||
"label": "Publish on Participant Page"
|
||||
},
|
||||
{
|
||||
"fieldname": "template",
|
||||
"fieldtype": "Link",
|
||||
"label": "Template",
|
||||
"options": "Print Format",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-09-13 11:03:23.479255",
|
||||
"modified": "2023-10-25 12:20:56.091979",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Certificate",
|
||||
|
||||
@@ -48,6 +48,15 @@ def create_certificate(course):
|
||||
if expires_after_yrs:
|
||||
expiry_date = add_years(nowdate(), expires_after_yrs)
|
||||
|
||||
default_certificate_template = frappe.db.get_value(
|
||||
"Property Setter",
|
||||
{
|
||||
"doc_type": "LMS Certificate",
|
||||
"property": "default_print_format",
|
||||
},
|
||||
"value",
|
||||
)
|
||||
|
||||
certificate = frappe.get_doc(
|
||||
{
|
||||
"doctype": "LMS Certificate",
|
||||
@@ -55,6 +64,7 @@ def create_certificate(course):
|
||||
"course": course,
|
||||
"issue_date": nowdate(),
|
||||
"expiry_date": expiry_date,
|
||||
"template": default_certificate_template,
|
||||
}
|
||||
)
|
||||
certificate.save(ignore_permissions=True)
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"payment_for_document_type",
|
||||
"member",
|
||||
"column_break_rqkd",
|
||||
"payment_for_document",
|
||||
"billing_name",
|
||||
"payment_received",
|
||||
"payment_details_section",
|
||||
@@ -115,11 +117,23 @@
|
||||
"fieldname": "amount_with_gst",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount with GST"
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_for_document_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Payment for Document Type",
|
||||
"options": "\nLMS Course\nLMS Batch"
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_for_document",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"label": "Payment for Document",
|
||||
"options": "payment_for_document_type"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-09-12 10:40:22.721371",
|
||||
"modified": "2023-10-17 23:17:50.334975",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Payment",
|
||||
|
||||
0
lms/lms/doctype/lms_question/__init__.py
Normal file
0
lms/lms/doctype/lms_question/__init__.py
Normal file
8
lms/lms/doctype/lms_question/lms_question.js
Normal file
8
lms/lms/doctype/lms_question/lms_question.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2023, Frappe and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("LMS Question", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
245
lms/lms/doctype/lms_question/lms_question.json
Normal file
245
lms/lms/doctype/lms_question/lms_question.json
Normal file
@@ -0,0 +1,245 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "format:QTS-{YYYY}-{#####}",
|
||||
"creation": "2023-10-10 10:24:14.035772",
|
||||
"default_view": "List",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"question",
|
||||
"type",
|
||||
"multiple",
|
||||
"section_break_ytxi",
|
||||
"option_1",
|
||||
"is_correct_1",
|
||||
"column_break_fpvl",
|
||||
"explanation_1",
|
||||
"section_break_eiaa",
|
||||
"option_2",
|
||||
"is_correct_2",
|
||||
"column_break_akwy",
|
||||
"explanation_2",
|
||||
"section_break_cwqv",
|
||||
"option_3",
|
||||
"is_correct_3",
|
||||
"column_break_atpl",
|
||||
"explanation_3",
|
||||
"section_break_yqel",
|
||||
"option_4",
|
||||
"is_correct_4",
|
||||
"column_break_lknb",
|
||||
"explanation_4",
|
||||
"section_break_hkfe",
|
||||
"possibility_1",
|
||||
"possibility_3",
|
||||
"column_break_wpjr",
|
||||
"possibility_2",
|
||||
"possibility_4"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "question",
|
||||
"fieldtype": "Text Editor",
|
||||
"in_list_view": 1,
|
||||
"label": "Question"
|
||||
},
|
||||
{
|
||||
"fieldname": "type",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Type",
|
||||
"options": "Choices\nUser Input"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.type == \"Choices\";",
|
||||
"fieldname": "section_break_ytxi",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "option_1",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Option 1",
|
||||
"mandatory_depends_on": "eval: doc.type == 'Choices'"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_correct_1",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Correct"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_fpvl",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "explanation_1",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Explanation"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.type == \"Choices\";",
|
||||
"fieldname": "section_break_eiaa",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "option_2",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Option 2",
|
||||
"mandatory_depends_on": "eval: doc.type == 'Choices'"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_correct_2",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Correct"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_akwy",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "explanation_2",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Explanation "
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.type == 'Choices'",
|
||||
"fieldname": "section_break_cwqv",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "option_3",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Option 3"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_correct_3",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Correct"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_atpl",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "explanation_3",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Explanation"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.type == 'Choices'",
|
||||
"fieldname": "section_break_yqel",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "option_4",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Option 4"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_correct_4",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Correct"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_lknb",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "explanation_4",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Explanation"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "multiple",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Multiple Correct Answers"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.type == 'User Input'",
|
||||
"fieldname": "section_break_hkfe",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_wpjr",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "possibility_1",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Possible Answer 1",
|
||||
"mandatory_depends_on": "eval: doc.type == 'User Input'"
|
||||
},
|
||||
{
|
||||
"fieldname": "possibility_3",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Possible Answer 3"
|
||||
},
|
||||
{
|
||||
"fieldname": "possibility_2",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Possible Answer 2"
|
||||
},
|
||||
{
|
||||
"fieldname": "possibility_4",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Possible Answer 4"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-18 21:58:42.653317",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Question",
|
||||
"naming_rule": "Expression",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"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",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "question"
|
||||
}
|
||||
92
lms/lms/doctype/lms_question/lms_question.py
Normal file
92
lms/lms/doctype/lms_question/lms_question.py
Normal file
@@ -0,0 +1,92 @@
|
||||
# Copyright (c) 2023, Frappe and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
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):
|
||||
def validate(self):
|
||||
validate_correct_answers(self)
|
||||
|
||||
|
||||
def validate_correct_answers(question):
|
||||
if question.type == "Choices":
|
||||
validate_duplicate_options(question)
|
||||
validate_correct_options(question)
|
||||
else:
|
||||
validate_possible_answer(question)
|
||||
|
||||
|
||||
def validate_duplicate_options(question):
|
||||
options = []
|
||||
|
||||
for num in range(1, 5):
|
||||
if question.get(f"option_{num}"):
|
||||
options.append(question.get(f"option_{num}"))
|
||||
|
||||
if len(set(options)) != len(options):
|
||||
frappe.throw(_("Duplicate options found for this question."))
|
||||
|
||||
|
||||
def validate_correct_options(question):
|
||||
correct_options = get_correct_options(question)
|
||||
|
||||
if len(correct_options) > 1:
|
||||
question.multiple = 1
|
||||
|
||||
if not len(correct_options):
|
||||
frappe.throw(_("At least one option must be correct for this question."))
|
||||
|
||||
|
||||
def validate_possible_answer(question):
|
||||
possible_answers = []
|
||||
possible_answers_fields = [
|
||||
"possibility_1",
|
||||
"possibility_2",
|
||||
"possibility_3",
|
||||
"possibility_4",
|
||||
]
|
||||
|
||||
for field in possible_answers_fields:
|
||||
if question.get(field):
|
||||
possible_answers.append(field)
|
||||
|
||||
if not len(possible_answers):
|
||||
frappe.throw(
|
||||
_("Add at least one possible answer for this question: {0}").format(
|
||||
frappe.bold(question.question)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def get_correct_options(question):
|
||||
correct_options = []
|
||||
correct_option_fields = [
|
||||
"is_correct_1",
|
||||
"is_correct_2",
|
||||
"is_correct_3",
|
||||
"is_correct_4",
|
||||
]
|
||||
for field in correct_option_fields:
|
||||
if question.get(field) == 1:
|
||||
correct_options.append(field)
|
||||
|
||||
return correct_options
|
||||
|
||||
|
||||
@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)
|
||||
9
lms/lms/doctype/lms_question/test_lms_question.py
Normal file
9
lms/lms/doctype/lms_question/test_lms_question.py
Normal file
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2023, Frappe and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
|
||||
class TestLMSQuestion(FrappeTestCase):
|
||||
pass
|
||||
@@ -5,3 +5,13 @@ frappe.ui.form.on("LMS Quiz", {
|
||||
// refresh: function(frm) {
|
||||
// }
|
||||
});
|
||||
|
||||
frappe.ui.form.on("LMS Quiz Question", {
|
||||
marks: function (frm) {
|
||||
total_marks = 0;
|
||||
frm.doc.questions.forEach((question) => {
|
||||
total_marks += question.marks;
|
||||
});
|
||||
frm.doc.total_marks = total_marks;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
"column_break_gaac",
|
||||
"max_attempts",
|
||||
"show_submission_history",
|
||||
"section_break_hsiv",
|
||||
"passing_percentage",
|
||||
"column_break_rocd",
|
||||
"total_marks",
|
||||
"section_break_sbjx",
|
||||
"questions",
|
||||
"section_break_3",
|
||||
@@ -43,7 +47,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"default": "0",
|
||||
"fieldname": "max_attempts",
|
||||
"fieldtype": "Int",
|
||||
"label": "Max Attempts"
|
||||
@@ -90,11 +94,34 @@
|
||||
"fieldname": "show_submission_history",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Submission History"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_hsiv",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "passing_percentage",
|
||||
"fieldtype": "Int",
|
||||
"label": "Passing Percentage",
|
||||
"non_negative": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_rocd",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "total_marks",
|
||||
"fieldtype": "Int",
|
||||
"label": "Total Marks",
|
||||
"non_negative": 1,
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-07-04 15:26:24.457745",
|
||||
"modified": "2023-10-18 22:50:58.252350",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Quiz",
|
||||
@@ -123,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,
|
||||
|
||||
@@ -5,7 +5,8 @@ import json
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cstr
|
||||
from frappe.utils import cstr, comma_and
|
||||
from lms.lms.doctype.lms_question.lms_question import validate_correct_answers
|
||||
from lms.lms.utils import (
|
||||
generate_slug,
|
||||
has_course_moderator_role,
|
||||
@@ -14,13 +15,22 @@ from lms.lms.utils import (
|
||||
|
||||
|
||||
class LMSQuiz(Document):
|
||||
def validate(self):
|
||||
self.validate_duplicate_questions()
|
||||
self.total_marks = set_total_marks(self.name, self.questions)
|
||||
|
||||
def validate_duplicate_questions(self):
|
||||
questions = [row.question for row in self.questions]
|
||||
rows = [i + 1 for i, x in enumerate(questions) if questions.count(x) > 1]
|
||||
if len(rows):
|
||||
frappe.throw(
|
||||
_("Rows {0} have the duplicate questions.").format(frappe.bold(comma_and(rows)))
|
||||
)
|
||||
|
||||
def autoname(self):
|
||||
if not self.name:
|
||||
self.name = generate_slug(self.title, "LMS Quiz")
|
||||
|
||||
def validate(self):
|
||||
validate_correct_answers(self.questions)
|
||||
|
||||
def get_last_submission_details(self):
|
||||
"""Returns the latest submission for this user."""
|
||||
user = frappe.session.user
|
||||
@@ -39,76 +49,11 @@ class LMSQuiz(Document):
|
||||
return result[0]
|
||||
|
||||
|
||||
def get_correct_options(question):
|
||||
correct_option_fields = [
|
||||
"is_correct_1",
|
||||
"is_correct_2",
|
||||
"is_correct_3",
|
||||
"is_correct_4",
|
||||
]
|
||||
return list(filter(lambda x: question.get(x) == 1, correct_option_fields))
|
||||
|
||||
|
||||
def validate_correct_answers(questions):
|
||||
def set_total_marks(quiz, questions):
|
||||
marks = 0
|
||||
for question in questions:
|
||||
if question.type == "Choices":
|
||||
validate_duplicate_options(question)
|
||||
validate_correct_options(question)
|
||||
else:
|
||||
validate_possible_answer(question)
|
||||
|
||||
|
||||
def validate_duplicate_options(question):
|
||||
options = []
|
||||
|
||||
for num in range(1, 5):
|
||||
if question.get(f"option_{num}"):
|
||||
options.append(question.get(f"option_{num}"))
|
||||
|
||||
if len(set(options)) != len(options):
|
||||
frappe.throw(
|
||||
_("Duplicate options found for this question: {0}").format(
|
||||
frappe.bold(question.question)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def validate_correct_options(question):
|
||||
correct_options = get_correct_options(question)
|
||||
|
||||
if len(correct_options) > 1:
|
||||
question.multiple = 1
|
||||
|
||||
if not len(correct_options):
|
||||
frappe.throw(
|
||||
_("At least one option must be correct for this question: {0}").format(
|
||||
frappe.bold(question.question)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def validate_possible_answer(question):
|
||||
possible_answers_fields = [
|
||||
"possibility_1",
|
||||
"possibility_2",
|
||||
"possibility_3",
|
||||
"possibility_4",
|
||||
]
|
||||
possible_answers = list(filter(lambda x: question.get(x), possible_answers_fields))
|
||||
|
||||
if not len(possible_answers):
|
||||
frappe.throw(
|
||||
_("Add at least one possible answer for this question: {0}").format(
|
||||
frappe.bold(question.question)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def update_lesson_info(doc, method):
|
||||
if doc.quiz_id:
|
||||
frappe.db.set_value(
|
||||
"LMS Quiz", doc.quiz_id, {"lesson": doc.name, "course": doc.course}
|
||||
)
|
||||
marks += question.get("marks")
|
||||
return marks
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@@ -118,45 +63,72 @@ def quiz_summary(quiz, results):
|
||||
|
||||
for result in results:
|
||||
correct = result["is_correct"][0]
|
||||
result["question"] = frappe.db.get_value(
|
||||
"LMS Quiz Question",
|
||||
{"parent": quiz, "idx": result["question_index"] + 1},
|
||||
["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,
|
||||
"percentage": percentage,
|
||||
"passing_percentage": quiz_details.passing_percentage,
|
||||
}
|
||||
)
|
||||
submission.save(ignore_permissions=True)
|
||||
|
||||
return {
|
||||
"score": score,
|
||||
"score_out_of": score_out_of,
|
||||
"submission": submission.name,
|
||||
"pass": percentage == quiz_details.passing_percentage,
|
||||
}
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def save_quiz(
|
||||
quiz_title, max_attempts=1, quiz=None, show_answers=1, show_submission_history=0
|
||||
quiz_title,
|
||||
passing_percentage,
|
||||
questions,
|
||||
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,
|
||||
@@ -164,41 +136,77 @@ def save_quiz(
|
||||
|
||||
if quiz:
|
||||
frappe.db.set_value("LMS Quiz", quiz, values)
|
||||
update_questions(quiz, questions)
|
||||
return quiz
|
||||
else:
|
||||
doc = frappe.new_doc("LMS Quiz")
|
||||
doc.update(values)
|
||||
doc.save(ignore_permissions=True)
|
||||
doc.save()
|
||||
update_questions(doc.name, questions)
|
||||
return doc.name
|
||||
|
||||
|
||||
def update_questions(quiz, questions):
|
||||
questions = json.loads(questions)
|
||||
|
||||
delete_questions(quiz, questions)
|
||||
add_questions(quiz, questions)
|
||||
frappe.db.set_value("LMS Quiz", quiz, "total_marks", set_total_marks(quiz, questions))
|
||||
|
||||
|
||||
def delete_questions(quiz, questions):
|
||||
existing_questions = frappe.get_all(
|
||||
"LMS Quiz Question",
|
||||
{
|
||||
"parent": quiz,
|
||||
},
|
||||
pluck="name",
|
||||
)
|
||||
|
||||
current_questions = [question.get("question_name") for question in questions]
|
||||
|
||||
for question in existing_questions:
|
||||
if question not in current_questions:
|
||||
frappe.db.delete("LMS Quiz Question", question)
|
||||
|
||||
|
||||
def add_questions(quiz, questions):
|
||||
for index, question in enumerate(questions):
|
||||
question = frappe._dict(question)
|
||||
if question.question_name:
|
||||
doc = frappe.get_doc("LMS Quiz Question", question.question_name)
|
||||
else:
|
||||
doc = frappe.new_doc("LMS Quiz Question")
|
||||
doc.update(
|
||||
{
|
||||
"parent": quiz,
|
||||
"parenttype": "LMS Quiz",
|
||||
"parentfield": "questions",
|
||||
"idx": index + 1,
|
||||
}
|
||||
)
|
||||
|
||||
doc.update({"question": question.question, "marks": question.marks})
|
||||
|
||||
doc.save()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def save_question(quiz, values, index):
|
||||
values = frappe._dict(json.loads(values))
|
||||
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(
|
||||
@@ -222,9 +230,8 @@ def save_question(quiz, values, index):
|
||||
}
|
||||
)
|
||||
|
||||
doc.save(ignore_permissions=True)
|
||||
|
||||
return quiz
|
||||
doc.save()
|
||||
return doc.name
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@@ -257,9 +264,7 @@ def check_choice_answers(question, answers):
|
||||
fields.append(f"option_{cstr(num)}")
|
||||
fields.append(f"is_correct_{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):
|
||||
if question_details[f"option_{num}"] in answers:
|
||||
@@ -275,9 +280,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():
|
||||
|
||||
@@ -10,51 +10,36 @@ import frappe
|
||||
class TestLMSQuiz(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
frappe.get_doc({"doctype": "LMS Quiz", "title": "Test Quiz"}).save(
|
||||
ignore_permissions=True
|
||||
)
|
||||
frappe.get_doc(
|
||||
{"doctype": "LMS Quiz", "title": "Test Quiz", "passing_percentage": 90}
|
||||
).save(ignore_permissions=True)
|
||||
|
||||
def test_with_multiple_options(self):
|
||||
quiz = frappe.get_doc("LMS Quiz", "test-quiz")
|
||||
quiz.append(
|
||||
"questions",
|
||||
{
|
||||
"question": "Question Multiple",
|
||||
"type": "Choices",
|
||||
"option_1": "Option 1",
|
||||
"is_correct_1": 1,
|
||||
"option_2": "Option 2",
|
||||
"is_correct_2": 1,
|
||||
},
|
||||
)
|
||||
quiz.save()
|
||||
self.assertTrue(quiz.questions[0].multiple)
|
||||
question = frappe.new_doc("LMS Question")
|
||||
question.question = "Question Multiple"
|
||||
question.type = "Choices"
|
||||
question.option_1 = "Option 1"
|
||||
question.is_correct_1 = 1
|
||||
question.option_2 = "Option 2"
|
||||
question.is_correct_2 = 1
|
||||
question.save()
|
||||
self.assertTrue(question.multiple)
|
||||
|
||||
def test_with_no_correct_option(self):
|
||||
quiz = frappe.get_doc("LMS Quiz", "test-quiz")
|
||||
quiz.append(
|
||||
"questions",
|
||||
{
|
||||
"question": "Question no correct option",
|
||||
"type": "Choices",
|
||||
"option_1": "Option 1",
|
||||
"option_2": "Option 2",
|
||||
},
|
||||
)
|
||||
self.assertRaises(frappe.ValidationError, quiz.save)
|
||||
question = frappe.new_doc("LMS Question")
|
||||
question.question = "Question Multiple"
|
||||
question.type = "Choices"
|
||||
question.option_1 = "Option 1"
|
||||
question.option_2 = "Option 2"
|
||||
self.assertRaises(frappe.ValidationError, question.save)
|
||||
|
||||
def test_with_no_possible_answers(self):
|
||||
quiz = frappe.get_doc("LMS Quiz", "test-quiz")
|
||||
quiz.append(
|
||||
"questions",
|
||||
{
|
||||
"question": "Question Possible Answers",
|
||||
"type": "User Input",
|
||||
},
|
||||
)
|
||||
self.assertRaises(frappe.ValidationError, quiz.save)
|
||||
question = frappe.new_doc("LMS Question")
|
||||
question.question = "Question Multiple"
|
||||
question.type = "User Input"
|
||||
self.assertRaises(frappe.ValidationError, question.save)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls) -> None:
|
||||
frappe.db.delete("LMS Quiz", "test-quiz")
|
||||
frappe.db.delete("LMS Quiz Question", {"parent": "test-quiz"})
|
||||
frappe.db.delete("LMS Question")
|
||||
|
||||
@@ -6,208 +6,31 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"question",
|
||||
"type",
|
||||
"options_section",
|
||||
"option_1",
|
||||
"is_correct_1",
|
||||
"column_break_5",
|
||||
"explanation_1",
|
||||
"section_break_5",
|
||||
"option_2",
|
||||
"is_correct_2",
|
||||
"column_break_10",
|
||||
"explanation_2",
|
||||
"column_break_4",
|
||||
"option_3",
|
||||
"is_correct_3",
|
||||
"column_break_15",
|
||||
"explanation_3",
|
||||
"section_break_11",
|
||||
"option_4",
|
||||
"is_correct_4",
|
||||
"column_break_20",
|
||||
"explanation_4",
|
||||
"section_break_mnhr",
|
||||
"possibility_1",
|
||||
"possibility_3",
|
||||
"column_break_vnaj",
|
||||
"possibility_2",
|
||||
"possibility_4",
|
||||
"section_break_c1lf",
|
||||
"multiple"
|
||||
"marks"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "question",
|
||||
"fieldtype": "Text Editor",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Question",
|
||||
"options": "LMS Question",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "option_1",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Option 1",
|
||||
"mandatory_depends_on": "eval: doc.type == 'Choices'"
|
||||
},
|
||||
{
|
||||
"fieldname": "option_2",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Option 2",
|
||||
"mandatory_depends_on": "eval: doc.type == 'Choices'"
|
||||
},
|
||||
{
|
||||
"fieldname": "option_3",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Option 3"
|
||||
},
|
||||
{
|
||||
"fieldname": "option_4",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Option 4"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "option_1",
|
||||
"fieldname": "is_correct_1",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Correct"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "option_2",
|
||||
"fieldname": "is_correct_2",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Correct"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "option_3",
|
||||
"fieldname": "is_correct_3",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Correct"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "option_4",
|
||||
"fieldname": "is_correct_4",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Correct"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "multiple",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Multiple Correct Answers",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.type == 'Choices'",
|
||||
"fieldname": "options_section",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.type == 'Choices'",
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.type == 'Choices'",
|
||||
"fieldname": "section_break_5",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.type == 'Choices'",
|
||||
"fieldname": "section_break_11",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "option_1",
|
||||
"fieldname": "explanation_1",
|
||||
"fieldtype": "Data",
|
||||
"label": "Explanation"
|
||||
},
|
||||
{
|
||||
"depends_on": "option_2",
|
||||
"fieldname": "explanation_2",
|
||||
"fieldtype": "Data",
|
||||
"label": "Explanation"
|
||||
},
|
||||
{
|
||||
"depends_on": "option_3",
|
||||
"fieldname": "explanation_3",
|
||||
"fieldtype": "Data",
|
||||
"label": "Explanation"
|
||||
},
|
||||
{
|
||||
"depends_on": "option_4",
|
||||
"fieldname": "explanation_4",
|
||||
"fieldtype": "Data",
|
||||
"label": "Explanation"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_5",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_10",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_15",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_20",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "type",
|
||||
"fieldtype": "Select",
|
||||
"default": "1",
|
||||
"fieldname": "marks",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Type",
|
||||
"options": "Choices\nUser Input"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.type == 'User Input'",
|
||||
"fieldname": "section_break_mnhr",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "possibility_1",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Possible Answer 1",
|
||||
"mandatory_depends_on": "eval: doc.type == 'User Input'"
|
||||
},
|
||||
{
|
||||
"fieldname": "possibility_2",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Possible Answer 2"
|
||||
},
|
||||
{
|
||||
"fieldname": "possibility_3",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Possible Answer 3"
|
||||
},
|
||||
{
|
||||
"fieldname": "possibility_4",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Possible Answer 4"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_c1lf",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_vnaj",
|
||||
"fieldtype": "Column Break"
|
||||
"label": "Marks",
|
||||
"non_negative": 1,
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-07-04 16:43:49.837134",
|
||||
"modified": "2023-10-16 19:51:03.893144",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Quiz Question",
|
||||
|
||||
@@ -6,7 +6,11 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"question",
|
||||
"section_break_fztv",
|
||||
"question_name",
|
||||
"answer",
|
||||
"column_break_flus",
|
||||
"marks",
|
||||
"is_correct"
|
||||
],
|
||||
"fields": [
|
||||
@@ -31,12 +35,33 @@
|
||||
"in_list_view": 1,
|
||||
"label": "Is Correct",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_fztv",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "question_name",
|
||||
"fieldtype": "Link",
|
||||
"label": "Question Name",
|
||||
"options": "LMS Question"
|
||||
},
|
||||
{
|
||||
"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": "2022-11-24 11:15:45.931119",
|
||||
"modified": "2023-10-17 11:55:25.641214",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Quiz Result",
|
||||
|
||||
@@ -6,11 +6,16 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"quiz",
|
||||
"score",
|
||||
"course",
|
||||
"column_break_3",
|
||||
"member",
|
||||
"member_name",
|
||||
"section_break_dkpn",
|
||||
"score",
|
||||
"score_out_of",
|
||||
"column_break_gkip",
|
||||
"percentage",
|
||||
"passing_percentage",
|
||||
"section_break_6",
|
||||
"result"
|
||||
],
|
||||
@@ -31,9 +36,11 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "score",
|
||||
"fieldtype": "Data",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Score"
|
||||
"label": "Score",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "member",
|
||||
@@ -65,12 +72,45 @@
|
||||
"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
|
||||
},
|
||||
{
|
||||
"fieldname": "passing_percentage",
|
||||
"fieldtype": "Int",
|
||||
"label": "Passing 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.979975",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Quiz Submission",
|
||||
|
||||
@@ -6,4 +6,10 @@ from frappe.model.document import Document
|
||||
|
||||
|
||||
class LMSQuizSubmission(Document):
|
||||
pass
|
||||
def before_insert(self):
|
||||
if not self.percentage:
|
||||
self.set_percentage()
|
||||
|
||||
def set_percentage(self):
|
||||
if self.score and self.score_out_of:
|
||||
self.percentage = (self.score / self.score_out_of) * 100
|
||||
|
||||
@@ -4,7 +4,6 @@ import frappe
|
||||
import json
|
||||
import razorpay
|
||||
import requests
|
||||
import base64
|
||||
from frappe import _
|
||||
from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_result
|
||||
from frappe.desk.doctype.notification_log.notification_log import make_notification_logs
|
||||
@@ -1029,6 +1028,8 @@ def record_payment(address, response, client, doctype, docname):
|
||||
"amount_with_gst": payment_details["amount_with_gst"],
|
||||
"gstin": address.gstin,
|
||||
"pan": address.pan,
|
||||
"payment_for_document_type": doctype,
|
||||
"payment_for_document": docname,
|
||||
}
|
||||
)
|
||||
payment_doc.save(ignore_permissions=True)
|
||||
|
||||
Reference in New Issue
Block a user