diff --git a/lms/install.py b/lms/install.py index b75b5518..8265d694 100644 --- a/lms/install.py +++ b/lms/install.py @@ -62,7 +62,7 @@ def delete_lms_roles(): def set_default_home(): - frappe.db.set_value("Portal Settings", None, "default_portal_home", "/courses") + frappe.db.set_single_value("Portal Settings", "default_portal_home", "/courses") def create_course_creator_role(): diff --git a/lms/lms/doctype/lms_quiz/lms_quiz.py b/lms/lms/doctype/lms_quiz/lms_quiz.py index fa49b0a9..41da49a6 100644 --- a/lms/lms/doctype/lms_quiz/lms_quiz.py +++ b/lms/lms/doctype/lms_quiz/lms_quiz.py @@ -17,52 +17,7 @@ class LMSQuiz(Document): self.name = generate_slug(self.title, "LMS Quiz") def validate(self): - self.validate_correct_answers() - - def validate_correct_answers(self): - for question in self.questions: - if question.type == "Choices": - self.validate_correct_options(question) - else: - self.validate_possible_answer(question) - - def validate_correct_options(self, question): - correct_options = self.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(self, 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 get_correct_options(self, 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)) + validate_correct_answers(self.questions) def get_last_submission_details(self): """Returns the latest submission for this user.""" @@ -82,6 +37,71 @@ 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): + 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( @@ -121,37 +141,84 @@ def quiz_summary(quiz, results): @frappe.whitelist() -def save_quiz(quiz_title, questions, quiz): +def save_quiz(quiz_title, quiz): if quiz: - doc = frappe.get_doc("LMS Quiz", quiz) + frappe.db.set_value("LMS Quiz", quiz, "title", quiz_title) + return quiz else: - doc = frappe.get_doc( + doc = frappe.new_doc("LMS Quiz") + doc.update({"title": quiz_title}) + doc.save(ignore_permissions=True) + return doc.name + + +@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")) + else: + doc = frappe.new_doc("LMS Quiz Question") + + doc.update( + { + "question": values["question"], + "type": values["type"], + } + ) + + if not values.get("name"): + doc.update( { - "doctype": "LMS Quiz", + "parent": quiz, + "parenttype": "LMS Quiz", + "parentfield": "questions", + "idx": index, } ) - doc.update({"title": quiz_title}) - doc.save(ignore_permissions=True) - - for index, row in enumerate(json.loads(questions)): - if row["question_name"]: - question_doc = frappe.get_doc("LMS Quiz Question", row["question_name"]) - else: - question_doc = frappe.get_doc( + for num in range(1, 5): + if values.get(f"option_{num}"): + doc.update( { - "doctype": "LMS Quiz Question", - "parent": doc.name, - "parenttype": "LMS Quiz", - "parentfield": "questions", - "idx": index + 1, + f"option_{num}": values[f"option_{num}"], + f"is_correct_{num}": values[f"is_correct_{num}"], } ) - question_doc.update(row) - question_doc.save(ignore_permissions=True) + if values.get(f"explanation_{num}"): + doc.update( + { + f"explanation_{num}": values[f"explanation_{num}"], + } + ) - return doc.name + if values.get(f"possibility_{num}"): + doc.update( + { + f"possibility_{num}": values[f"possibility_{num}"], + } + ) + + doc.save(ignore_permissions=True) + + return quiz + + +@frappe.whitelist() +def get_question_details(question): + if frappe.db.exists("LMS Quiz Question", question): + fields = ["name", "question", "type"] + for num in range(1, 5): + fields.append(f"option_{cstr(num)}") + fields.append(f"is_correct_{cstr(num)}") + fields.append(f"explanation_{cstr(num)}") + fields.append(f"possibility_{cstr(num)}") + + return frappe.db.get_value("LMS Quiz Question", question, fields, as_dict=1) + return @frappe.whitelist() diff --git a/lms/lms/doctype/lms_quiz_question/lms_quiz_question.json b/lms/lms/doctype/lms_quiz_question/lms_quiz_question.json index 09856df7..3231fc8c 100644 --- a/lms/lms/doctype/lms_quiz_question/lms_quiz_question.json +++ b/lms/lms/doctype/lms_quiz_question/lms_quiz_question.json @@ -39,31 +39,31 @@ "fields": [ { "fieldname": "question", - "fieldtype": "Text", + "fieldtype": "Text Editor", "in_list_view": 1, "label": "Question", "reqd": 1 }, { "fieldname": "option_1", - "fieldtype": "Data", + "fieldtype": "Small Text", "label": "Option 1", "mandatory_depends_on": "eval: doc.type == 'Choices'" }, { "fieldname": "option_2", - "fieldtype": "Data", + "fieldtype": "Small Text", "label": "Option 2", "mandatory_depends_on": "eval: doc.type == 'Choices'" }, { "fieldname": "option_3", - "fieldtype": "Data", + "fieldtype": "Small Text", "label": "Option 3" }, { "fieldname": "option_4", - "fieldtype": "Data", + "fieldtype": "Small Text", "label": "Option 4" }, { @@ -206,7 +206,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-03-17 18:22:20.324536", + "modified": "2023-06-09 17:09:53.740179", "modified_by": "Administrator", "module": "LMS", "name": "LMS Quiz Question", diff --git a/lms/patches/create_mentor_request_email_templates.py b/lms/patches/create_mentor_request_email_templates.py index 006a86cf..2d8667c2 100644 --- a/lms/patches/create_mentor_request_email_templates.py +++ b/lms/patches/create_mentor_request_email_templates.py @@ -22,9 +22,8 @@ def execute(): } ).insert(ignore_permissions=True) - frappe.db.set_value( + frappe.db.set_single_value( "LMS Settings", - None, "mentor_request_creation", _("Mentor Request Creation Template"), ) @@ -43,9 +42,8 @@ def execute(): } ).insert(ignore_permissions=True) - frappe.db.set_value( + frappe.db.set_single_value( "LMS Settings", - None, "mentor_request_status_update", _("Mentor Request Status Update Template"), ) diff --git a/lms/public/css/style.css b/lms/public/css/style.css index 23b64b1a..7169ada5 100644 --- a/lms/public/css/style.css +++ b/lms/public/css/style.css @@ -162,8 +162,8 @@ textarea.field-input { .lesson-editor { border: 1px solid var(--gray-300); - border-radius: var(--border-radius-md); - padding-top: 0.5rem; + border-radius: var(--border-radius-md); + padding-top: 0.5rem; } .lesson-parent .breadcrumb { @@ -185,12 +185,24 @@ textarea.field-input { .clickable { color: var(--gray-900); font-weight: 500; - cursor: pointer; } .clickable:hover { color: var(--gray-900); text-decoration: none; + cursor: pointer; +} + +.question-row .ql-editor.read-mode p:hover { + cursor: pointer; +} + +.question-row .ql-editor.read-mode p { + display: none; +} + +.question-row .ql-editor.read-mode p:first-child { + display: block; } .codex-editor path { @@ -471,10 +483,6 @@ input[type=checkbox] { color: var(--gray-700); } -.custom-checkbox>label>input { - visibility: hidden; -} - .custom-checkbox>label>.empty-checkbox { height: 1.5rem; width: 1.5rem; @@ -493,16 +501,30 @@ input[type=checkbox] { } .quiz-label { - display: flex; - align-items: center; - margin-bottom: 0; - cursor: pointer; + } .quiz-label p { display: inline; } +.option-row { + display: flex; + align-items: center; + flex: 1; + margin-bottom: 0; + padding: 0.75rem; + border: 1px solid var(--gray-200); + border-radius: var(--border-radius-lg); + cursor:pointer; + background-color: var(--gray-100); +} + +.active-option .option-row { + background-color: var(--blue-50); + border: 1px solid var(--blue-500); +} + .course-card-wide { width: 50%; margin-bottom: 2rem; @@ -1056,7 +1078,7 @@ pre { .column-card { flex-direction: column; - padding: 1.25rem; + padding: 1rem; height: 100%; } @@ -1502,41 +1524,21 @@ pre { } .reviews-parent .progress-bar { - background-color: var(--primary-color); + background-color: var(--primary-color); } .course-home-top-container { position: relative; } -.question-header { - display: flex; - align-items: center; - margin-bottom: 1rem; -} - -.question-number { - padding-right: 0.25rem; -} - -.option-text { - padding: 0.75rem; - border: 1px solid var(--gray-200); - border-radius: var(--border-radius-md); - flex: 1; -} - -.active-option .option-text { - background-color: var(--blue-50); - border: 1px solid var(--blue-500); -} - .question-text { - font-size: var(--text-lg); - color: var(--gray-900); - font-weight: 600; - flex: 1; - margin: 0 1rem; + margin: 0.5rem 0 1rem; + font-weight: 600; +} + +.question-text .ql-editor.read-mode { + white-space: inherit; + font-weight: 600; } .profile-column-grid { diff --git a/lms/templates/quiz.html b/lms/templates/quiz.html index c9752953..884c836e 100644 --- a/lms/templates/quiz.html +++ b/lms/templates/quiz.html @@ -9,20 +9,16 @@ - {% else %} -