diff --git a/erpnext/domains/education.py b/erpnext/domains/education.py index 55e4eed801..bbaa6e55d9 100644 --- a/erpnext/domains/education.py +++ b/erpnext/domains/education.py @@ -14,7 +14,7 @@ data = { 'Student Attendance Tool', 'Student Applicant' ], - 'default_portal_role': 'LMS User', + 'default_portal_role': 'Student', 'restricted_roles': [ 'Student', 'Instructor', diff --git a/erpnext/education/doctype/course/course.json b/erpnext/education/doctype/course/course.json index 072e8b4afb..7d8b07397e 100644 --- a/erpnext/education/doctype/course/course.json +++ b/erpnext/education/doctype/course/course.json @@ -1,514 +1,135 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:course_code", - "beta": 0, - "creation": "2015-09-07 12:39:55.181893", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 0, - "engine": "InnoDB", + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:course_code", + "creation": "2015-09-07 12:39:55.181893", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "course_name", + "department", + "parent_course", + "column_break_3", + "course_code", + "course_abbreviation", + "section_break_6", + "topics", + "description", + "hero_image", + "assessment", + "default_grading_scale", + "assessment_criteria" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "course_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Course Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "course_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Course Name", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "department", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Department", - "length": 0, - "no_copy": 0, - "options": "Department", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "department", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Department", + "options": "Department" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "parent_course", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Parent Course (Leave blank, if this isn't part of Parent Course)", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "parent_course", + "fieldtype": "Data", + "label": "Parent Course (Leave blank, if this isn't part of Parent Course)" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "course_code", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Course Code", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "fieldname": "course_code", + "fieldtype": "Data", + "label": "Course Code", + "reqd": 1, "unique": 1 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "course_abbreviation", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Course Abbreviation", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "course_abbreviation", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Course Abbreviation" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "section_break_6", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "topics", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Topics", - "length": 0, - "no_copy": 0, - "options": "Course Topic", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "topics", + "fieldtype": "Table", + "label": "Topics", + "options": "Course Topic" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "course_intro", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Course Intro", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "hero_image", + "fieldtype": "Attach Image", + "label": "Hero Image" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "hero_image", - "fieldtype": "Attach Image", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Hero Image", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "assessment", + "fieldtype": "Section Break", + "label": "Assessment" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "assessment", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Assessment", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "default_grading_scale", + "fieldtype": "Link", + "label": "Default Grading Scale", + "options": "Grading Scale" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "default_grading_scale", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Default Grading Scale", - "length": 0, - "no_copy": 0, - "options": "Grading Scale", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "assessment_criteria", + "fieldtype": "Table", + "label": "Assessment Criteria", + "options": "Course Assessment Criteria" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "assessment_criteria", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Assessment Criteria", - "length": 0, - "no_copy": 0, - "options": "Course Assessment Criteria", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description" } - ], - "has_web_view": 0, - "hide_toolbar": 0, - "idx": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "menu_index": 0, - "modified": "2019-04-09 11:35:27.354877", - "modified_by": "Administrator", - "module": "Education", - "name": "Course", - "name_case": "", - "owner": "Administrator", + ], + "modified": "2019-06-05 18:39:11.870605", + "modified_by": "Administrator", + "module": "Education", + "name": "Course", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Academics User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Academics User", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Instructor", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Instructor", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "restrict_to_domain": "Education", - "search_fields": "course_name", - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "restrict_to_domain": "Education", + "search_fields": "course_name", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/education/doctype/course_enrollment/course_enrollment.py b/erpnext/education/doctype/course_enrollment/course_enrollment.py index 6f2bb0db1f..b082be2aa2 100644 --- a/erpnext/education/doctype/course_enrollment/course_enrollment.py +++ b/erpnext/education/doctype/course_enrollment/course_enrollment.py @@ -35,7 +35,7 @@ class CourseEnrollment(Document): if enrollment: frappe.throw(_("Student is already enrolled.")) - def add_quiz_activity(self, quiz_name, quiz_response,answers, score, status): + def add_quiz_activity(self, quiz_name, quiz_response, answers, score, status): result = {k: ('Correct' if v else 'Wrong') for k,v in answers.items()} result_data = [] for key in answers: @@ -43,7 +43,9 @@ class CourseEnrollment(Document): item['question'] = key item['quiz_result'] = result[key] try: - if isinstance(quiz_response[key], list): + if not quiz_response[key]: + item['selected_option'] = "Unattempted" + elif isinstance(quiz_response[key], list): item['selected_option'] = ', '.join(frappe.get_value('Options', res, 'option') for res in quiz_response[key]) else: item['selected_option'] = frappe.get_value('Options', quiz_response[key], 'option') @@ -59,11 +61,12 @@ class CourseEnrollment(Document): "result": result_data, "score": score, "status": status - }).insert() + }).insert(ignore_permissions = True) def add_activity(self, content_type, content): - if check_activity_exists(self.name, content_type, content): - pass + activity = check_activity_exists(self.name, content_type, content) + if activity: + return activity else: activity = frappe.get_doc({ "doctype": "Course Activity", @@ -71,9 +74,14 @@ class CourseEnrollment(Document): "content_type": content_type, "content": content, "activity_date": frappe.utils.datetime.datetime.now() - }) - activity.insert() + }) + + activity.insert(ignore_permissions=True) + return activity.name def check_activity_exists(enrollment, content_type, content): activity = frappe.get_all("Course Activity", filters={'enrollment': enrollment, 'content_type': content_type, 'content': content}) - return bool(activity) \ No newline at end of file + if activity: + return activity[0].name + else: + return None \ No newline at end of file diff --git a/erpnext/education/doctype/program/program.json b/erpnext/education/doctype/program/program.json index cb8d7786e1..a0a2aa2e2b 100644 --- a/erpnext/education/doctype/program/program.json +++ b/erpnext/education/doctype/program/program.json @@ -1,627 +1,181 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 1, "autoname": "field:program_code", - "beta": 0, "creation": "2015-09-07 12:54:03.609282", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", - "editable_grid": 0, "engine": "InnoDB", + "field_order": [ + "program_name", + "department", + "column_break_3", + "program_code", + "program_abbreviation", + "section_break_5", + "courses", + "section_break_9", + "description", + "intro_video", + "hero_image", + "column_break_11", + "is_published", + "is_featured", + "allow_self_enroll" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "program_name", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Program Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "department", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 1, "label": "Department", - "length": 0, - "no_copy": 0, - "options": "Department", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Department" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "program_code", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Program Code", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, "unique": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "program_abbreviation", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, - "label": "Program Abbreviation", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Program Abbreviation" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "section_break_5", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Portal Settings", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Portal Settings" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "courses", "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Courses", - "length": 0, - "no_copy": 0, - "options": "Program Course", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Program Course" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "section_break_9", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "LMS Settings" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "description", "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Description" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "intro_video", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Intro Video", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Intro Video" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "hero_image", "fieldtype": "Attach Image", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Hero Image", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Hero Image" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break_11", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "0", - "fetch_if_empty": 0, "fieldname": "is_published", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Is Published", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Is Published" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "0", - "fetch_if_empty": 0, + "depends_on": "eval: doc.is_published == 1", "fieldname": "is_featured", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Is Featured", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Is Featured" + }, + { + "default": "0", + "depends_on": "eval: doc.is_published == 1", + "description": "Allow students to enroll themselves from the portal", + "fieldname": "allow_self_enroll", + "fieldtype": "Check", + "label": "Allow Self Enroll" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "menu_index": 0, - "modified": "2019-03-18 15:26:56.737903", + "modified": "2019-06-05 17:47:26.877296", "modified_by": "Administrator", "module": "Education", "name": "Program", - "name_case": "", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Academics User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Instructor", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Guest", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "LMS User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Student", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 - }, - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "System Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Academics User", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Instructor", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Guest", + "share": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Student", + "share": 1 } ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, "restrict_to_domain": "Education", - "route": "", "search_fields": "program_name", "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/education/doctype/program_enrollment/program_enrollment.py b/erpnext/education/doctype/program_enrollment/program_enrollment.py index 22cca86fcf..d232e47245 100644 --- a/erpnext/education/doctype/program_enrollment/program_enrollment.py +++ b/erpnext/education/doctype/program_enrollment/program_enrollment.py @@ -96,29 +96,6 @@ class ProgramEnrollment(Document): quiz_progress.program = self.program return quiz_progress - def get_program_progress(self): - import math - program = frappe.get_doc("Program", self.program) - program_progress = {} - progress = [] - for course in program.get_all_children(): - course_progress = lms.get_student_course_details(course.course, self.program) - is_complete = False - if course_progress['flag'] == "Completed": - is_complete = True - progress.append({'course_name': course.course_name, 'name': course.course, 'is_complete': is_complete}) - - program_progress['progress'] = progress - program_progress['name'] = self.program - program_progress['program'] = frappe.get_value("Program", self.program, 'program_name') - - try: - program_progress['percentage'] = math.ceil((sum([item['is_complete'] for item in progress] * 100)/len(progress))) - except ZeroDivisionError: - program_progress['percentage'] = 0 - - return program_progress - @frappe.whitelist() def get_program_courses(doctype, txt, searchfield, start, page_len, filters): if filters.get('program'): diff --git a/erpnext/education/doctype/question/question.json b/erpnext/education/doctype/question/question.json index 14a9f3ce92..b3a161daa0 100644 --- a/erpnext/education/doctype/question/question.json +++ b/erpnext/education/doctype/question/question.json @@ -1,167 +1,80 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "format:QUESTION-{#####}", - "beta": 0, - "creation": "2018-10-01 15:58:00.696815", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "allow_import": 1, + "autoname": "format:QUESTION-{#####}", + "creation": "2018-10-01 15:58:00.696815", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "question", + "options", + "question_type" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "question", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Question", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "question", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Question", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fetch_if_empty": 0, - "fieldname": "options", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Options", - "length": 0, - "no_copy": 0, - "options": "Options", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "options", + "fieldtype": "Table", + "label": "Options", + "options": "Options", + "reqd": 1 + }, + { + "fieldname": "question_type", + "fieldtype": "Select", + "in_standard_filter": 1, + "label": "Type", + "options": "\nSingle Correct Answer\nMultiple Correct Answer", + "read_only": 1 } - ], - "has_web_view": 0, - "hide_toolbar": 0, - "idx": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-04-22 14:02:08.140652", - "modified_by": "Administrator", - "module": "Education", - "name": "Question", - "name_case": "", - "owner": "Administrator", + ], + "modified": "2019-05-30 18:39:21.880974", + "modified_by": "Administrator", + "module": "Education", + "name": "Question", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Academics User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Academics User", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Instructor", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Instructor", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "LMS User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "LMS User", + "share": 1 } - ], - "quick_entry": 1, - "read_only": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/education/doctype/question/question.py b/erpnext/education/doctype/question/question.py index 8cd23983ce..b8221081fa 100644 --- a/erpnext/education/doctype/question/question.py +++ b/erpnext/education/doctype/question/question.py @@ -12,6 +12,7 @@ class Question(Document): def validate(self): self.check_at_least_one_option() self.check_minimum_one_correct_answer() + self.set_question_type() def check_at_least_one_option(self): if len(self.options) <= 1: @@ -26,6 +27,13 @@ class Question(Document): else: frappe.throw(_("A qustion must have at least one correct options")) + def set_question_type(self): + correct_options = [option for option in self.options if option.is_correct] + if len(correct_options) > 1: + self.question_type = "Multiple Correct Answer" + else: + self.question_type = "Single Correct Answer" + def get_answer(self): options = self.options answers = [item.name for item in options if item.is_correct == True] diff --git a/erpnext/education/doctype/quiz/quiz.json b/erpnext/education/doctype/quiz/quiz.json index f91bc0f021..b4903fc285 100644 --- a/erpnext/education/doctype/quiz/quiz.json +++ b/erpnext/education/doctype/quiz/quiz.json @@ -1,299 +1,105 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, "autoname": "field:title", - "beta": 0, "creation": "2018-10-17 05:52:50.149904", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "title", + "question", + "quiz_configuration_section", + "passing_score", + "max_attempts", + "grading_basis" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "title", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Title", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, "unique": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "question", "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Question", - "length": 0, - "no_copy": 0, "options": "Quiz Question", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "quiz_configuration_section", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Quiz Configuration", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Quiz Configuration" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "75", + "description": "Score out of 100", "fieldname": "passing_score", "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Passing Score", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "1", "description": "Enter 0 to waive limit", "fieldname": "max_attempts", "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Max Attempts", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Last Highest Score", + "default": "Latest Highest Score", "fieldname": "grading_basis", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Grading Basis", - "length": 0, - "no_copy": 0, - "options": "\nLast Attempt\nLast Highest Score", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Latest Highest Score\nLatest Attempt" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-11-25 19:07:36.190116", + "modified": "2019-05-30 18:50:54.218571", "modified_by": "Administrator", "module": "Education", "name": "Quiz", - "name_case": "", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Academics User", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "LMS User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 + "share": 1 }, { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Instructor", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 } ], "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/education/doctype/quiz/quiz.py b/erpnext/education/doctype/quiz/quiz.py index 6da50a6e25..8e54745464 100644 --- a/erpnext/education/doctype/quiz/quiz.py +++ b/erpnext/education/doctype/quiz/quiz.py @@ -7,51 +7,47 @@ import frappe from frappe.model.document import Document class Quiz(Document): + def validate(self): + if self.passing_score > 100: + frappe.throw("Passing Score value should be between 0 and 100") - def validate_quiz_attempts(self, enrollment, quiz_name): - if self.max_attempts > 0: - try: - if len(frappe.get_all("Quiz Activity", {'enrollment': enrollment.name, 'quiz': quiz_name})) >= self.max_attempts: - frappe.throw('Maximum attempts reached!') - except Exception as e: - pass + def allowed_attempt(self, enrollment, quiz_name): + if self.max_attempts == 0: + return True + + try: + if len(frappe.get_all("Quiz Activity", {'enrollment': enrollment.name, 'quiz': quiz_name})) >= self.max_attempts: + frappe.msgprint("Maximum attempts for this quiz reached!") + return False + else: + return True + except Exception as e: + return False def evaluate(self, response_dict, quiz_name): - # self.validate_quiz_attempts(enrollment, quiz_name) questions = [frappe.get_doc('Question', question.question_link) for question in self.question] answers = {q.name:q.get_answer() for q in questions} - correct_answers = {} + result = {} for key in answers: try: if isinstance(response_dict[key], list): - result = compare_list_elementwise(response_dict[key], answers[key]) + is_correct = compare_list_elementwise(response_dict[key], answers[key]) else: - result = (response_dict[key] == answers[key]) - except: - result = False - correct_answers[key] = result - score = (sum(correct_answers.values()) * 100 ) / len(answers) + is_correct = (response_dict[key] == answers[key]) + except Exception as e: + is_correct = False + result[key] = is_correct + score = (sum(result.values()) * 100 ) / len(answers) if score >= self.passing_score: status = "Pass" else: status = "Fail" - return correct_answers, score, status + return result, score, status def get_questions(self): - quiz_question = self.get_all_children() - if quiz_question: - questions = [frappe.get_doc('Question', question.question_link).as_dict() for question in quiz_question] - for question in questions: - correct_options = [option.is_correct for option in question.options] - if sum(correct_options) > 1: - question['type'] = "MultipleChoice" - else: - question['type'] = "SingleChoice" - return questions - else: - return None + return [frappe.get_doc('Question', question.question_link) for question in self.question] def compare_list_elementwise(*args): try: diff --git a/erpnext/education/doctype/quiz_result/quiz_result.json b/erpnext/education/doctype/quiz_result/quiz_result.json index 86505ac756..67c7e2d449 100644 --- a/erpnext/education/doctype/quiz_result/quiz_result.json +++ b/erpnext/education/doctype/quiz_result/quiz_result.json @@ -1,145 +1,52 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-10-15 15:52:25.766374", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "creation": "2018-10-15 15:52:25.766374", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "question", + "selected_option", + "quiz_result" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "question", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Question", - "length": 0, - "no_copy": 0, - "options": "Question", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 - }, + "fieldname": "question", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Question", + "options": "Question", + "read_only": 1, + "reqd": 1, + "set_only_once": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "selected_option", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Selected Option", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 - }, + "fieldname": "selected_option", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Selected Option", + "read_only": 1, + "set_only_once": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "quiz_result", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Result", - "length": 0, - "no_copy": 0, - "options": "\nCorrect\nWrong", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 + "fieldname": "quiz_result", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Result", + "options": "\nCorrect\nWrong", + "read_only": 1, + "reqd": 1, + "set_only_once": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2019-03-27 17:58:54.388848", - "modified_by": "Administrator", - "module": "Education", - "name": "Quiz Result", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "istable": 1, + "modified": "2019-06-03 12:52:32.267392", + "modified_by": "Administrator", + "module": "Education", + "name": "Quiz Result", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/education/doctype/student/student.py b/erpnext/education/doctype/student/student.py index 529f78d4f5..da25880c81 100644 --- a/erpnext/education/doctype/student/student.py +++ b/erpnext/education/doctype/student/student.py @@ -54,7 +54,7 @@ class Student(Document): 'send_welcome_email': 1, 'user_type': 'Website User' }) - student_user.add_roles("Student", "LMS User") + student_user.add_roles("Student") student_user.save() update_password_link = student_user.reset_password() diff --git a/erpnext/education/doctype/topic/topic.json b/erpnext/education/doctype/topic/topic.json index f47b10d780..6e748fddce 100644 --- a/erpnext/education/doctype/topic/topic.json +++ b/erpnext/education/doctype/topic/topic.json @@ -1,297 +1,104 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "field:topic_code", - "beta": 0, - "creation": "2018-12-12 11:37:39.917760", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "autoname": "field:topic_code", + "creation": "2018-12-12 11:37:39.917760", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "topic_name", + "column_break_2", + "topic_code", + "section_break_4", + "topic_content", + "description", + "hero_image" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "topic_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "topic_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Name", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "column_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "topic_code", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Code", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "fieldname": "topic_code", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Code", + "reqd": 1, "unique": 1 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "section_break_4", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_4", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "topic_content", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Topic Content", - "length": 0, - "no_copy": 0, - "options": "Topic Content", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "topic_content", + "fieldtype": "Table", + "label": "Topic Content", + "options": "Topic Content" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "hero_image", - "fieldtype": "Attach Image", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Hero Image", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "hero_image", + "fieldtype": "Attach Image", + "label": "Hero Image" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description" } - ], - "has_web_view": 0, - "hide_toolbar": 0, - "idx": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "menu_index": 0, - "modified": "2019-04-09 11:35:34.137040", - "modified_by": "Administrator", - "module": "Education", - "name": "Topic", - "name_case": "", - "owner": "Administrator", + ], + "modified": "2019-06-05 18:38:44.029711", + "modified_by": "Administrator", + "module": "Education", + "name": "Topic", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Administrator", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Administrator", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Instructor", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Instructor", + "share": 1, "write": 1 } - ], - "quick_entry": 1, - "read_only": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/education/doctype/video/video.json b/erpnext/education/doctype/video/video.json index cc8f718ba4..3d11bd256f 100644 --- a/erpnext/education/doctype/video/video.json +++ b/erpnext/education/doctype/video/video.json @@ -1,262 +1,102 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, "allow_import": 1, - "allow_rename": 0, "autoname": "field:title", - "beta": 0, "creation": "2018-10-17 05:47:13.087395", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "title", + "description", + "duration", + "provider", + "url", + "publish_date" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "title", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, + "in_list_view": 1, "label": "Title", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "reqd": 1, "unique": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "description", "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, + "in_list_view": 1, "label": "Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "duration", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Duration", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Duration" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "url", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, + "in_list_view": 1, "label": "URL", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "publish_date", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Publish Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Publish Date" + }, + { + "fieldname": "provider", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Provider", + "options": "YouTube\nVimeo", + "reqd": 1 } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-11-25 19:07:17.134288", + "modified": "2019-05-20 15:11:53.075093", "modified_by": "Administrator", "module": "Education", "name": "Video", - "name_case": "", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Academics User", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Instructor", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "LMS User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 + "share": 1 } ], "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/education/setup.py b/erpnext/education/setup.py index ed1d69e80d..5c4092849a 100644 --- a/erpnext/education/setup.py +++ b/erpnext/education/setup.py @@ -9,7 +9,8 @@ from erpnext.setup.utils import insert_record def setup_education(): - if frappe.db.exists('Academic Year', '2015-16'): + disable_desk_access_for_student_role() + if frappe.db.exists("Academic Year", "2015-16"): # already setup return create_academic_sessions() @@ -26,3 +27,22 @@ def create_academic_sessions(): {"doctype": "Academic Term", "academic_year": "2017-18", "term_name": "Semester 2"} ] insert_record(data) + +def disable_desk_access_for_student_role(): + try: + student_role = frappe.get_doc("Role", "Student") + except frappe.DoesNotExistError: + create_student_role() + return + + student_role.desk_access = 0 + student_role.save() + +def create_student_role(): + student_role = frappe.get_doc({ + "doctype": "Role", + "role_name": "Student", + "desk_access": 0, + "restrict_to_domain": "Education" + }) + student_role.insert() diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index bf766adc09..0e02712acb 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For lice from __future__ import unicode_literals, division import frappe @@ -57,9 +56,10 @@ def validate_duplicate_student(students): # LMS Utils def get_current_student(): - """ - Returns student user name, example EDU-STU-2018-00001 (Based on the naming series). - Takes email from from frappe.session.user + """Returns current student from frappe.session.user + + Returns: + object: Student Document """ email = frappe.session.user if email in ('Administrator', 'Guest'): @@ -70,44 +70,266 @@ def get_current_student(): except (IndexError, frappe.DoesNotExistError): return None -def check_super_access(): +def get_portal_programs(): + """Returns a list of all program to be displayed on the portal + Programs are returned based on the following logic + is_published and (student_is_enrolled or student_can_self_enroll) + + Returns: + list of dictionary: List of all programs and to be displayed on the portal along with access rights + """ + published_programs = frappe.get_all("Program", filters={"is_published": True}) + if not published_programs: + return None + + program_list = [frappe.get_doc("Program", program) for program in published_programs] + portal_programs = [{'program': program, 'has_access': allowed_program_access(program.name)} for program in program_list if allowed_program_access(program.name) or program.allow_self_enroll] + + return portal_programs + +def allowed_program_access(program, student=None): + """Returns enrollment status for current student + + Args: + program (string): Name of the program + student (object): instance of Student document + + Returns: + bool: Is current user enrolled or not + """ + if has_super_access(): + return True + if not student: + student = get_current_student() + if student and get_enrollment('program', program, student.name): + return True + else: + return False + +def get_enrollment(master, document, student): + """Gets enrollment for course or program + + Args: + master (string): can either be program or course + document (string): program or course name + student (string): Student ID + + Returns: + string: Enrollment Name if exists else returns empty string + """ + if master == 'program': + enrollments = frappe.get_all("Program Enrollment", filters={'student':student, 'program': document, 'docstatus': 1}) + if master == 'course': + enrollments = frappe.get_all("Course Enrollment", filters={'student':student, 'course': document}) + + if enrollments: + return enrollments[0].name + else: + return None + +@frappe.whitelist() +def enroll_in_program(program_name, student=None): + """Enroll student in program + + Args: + program_name (string): Name of the program to be enrolled into + student (string, optional): name of student who has to be enrolled, if not + provided, a student will be created from the current user + + Returns: + string: name of the program enrollment document + """ + if has_super_access(): + return + + if not student == None: + student = frappe.get_doc("Student", student) + else: + # Check if self enrollment in allowed + program = frappe.get_doc('Program', program_name) + if not program.allow_self_enroll: + return frappe.throw("You are not allowed to enroll for this course") + + student = get_current_student() + if not student: + student = create_student_from_current_user() + + # Check if student is already enrolled in program + enrollment = get_enrollment('program', program_name, student.name) + if enrollment: + return enrollment + + # Check if self enrollment in allowed + program = frappe.get_doc('Program', program_name) + if not program.allow_self_enroll: + return frappe.throw("You are not allowed to enroll for this course") + + # Enroll in program + program_enrollment = student.enroll_in_program(program_name) + return program_enrollment.name + +def has_super_access(): + """Check if user has a role that allows full access to LMS + + Returns: + bool: true if user has access to all lms content + """ current_user = frappe.get_doc('User', frappe.session.user) roles = set([role.role for role in current_user.roles]) return bool(roles & {'Administrator', 'Instructor', 'Education Manager', 'System Manager', 'Academic User'}) -def get_program_enrollment(program_name): - """ - Function to get program enrollments for a particular student for a program - """ +@frappe.whitelist() +def add_activity(course, content_type, content, program): + if has_super_access(): + return None + student = get_current_student() if not student: - return None + return frappe.throw("Student with email {0} does not exist".format(frappe.session.user), frappe.DoesNotExistError) + + enrollment = get_or_create_course_enrollment(course, program) + if content_type == 'Quiz': + return else: - enrollment = frappe.get_all("Program Enrollment", filters={'student':student.name, 'program': program_name}) - if enrollment: - return enrollment[0].name + return enrollment.add_activity(content_type, content) + +@frappe.whitelist() +def evaluate_quiz(quiz_response, quiz_name, course, program): + import json + + student = get_current_student() + + quiz_response = json.loads(quiz_response) + quiz = frappe.get_doc("Quiz", quiz_name) + result, score, status = quiz.evaluate(quiz_response, quiz_name) + + if has_super_access(): + return {'result': result, 'score': score, 'status': status} + + if student: + enrollment = get_or_create_course_enrollment(course, program) + if quiz.allowed_attempt(enrollment, quiz_name): + enrollment.add_quiz_activity(quiz_name, quiz_response, result, score, status) + return {'result': result, 'score': score, 'status': status} else: return None -def get_program_and_enrollment_status(program_name): - program = frappe.get_doc('Program', program_name) - is_enrolled = bool(get_program_enrollment(program_name)) or check_super_access() - return {'program': program, 'is_enrolled': is_enrolled} +@frappe.whitelist() +def get_quiz(quiz_name, course): + try: + quiz = frappe.get_doc("Quiz", quiz_name) + questions = quiz.get_questions() + except: + frappe.throw("Quiz {0} does not exist".format(quiz_name)) + return None -def get_course_enrollment(course_name): + questions = [{ + 'name': question.name, + 'question': question.question, + 'type': question.question_type, + 'options': [{'name': option.name, 'option': option.option} + for option in question.options], + } for question in questions] + + if has_super_access(): + return {'questions': questions, 'activity': None} + + student = get_current_student() + course_enrollment = get_enrollment("course", course, student.name) + status, score, result = check_quiz_completion(quiz, course_enrollment) + return {'questions': questions, 'activity': {'is_complete': status, 'score': score, 'result': result}} + +def get_topic_progress(topic, course_name, program): + """ + Return the porgress of a course in a program as well as the content to continue from. + :param topic_name: + :param course_name: + """ student = get_current_student() if not student: return None - enrollment_name = frappe.get_all("Course Enrollment", filters={'student': student.name, 'course':course_name}) - try: - name = enrollment_name[0].name - enrollment = frappe.get_doc("Course Enrollment", name) - return enrollment - except: + course_enrollment = get_or_create_course_enrollment(course_name, program) + progress = student.get_topic_progress(course_enrollment.name, topic) + if not progress: return None + count = sum([activity['is_complete'] for activity in progress]) + if count == 0: + return {'completed': False, 'started': False} + elif count == len(progress): + return {'completed': True, 'started': True} + elif count < len(progress): + return {'completed': False, 'started': True} + +def get_course_progress(course, program): + """ + Return the porgress of a course in a program as well as the content to continue from. + :param topic_name: + :param course_name: + """ + course_progress = [] + for course_topic in course.topics: + topic = frappe.get_doc("Topic", course_topic.topic) + progress = get_topic_progress(topic, course.name, program) + if progress: + course_progress.append(progress) + if course_progress: + number_of_completed_topics = sum([activity['completed'] for activity in course_progress]) + total_topics = len(course_progress) + if total_topics == 1: + return course_progress[0] + if number_of_completed_topics == 0: + return {'completed': False, 'started': False} + if number_of_completed_topics == total_topics: + return {'completed': True, 'started': True} + if number_of_completed_topics < total_topics: + return {'completed': False, 'started': True} + + return None + +def get_program_progress(program): + program_progress = [] + if not program.courses: + return None + for program_course in program.courses: + course = frappe.get_doc("Course", program_course.course) + progress = get_course_progress(course, program.name) + if progress: + progress['name'] = course.name + progress['course'] = course.course_name + program_progress.append(progress) + + if program_progress: + return program_progress + + return None + +def get_program_completion(program): + topics = frappe.db.sql("""select `tabcourse topic`.topic, `tabcourse topic`.parent + from `tabcourse topic`, + `tabprogram course` + where `tabcourse topic`.parent = `tabprogram course`.course + and `tabprogram course`.parent = %s""", program.name) + + progress = [] + for topic in topics: + topic_doc = frappe.get_doc('Topic', topic[0]) + topic_progress = get_topic_progress(topic_doc, topic[1], program.name) + if topic_progress: + progress.append(topic_progress) + + if progress: + number_of_completed_topics = sum([activity['completed'] for activity in progress if activity]) + total_topics = len(progress) + try: + return int((float(number_of_completed_topics)/total_topics)*100) + except ZeroDivisionError: + return 0 + + return 0 def create_student_from_current_user(): user = frappe.get_doc("User", frappe.session.user) + student = frappe.get_doc({ "doctype": "Student", "first_name": user.first_name, @@ -115,12 +337,21 @@ def create_student_from_current_user(): "student_email_id": user.email, "user": frappe.session.user }) + student.save(ignore_permissions=True) return student -def enroll_in_course(course_name, program_name): +def get_or_create_course_enrollment(course, program): student = get_current_student() - return student.enroll_in_course(course_name=course_name, program_enrollment=get_program_enrollment(program_name)) + course_enrollment = get_enrollment("course", course, student.name) + if not course_enrollment: + program_enrollment = get_enrollment('program', program, student.name) + if not program_enrollment: + frappe.throw("You are not enrolled in program {0}".format(program)) + return + return student.enroll_in_course(course_name=course, program_enrollment=get_enrollment('program', program, student.name)) + else: + return frappe.get_doc('Course Enrollment', course_enrollment) def check_content_completion(content_name, content_type, enrollment_name): activity = frappe.get_all("Course Activity", filters={'enrollment': enrollment_name, 'content_type': content_type, 'content': content_name}) @@ -131,7 +362,7 @@ def check_content_completion(content_name, content_type, enrollment_name): def check_quiz_completion(quiz, enrollment_name): attempts = frappe.get_all("Quiz Activity", filters={'enrollment': enrollment_name, 'quiz': quiz.name}, fields=["name", "activity_date", "score", "status"]) - status = False if quiz.max_attempts == 0 else bool(len(attempts) == quiz.max_attempts) + status = False if quiz.max_attempts == 0 else bool(len(attempts) >= quiz.max_attempts) score = None result = None if attempts: diff --git a/erpnext/public/build.json b/erpnext/public/build.json index bb1980321b..be7189bb96 100644 --- a/erpnext/public/build.json +++ b/erpnext/public/build.json @@ -55,8 +55,5 @@ "stock/dashboard/item_dashboard.html", "stock/dashboard/item_dashboard_list.html", "stock/dashboard/item_dashboard.js" - ], - "js/lms.min.js": [ - "public/js/education/lms/lms.js" ] } diff --git a/erpnext/public/js/education/lms/call.js b/erpnext/public/js/education/lms/call.js deleted file mode 100644 index e35acbdd75..0000000000 --- a/erpnext/public/js/education/lms/call.js +++ /dev/null @@ -1,15 +0,0 @@ -frappe.ready(() => { - frappe.provide('lms'); - - lms.call = (method, args) => { - const method_path = 'erpnext.www.lms.' + method; - return new Promise((resolve, reject) => { - return frappe.call({ - method: method_path, - args, - }) - .then(r => resolve(r.message)) - .fail(reject); - }); - }; -}); \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/Article.vue b/erpnext/public/js/education/lms/components/Article.vue deleted file mode 100644 index eab1424455..0000000000 --- a/erpnext/public/js/education/lms/components/Article.vue +++ /dev/null @@ -1,44 +0,0 @@ - - diff --git a/erpnext/public/js/education/lms/components/Breadcrumb.vue b/erpnext/public/js/education/lms/components/Breadcrumb.vue deleted file mode 100644 index 1b617a3751..0000000000 --- a/erpnext/public/js/education/lms/components/Breadcrumb.vue +++ /dev/null @@ -1,56 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/Button.vue b/erpnext/public/js/education/lms/components/Button.vue deleted file mode 100644 index 4d8df4b314..0000000000 --- a/erpnext/public/js/education/lms/components/Button.vue +++ /dev/null @@ -1,25 +0,0 @@ - - diff --git a/erpnext/public/js/education/lms/components/CardList.vue b/erpnext/public/js/education/lms/components/CardList.vue deleted file mode 100644 index 10f6af096c..0000000000 --- a/erpnext/public/js/education/lms/components/CardList.vue +++ /dev/null @@ -1,28 +0,0 @@ - - - diff --git a/erpnext/public/js/education/lms/components/ContentNavigation.vue b/erpnext/public/js/education/lms/components/ContentNavigation.vue deleted file mode 100644 index a07c0f85f4..0000000000 --- a/erpnext/public/js/education/lms/components/ContentNavigation.vue +++ /dev/null @@ -1,40 +0,0 @@ - - - - - diff --git a/erpnext/public/js/education/lms/components/ContentTitle.vue b/erpnext/public/js/education/lms/components/ContentTitle.vue deleted file mode 100644 index a488ab85c3..0000000000 --- a/erpnext/public/js/education/lms/components/ContentTitle.vue +++ /dev/null @@ -1,29 +0,0 @@ - - - - - diff --git a/erpnext/public/js/education/lms/components/CourseCard.vue b/erpnext/public/js/education/lms/components/CourseCard.vue deleted file mode 100644 index 48a9f591c7..0000000000 --- a/erpnext/public/js/education/lms/components/CourseCard.vue +++ /dev/null @@ -1,87 +0,0 @@ - - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/Navbar.vue b/erpnext/public/js/education/lms/components/Navbar.vue deleted file mode 100644 index f3f3ce4cbb..0000000000 --- a/erpnext/public/js/education/lms/components/Navbar.vue +++ /dev/null @@ -1,85 +0,0 @@ - - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/ProfileInfo.vue b/erpnext/public/js/education/lms/components/ProfileInfo.vue deleted file mode 100644 index 5bad713997..0000000000 --- a/erpnext/public/js/education/lms/components/ProfileInfo.vue +++ /dev/null @@ -1,83 +0,0 @@ - - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/ProgramCard.vue b/erpnext/public/js/education/lms/components/ProgramCard.vue deleted file mode 100644 index 15a9fcdcd2..0000000000 --- a/erpnext/public/js/education/lms/components/ProgramCard.vue +++ /dev/null @@ -1,82 +0,0 @@ - - - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/ProgressCard.vue b/erpnext/public/js/education/lms/components/ProgressCard.vue deleted file mode 100644 index 66b61f694e..0000000000 --- a/erpnext/public/js/education/lms/components/ProgressCard.vue +++ /dev/null @@ -1,89 +0,0 @@ - - - diff --git a/erpnext/public/js/education/lms/components/Quiz.vue b/erpnext/public/js/education/lms/components/Quiz.vue deleted file mode 100644 index 0a6199a756..0000000000 --- a/erpnext/public/js/education/lms/components/Quiz.vue +++ /dev/null @@ -1,119 +0,0 @@ - - - - - diff --git a/erpnext/public/js/education/lms/components/Quiz/QuizMultipleChoice.vue b/erpnext/public/js/education/lms/components/Quiz/QuizMultipleChoice.vue deleted file mode 100644 index 338b1ac0c5..0000000000 --- a/erpnext/public/js/education/lms/components/Quiz/QuizMultipleChoice.vue +++ /dev/null @@ -1,34 +0,0 @@ - - - - - diff --git a/erpnext/public/js/education/lms/components/Quiz/QuizSingleChoice.vue b/erpnext/public/js/education/lms/components/Quiz/QuizSingleChoice.vue deleted file mode 100644 index 235cbce4ae..0000000000 --- a/erpnext/public/js/education/lms/components/Quiz/QuizSingleChoice.vue +++ /dev/null @@ -1,28 +0,0 @@ - - - - - diff --git a/erpnext/public/js/education/lms/components/ScoreCard.vue b/erpnext/public/js/education/lms/components/ScoreCard.vue deleted file mode 100644 index 80b12cb6f6..0000000000 --- a/erpnext/public/js/education/lms/components/ScoreCard.vue +++ /dev/null @@ -1,60 +0,0 @@ - - - diff --git a/erpnext/public/js/education/lms/components/TopSection.vue b/erpnext/public/js/education/lms/components/TopSection.vue deleted file mode 100644 index c27d0031ef..0000000000 --- a/erpnext/public/js/education/lms/components/TopSection.vue +++ /dev/null @@ -1,27 +0,0 @@ - - - diff --git a/erpnext/public/js/education/lms/components/TopSectionButton.vue b/erpnext/public/js/education/lms/components/TopSectionButton.vue deleted file mode 100644 index 0fa49d4da5..0000000000 --- a/erpnext/public/js/education/lms/components/TopSectionButton.vue +++ /dev/null @@ -1,49 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/TopicCard.vue b/erpnext/public/js/education/lms/components/TopicCard.vue deleted file mode 100644 index 4cb8e85c3b..0000000000 --- a/erpnext/public/js/education/lms/components/TopicCard.vue +++ /dev/null @@ -1,112 +0,0 @@ - - - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/Video.vue b/erpnext/public/js/education/lms/components/Video.vue deleted file mode 100644 index 50b4dd460d..0000000000 --- a/erpnext/public/js/education/lms/components/Video.vue +++ /dev/null @@ -1,63 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/VideoModal.vue b/erpnext/public/js/education/lms/components/VideoModal.vue deleted file mode 100644 index 71227ade2c..0000000000 --- a/erpnext/public/js/education/lms/components/VideoModal.vue +++ /dev/null @@ -1,35 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/YoutubePlayer.vue b/erpnext/public/js/education/lms/components/YoutubePlayer.vue deleted file mode 100644 index 9377b57d3b..0000000000 --- a/erpnext/public/js/education/lms/components/YoutubePlayer.vue +++ /dev/null @@ -1,36 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/lms.js b/erpnext/public/js/education/lms/lms.js deleted file mode 100644 index 4665b144c2..0000000000 --- a/erpnext/public/js/education/lms/lms.js +++ /dev/null @@ -1,81 +0,0 @@ -import Vue from 'vue/dist/vue.js'; -import VueRouter from 'vue-router/dist/vue-router.js'; -import moment from 'moment/min/moment.min.js'; - -import lmsRoot from "./lmsRoot.vue"; -import routes from './routes'; -import './call'; - -Vue.use(VueRouter); - -var store = { - enrolledPrograms: [], - enrolledCourses: [] -}; - -// let profile_page = ` LMS Profile ` -// document.querySelector('#website-post-login > ul').innerHTML += profile_page - -frappe.ready(() => { - frappe.provide('lms'); - - lms.moment = moment; - - lms.store = new Vue({ - data: store, - methods: { - updateEnrolledPrograms() { - if(this.checkLogin()) { - lms.call("get_program_enrollments").then(data => { - this.enrolledPrograms = data; - }); - } - }, - updateEnrolledCourses() { - if(this.checkLogin()) { - lms.call("get_all_course_enrollments").then(data => { - this.enrolledCourses = data; - }); - } - }, - checkLogin() { - return frappe.is_user_logged_in(); - }, - updateState() { - this.checkLogin(); - this.updateEnrolledPrograms(); - this.updateEnrolledCourses(); - }, - checkProgramEnrollment(programName) { - if(this.checkLogin()){ - if(this.enrolledPrograms) { - if(this.enrolledPrograms.includes(programName)) { - return true; - } - else { - return false; - } - } - else { - return false; - } - } - else { - return false; - } - } - } - }); - lms.view = new Vue({ - el: "#lms-app", - router: new VueRouter({ routes }), - template: "", - components: { lmsRoot }, - mounted() { - lms.store.updateState(); - } - }); - lms.view.$router.afterEach((to, from) => { - window.scrollTo(0,0); - }); -}); \ No newline at end of file diff --git a/erpnext/public/js/education/lms/lmsRoot.vue b/erpnext/public/js/education/lms/lmsRoot.vue deleted file mode 100644 index d359265c58..0000000000 --- a/erpnext/public/js/education/lms/lmsRoot.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - diff --git a/erpnext/public/js/education/lms/pages/ContentPage.vue b/erpnext/public/js/education/lms/pages/ContentPage.vue deleted file mode 100644 index 224ee03a4a..0000000000 --- a/erpnext/public/js/education/lms/pages/ContentPage.vue +++ /dev/null @@ -1,84 +0,0 @@ - - - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/pages/CoursePage.vue b/erpnext/public/js/education/lms/pages/CoursePage.vue deleted file mode 100644 index dc3d13052b..0000000000 --- a/erpnext/public/js/education/lms/pages/CoursePage.vue +++ /dev/null @@ -1,49 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/pages/Home.vue b/erpnext/public/js/education/lms/pages/Home.vue deleted file mode 100644 index 6554a76587..0000000000 --- a/erpnext/public/js/education/lms/pages/Home.vue +++ /dev/null @@ -1,48 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/pages/ListPage.vue b/erpnext/public/js/education/lms/pages/ListPage.vue deleted file mode 100644 index cf5cecce9c..0000000000 --- a/erpnext/public/js/education/lms/pages/ListPage.vue +++ /dev/null @@ -1,53 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/pages/ProfilePage.vue b/erpnext/public/js/education/lms/pages/ProfilePage.vue deleted file mode 100644 index beff5eb34e..0000000000 --- a/erpnext/public/js/education/lms/pages/ProfilePage.vue +++ /dev/null @@ -1,50 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/pages/ProgramPage.vue b/erpnext/public/js/education/lms/pages/ProgramPage.vue deleted file mode 100644 index 415c861e81..0000000000 --- a/erpnext/public/js/education/lms/pages/ProgramPage.vue +++ /dev/null @@ -1,49 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/quiz.js b/erpnext/public/js/education/lms/quiz.js new file mode 100644 index 0000000000..1b520eb9f5 --- /dev/null +++ b/erpnext/public/js/education/lms/quiz.js @@ -0,0 +1,186 @@ +class Quiz { + constructor(wrapper, options) { + this.wrapper = wrapper; + Object.assign(this, options); + this.questions = [] + this.refresh(); + } + + refresh() { + this.get_quiz(); + } + + get_quiz() { + frappe.call('erpnext.education.utils.get_quiz', { + quiz_name: this.name, + course: this.course + }).then(res => { + this.make(res.message) + }); + } + + make(data) { + data.questions.forEach(question_data => { + let question_wrapper = document.createElement('div'); + let question = new Question({ + wrapper: question_wrapper, + ...question_data + }); + this.questions.push(question) + this.wrapper.appendChild(question_wrapper); + }) + if (data.activity.is_complete) { + this.disable() + let indicator = 'red' + let message = 'Your are not allowed to attempt the quiz again.' + if (data.activity.result == 'Pass') { + indicator = 'green' + message = 'You have already cleared the quiz.' + } + + this.set_quiz_footer(message, indicator, data.activity.score) + } + else { + this.make_actions(); + } + } + + make_actions() { + const button = document.createElement("button"); + button.classList.add("btn", "btn-primary", "mt-5", "mr-2"); + + button.id = 'submit-button'; + button.innerText = 'Submit'; + button.onclick = () => this.submit(); + this.submit_btn = button + this.wrapper.appendChild(button); + } + + submit() { + this.submit_btn.innerText = 'Evaluating..' + this.submit_btn.disabled = true + this.disable() + frappe.call('erpnext.education.utils.evaluate_quiz', { + quiz_name: this.name, + quiz_response: this.get_selected(), + course: this.course, + program: this.program + }).then(res => { + this.submit_btn.remove() + if (!res.message) { + frappe.throw("Something went wrong while evaluating the quiz.") + } + + let indicator = 'red' + let message = 'Fail' + if (res.message.status == 'Pass') { + indicator = 'green' + message = 'Congratulations, you cleared the quiz.' + } + + this.set_quiz_footer(message, indicator, res.message.score) + }); + } + + set_quiz_footer(message, indicator, score) { + const div = document.createElement("div"); + div.classList.add("mt-5"); + div.innerHTML = `
+
+

${message}

+
Score: ${score}/100
+
+ +
` + + this.wrapper.appendChild(div) + } + + disable() { + this.questions.forEach(que => que.disable()) + } + + get_selected() { + let que = {} + this.questions.forEach(question => { + que[question.name] = question.get_selected() + }) + return que + } +} + +class Question { + constructor(opts) { + Object.assign(this, opts); + this.make(); + } + + make() { + this.make_question() + this.make_options() + } + + get_selected() { + let selected = this.options.filter(opt => opt.input.checked) + if (this.type == 'Single Correct Answer') { + if (selected[0]) return selected[0].name + } + if (this.type == 'Multiple Correct Answer') { + return selected.map(opt => opt.name) + } + return null + } + + disable() { + let selected = this.options.forEach(opt => opt.input.disabled = true) + } + + make_question() { + let question_wrapper = document.createElement('h5'); + question_wrapper.classList.add('mt-3'); + question_wrapper.innerText = this.question; + this.wrapper.appendChild(question_wrapper); + } + + make_options() { + let make_input = (name, value) => { + let input = document.createElement('input'); + input.id = name; + input.name = this.name; + input.value = value; + input.type = 'radio'; + if (this.type == 'Multiple Correct Answer') + input.type = 'checkbox'; + input.classList.add('form-check-input'); + return input; + } + + let make_label = function(name, value) { + let label = document.createElement('label'); + label.classList.add('form-check-label'); + label.htmlFor = name; + label.innerText = value; + return label + } + + let make_option = function (wrapper, option) { + let option_div = document.createElement('div') + option_div.classList.add('form-check', 'pb-1') + let input = make_input(option.name, option.option); + let label = make_label(option.name, option.option); + option_div.appendChild(input) + option_div.appendChild(label) + wrapper.appendChild(option_div) + return {input: input, ...option} + } + + let options_wrapper = document.createElement('div') + options_wrapper.classList.add('ml-2') + let option_list = [] + this.options.forEach(opt => option_list.push(make_option(options_wrapper, opt))) + this.options = option_list + this.wrapper.appendChild(options_wrapper) + } +} \ No newline at end of file diff --git a/erpnext/public/js/education/lms/routes.js b/erpnext/public/js/education/lms/routes.js deleted file mode 100644 index 483f2220c3..0000000000 --- a/erpnext/public/js/education/lms/routes.js +++ /dev/null @@ -1,92 +0,0 @@ -import Home from "./pages/Home.vue"; -import ProgramPage from "./pages/ProgramPage.vue"; -import CoursePage from "./pages/CoursePage.vue"; -import ContentPage from "./pages/ContentPage.vue"; -import ListPage from "./pages/ListPage.vue"; -import ProfilePage from "./pages/ProfilePage.vue"; - -const routes = [{ - name: 'home', - path: '', - component: Home -}, -{ - name: 'program', - path: '/Program/:program_name', - component: ProgramPage, - props: true -}, -{ - name: 'course', - path: '/Program/:program_name/:course_name/', - component: CoursePage, - props: true, -}, -{ - name: 'content', - path: '/Program/:program_name/:course_name/:topic/:type/:content', - component: ContentPage, - props: true, - beforeRouteUpdate (to, from, next) { - if (lms.store.checkProgramEnrollment(to.params.program_name)) { - next(); - } else { - next({ - name: 'program', - params: { - program_name: to.params.program_name - } - }); - } - } -}, -{ - name: 'list', - path: '/List/:master', - component: ListPage, - props: true -}, -{ - name: 'signup', - path: '/Signup', - beforeEnter(to, from, next) { - window.location = window.location.origin.toString() + '/login#signup'; - }, - component: Home, - props: true -}, -{ - name: 'login', - path: '/Login', - beforeEnter(to, from, next) { - window.location = window.location.origin.toString() + '/login#login'; - }, - component: Home, - props: true -}, -{ - name: 'logout', - path: '/Logout', - beforeEnter(to, from, next) { - window.location = window.location.origin.toString() + '/?cmd=web_logout'; - }, - component: Home, - props: true -}, -{ - name: 'profile', - path: '/Profile', - component: ProfilePage, - props: true, - beforeEnter: (to, from, next) => { - if (!lms.store.checkLogin()) { - next({ - name: 'home' - }); - } else { - next(); - } - } -}]; - -export default routes; \ No newline at end of file diff --git a/erpnext/www/lms.html b/erpnext/www/lms.html deleted file mode 100644 index aa76ca06e7..0000000000 --- a/erpnext/www/lms.html +++ /dev/null @@ -1,39 +0,0 @@ -{% extends "templates/web.html" %} - -{% block title %}{{ heading or "LMS"}}{% endblock %} - -{% block navbar %}{% endblock %} - -{% block content %} -{% if lms_enabled %} -
- -{% else %} - - -
-
- {{_("Page Missing or Moved")}} -
-

{{_("The page you are looking for is missing. This could be because it is moved or there is a typo in the link.")}}

-
{{ _("Home") }}
-
-

{{ _("Error Code: {0}").format('404') }}

- -{% endif %} -{% endblock %} \ No newline at end of file diff --git a/erpnext/www/lms.py b/erpnext/www/lms.py deleted file mode 100644 index 7561d73d20..0000000000 --- a/erpnext/www/lms.py +++ /dev/null @@ -1,242 +0,0 @@ -from __future__ import unicode_literals -import erpnext.education.utils as utils -import frappe -from frappe import _ - -# LMS Utils to Update State for Vue Store -@frappe.whitelist() -def get_program_enrollments(): - student = utils.get_current_student() - if student == None: - return None - return student.get_program_enrollments() - -@frappe.whitelist() -def get_all_course_enrollments(): - student = utils.get_current_student() - if student == None: - return None - return student.get_all_course_enrollments() - -# Vue Client Functions -@frappe.whitelist(allow_guest=True) -def get_portal_details(): - """ - Returns portal details from Education Settings Doctype. This contains the Title and Description for LMS amoung other things. - """ - from erpnext import get_default_company - - settings = frappe.get_doc("Education Settings") - title = settings.portal_title or get_default_company() - description = settings.description - return dict(title=title, description=description) - -@frappe.whitelist(allow_guest=True) -def get_featured_programs(): - featured_program_names = frappe.get_all("Program", filters={"is_published": True, "is_featured": True}) - if featured_program_names: - featured_list = [utils.get_program_and_enrollment_status(program['name']) for program in featured_program_names] - return featured_list - else: - return get_all_programs()[:2] - -@frappe.whitelist(allow_guest=True) -def get_all_programs(): - program_names = frappe.get_all("Program", filters={"is_published": True}) - if program_names: - program_list = [utils.get_program_and_enrollment_status(program['name']) for program in program_names] - return program_list - -@frappe.whitelist(allow_guest=True) -def get_program(program_name): - try: - return frappe.get_doc('Program', program_name) - except frappe.DoesNotExistError: - frappe.throw(_("Program {0} does not exist.".format(program_name))) - -# Functions to get program & course details -@frappe.whitelist(allow_guest=True) -def get_courses(program_name): - program = frappe.get_doc('Program', program_name) - courses = program.get_course_list() - return courses - -@frappe.whitelist() -def get_next_content(current_content, current_content_type, topic): - if frappe.session.user == "Guest": - return None - topic = frappe.get_doc("Topic", topic) - content_list = [{'content_type':item.doctype, 'content':item.name} for item in topic.get_contents()] - current_index = content_list.index({'content': current_content, 'content_type': current_content_type}) - try: - return content_list[current_index + 1] - except IndexError: - return None - -def get_quiz_with_answers(quiz_name): - try: - quiz = frappe.get_doc("Quiz", quiz_name).get_questions() - quiz_output = [{'name':question.name, 'question':question.question, 'options':[{'name': option.name, 'option':option.option, 'is_correct':option.is_correct} for option in question.options]} for question in quiz] - return quiz_output - except: - frappe.throw("Quiz {0} does not exist".format(quiz_name)) - return None - -@frappe.whitelist() -def get_quiz_without_answers(quiz_name, course_name): - try: - quiz = frappe.get_doc("Quiz", quiz_name) - questions = quiz.get_questions() - except: - frappe.throw("Quiz {0} does not exist".format(quiz_name)) - return None - - if utils.check_super_access(): - quiz_output = [{'name':question.name, 'question':question.question, 'type': question.type, 'options':[{'name': option.name, 'option':option.option} for option in question.options]} for question in questions] - return { 'quizData': quiz_output, 'status': None} - - enrollment = utils.get_course_enrollment(course_name).name - quiz_progress = {} - quiz_progress['is_complete'], quiz_progress['score'], quiz_progress['result'] = utils.check_quiz_completion(quiz, enrollment) - quiz_output = [{'name':question.name, 'question':question.question, 'type': question.type, 'options':[{'name': option.name, 'option':option.option} for option in question.options]} for question in questions] - return { 'quizData': quiz_output, 'status': quiz_progress} - -@frappe.whitelist() -def evaluate_quiz(course, quiz_response, quiz_name): - """LMS Function: Evaluates a simple multiple choice quiz. - :param course: name of the course - :param quiz_response: contains user selected choices for a quiz in the form of a string formatted as a dictionary. The function uses `json.loads()` to convert it to a python dictionary. - :param quiz_name: Name of the quiz attempted - """ - import json - quiz_response = json.loads(quiz_response) - quiz = frappe.get_doc("Quiz", quiz_name) - answers, score, status = quiz.evaluate(quiz_response, quiz_name) - print(answers) - - course_enrollment = utils.get_course_enrollment(course) - if course_enrollment: - course_enrollment.add_quiz_activity(quiz_name, quiz_response, answers, score, status) - - return score - -@frappe.whitelist() -def enroll_in_program(program_name): - student = utils.get_current_student() - if not student: - student = utils.create_student_from_current_user() - program_enrollment = student.enroll_in_program(program_name) - return program_name - -# Academdy Activity -@frappe.whitelist() -def add_activity(course, content_type, content): - if not utils.get_current_student(): - return - enrollment = utils.get_course_enrollment(course) - enrollment.add_activity(content_type, content) - -@frappe.whitelist() -def get_student_course_details(course_name, program_name): - """ - Return the porgress of a course in a program as well as the content to continue from. - :param course_name: - :param program_name: - """ - student = utils.get_current_student() - if not student: - return {'flag':'Start Course' } - - course_enrollment = utils.get_course_enrollment(course_name) - program_enrollment = utils.get_program_enrollment(program_name) - - if not program_enrollment: - return None - - if not course_enrollment: - course_enrollment = utils.enroll_in_course(course_name, program_name) - - progress = course_enrollment.get_progress(student) - count = sum([activity['is_complete'] for activity in progress]) - if count == 0: - return {'flag':'Start Course'} - elif count == len(progress): - return {'flag':'Completed'} - elif count < len(progress): - next_item = next(item for item in progress if item['is_complete']==False) - return {'flag':'Continue'} - -@frappe.whitelist() -def get_student_topic_details(topic_name, course_name): - """ - Return the porgress of a course in a program as well as the content to continue from. - :param topic_name: - :param course_name: - """ - topic = frappe.get_doc("Topic", topic_name) - student = utils.get_current_student() - if not student: - topic_content = topic.get_all_children() - if topic_content: - return {'flag':'Start Course', 'content_type': topic_content[0].content_type, 'content': topic_content[0].content} - else: - return None - course_enrollment = utils.get_course_enrollment(course_name) - progress = student.get_topic_progress(course_enrollment.name, topic) - if not progress: - return { 'flag':'Start Topic', 'content_type': None, 'content': None } - count = sum([activity['is_complete'] for activity in progress]) - if count == 0: - return {'flag':'Start Topic', 'content_type': progress[0]['content_type'], 'content': progress[0]['content']} - elif count == len(progress): - return {'flag':'Completed', 'content_type': progress[0]['content_type'], 'content': progress[0]['content']} - elif count < len(progress): - next_item = next(item for item in progress if item['is_complete']==False) - return {'flag':'Continue', 'content_type': next_item['content_type'], 'content': next_item['content']} - -@frappe.whitelist() -def get_program_progress(program_name): - program_enrollment = frappe.get_doc("Program Enrollment", utils.get_program_enrollment(program_name)) - if not program_enrollment: - return None - else: - return program_enrollment.get_program_progress() - -@frappe.whitelist() -def get_joining_date(): - student = utils.get_current_student() - if student: - return student.joining_date - -@frappe.whitelist() -def get_quiz_progress_of_program(program_name): - program_enrollment = frappe.get_doc("Program Enrollment", utils.get_program_enrollment(program_name)) - if not program_enrollment: - return None - else: - return program_enrollment.get_quiz_progress() - - -@frappe.whitelist(allow_guest=True) -def get_course_details(course_name): - try: - course = frappe.get_doc('Course', course_name) - return course - except: - return None - -# Functions to get program & course details -@frappe.whitelist(allow_guest=True) -def get_topics(course_name): - try: - course = frappe.get_doc('Course', course_name) - return course.get_topics() - except frappe.DoesNotExistError: - frappe.throw(_("Course {0} does not exist.".format(course_name))) - -@frappe.whitelist() -def get_content(content_type, content): - try: - return frappe.get_doc(content_type, content) - except frappe.DoesNotExistError: - frappe.throw(_("{0} {1} does not exist.".format(content_type, content))) \ No newline at end of file diff --git a/erpnext/www/lms/__init__.py b/erpnext/www/lms/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/www/lms/content.html b/erpnext/www/lms/content.html new file mode 100644 index 0000000000..9b8c45cb9b --- /dev/null +++ b/erpnext/www/lms/content.html @@ -0,0 +1,208 @@ +{% extends "templates/base.html" %} +{% block title %}{{ content.name or 'Content Page' }}{% endblock %} + +{% block head_include %} + + +{% endblock %} + +{% macro title() %} +
+ + Back to Course + +
+
+

{{ content.name }} ({{ position + 1 }}/{{length}})

+
+{% endmacro %} + +{% macro navigation() %} + {% if previous %} + Previous + {% else %} + Back to Course + {% endif %} + + {% if next %} + + {% else %} + + {% endif %} +{% endmacro %} + +{% macro video() %} +
+ {{ title() }} +
+ {% if content.duration %} + {{ content.duration }} Mins + {% endif %} + + {% if content.publish_date and content.duration%} + - + {% endif %} + + {% if content.publish_date %} + Published on {{ content.publish_date.strftime('%d, %b %Y') }} + {% endif %} +
+
+
+
+ {{ content.description }} +
+{% endmacro %} + +{% macro article() %} +
+ {{ title() }} +
+ {% if content.author or content.publish_date %} + Published + {% endif %} + {% if content.author %} + by {{ content.author }} + {% endif %} + {% if content.publish_date %} + on {{ content.publish_date.strftime('%d, %b %Y') }} + {% endif %} +
+
+
+ {{ content.content }} +
+{% endmacro %} + +{% macro quiz() %} +
+ {{ title() }} +
+
+
+{% endmacro %} + +{% block content %} +
+
+
+ {% if content_type=='Video' %} + {{ video() }} + {% elif content_type=='Article'%} + {{ article() }} + {% elif content_type=='Quiz' %} + {{ quiz() }} + {% endif %} +
+ {{ navigation() }} +
+
+
+
+{% endblock %} + +{% block script %} + {% if content_type=='Video' %} + + {% elif content_type == 'Quiz' %} + + {% endif %} + +{% endblock %} \ No newline at end of file diff --git a/erpnext/www/lms/content.py b/erpnext/www/lms/content.py new file mode 100644 index 0000000000..3ab7a9f478 --- /dev/null +++ b/erpnext/www/lms/content.py @@ -0,0 +1,68 @@ +from __future__ import unicode_literals +import erpnext.education.utils as utils +import frappe + +no_cache = 1 + +def get_context(context): + # Load Query Parameters + try: + program = frappe.form_dict['program'] + content = frappe.form_dict['content'] + content_type = frappe.form_dict['type'] + course = frappe.form_dict['course'] + topic = frappe.form_dict['topic'] + except KeyError: + frappe.local.flags.redirect_location = '/lms' + raise frappe.Redirect + + + # Check if user has access to the content + has_program_access = utils.allowed_program_access(program) + has_content_access = allowed_content_access(program, content, content_type) + + if frappe.session.user == "Guest" or not has_program_access or not has_content_access: + frappe.local.flags.redirect_location = '/lms' + raise frappe.Redirect + + + # Set context for content to be displayer + context.content = frappe.get_doc(content_type, content).as_dict() + context.content_type = content_type + context.program = program + context.course = course + context.topic = topic + + topic = frappe.get_doc("Topic", topic) + content_list = [{'content_type':item.doctype, 'content':item.name} for item in topic.get_contents()] + + # Set context for progress numbers + context.position = content_list.index({'content': content, 'content_type': content_type}) + context.length = len(content_list) + + # Set context for navigation + context.previous = get_previous_content(content_list, context.position) + context.next = get_next_content(content_list, context.position) + +def get_next_content(content_list, current_index): + try: + return content_list[current_index + 1] + except IndexError: + return None + +def get_previous_content(content_list, current_index): + if current_index == 0: + return None + else: + return content_list[current_index - 1] + +def allowed_content_access(program, content, content_type): + contents_of_program = frappe.db.sql("""select `tabtopic content`.content, `tabtopic content`.content_type + from `tabcourse topic`, + `tabprogram course`, + `tabtopic content` + where `tabcourse topic`.parent = `tabprogram course`.course + and `tabtopic content`.parent = `tabcourse topic`.topic + and `tabprogram course`.parent = %(program)s""", {'program': program}) + + return (content, content_type) in contents_of_program \ No newline at end of file diff --git a/erpnext/www/lms/course.html b/erpnext/www/lms/course.html new file mode 100644 index 0000000000..0bfd059013 --- /dev/null +++ b/erpnext/www/lms/course.html @@ -0,0 +1,106 @@ +{% extends "templates/base.html" %} +{% block title %}{{ course.course_name }}{% endblock %} +{% from "www/lms/macros/hero.html" import hero %} +{% from "www/lms/macros/card.html" import null_card %} + +{% block head_include %} + +{% endblock %} + + +{% macro card(topic) %} +
+
+ {% if has_access %} + + {% else %} +
+ {% endif %} + {% if topic.hero_image %} +
+ {% else %} +
+
+
+ {% endif %} +
+ {% if has_access %} + + + {% else %} +
+ {% endif %} +
+
+{% endmacro %} + +{% block content %} +
+ {{ hero(course.course_name, course.description, has_access, {'name': 'Program', 'url': '/lms/program?program=' + program }) }} +
+
+ {% for topic in topics %} + {{ card(topic) }} + {% endfor %} + {% if topics %} + {% for n in range(3 - ((topics|length)%3)) %} + {{ null_card() }} + {% endfor %} + {% endif %} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/erpnext/www/lms/course.py b/erpnext/www/lms/course.py new file mode 100644 index 0000000000..e7ed2e3ed6 --- /dev/null +++ b/erpnext/www/lms/course.py @@ -0,0 +1,19 @@ +from __future__ import unicode_literals +import erpnext.education.utils as utils +import frappe + +no_cache = 1 + +def get_context(context): + context.education_settings = frappe.get_single("Education Settings") + course = frappe.get_doc('Course', frappe.form_dict['name']) + context.program = frappe.form_dict['program'] + context.course = course + + context.topics = course.get_topics() + context.has_access = utils.allowed_program_access(context.program) + context.progress = get_topic_progress(context.topics, course, context.program) + +def get_topic_progress(topics, course, program): + progress = {topic.name: utils.get_topic_progress(topic, course.name, program) for topic in topics} + return progress diff --git a/erpnext/www/lms/index.html b/erpnext/www/lms/index.html new file mode 100644 index 0000000000..7ea39d8a99 --- /dev/null +++ b/erpnext/www/lms/index.html @@ -0,0 +1,65 @@ +{% extends "templates/base.html" %} +{% block title %}{{ education_settings.portal_title }}{% endblock %} +{% from "www/lms/macros/card.html" import program_card %} +{% from "www/lms/macros/card.html" import null_card %} + +{% block head_include %} + + + +{% endblock %} + +{% block content %} +
+
+

{{ education_settings.portal_title }}

+

{{ education_settings.description }}

+

+ {% if frappe.session.user == 'Guest' %} + Start Learning + {% endif %} +

+
+
+
+ {% for program in featured_programs %} + {{ program_card(program.program, program.has_access) }} + {% endfor %} + {% if featured_programs %} + {% for n in range(3 - ((featured_programs|length)%3)) %} + {{ null_card() }} + {% endfor %} + {% endif %} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/erpnext/www/lms/index.py b/erpnext/www/lms/index.py new file mode 100644 index 0000000000..00f66e72c3 --- /dev/null +++ b/erpnext/www/lms/index.py @@ -0,0 +1,16 @@ +from __future__ import unicode_literals +import erpnext.education.utils as utils +import frappe + +no_cache = 1 + +def get_context(context): + context.education_settings = frappe.get_single("Education Settings") + if not context.education_settings.enable_lms: + frappe.local.flags.redirect_location = '/' + raise frappe.Redirect + context.featured_programs = get_featured_programs() + + +def get_featured_programs(): + return utils.get_portal_programs() \ No newline at end of file diff --git a/erpnext/www/lms/macros/card.html b/erpnext/www/lms/macros/card.html new file mode 100644 index 0000000000..f227355832 --- /dev/null +++ b/erpnext/www/lms/macros/card.html @@ -0,0 +1,34 @@ +{% macro program_card(program, has_access) %} +
+ + + +
+{% endmacro %} + + +{% macro null_card() %} +
+
+
+
+{% endmacro %} \ No newline at end of file diff --git a/erpnext/www/lms/macros/hero.html b/erpnext/www/lms/macros/hero.html new file mode 100644 index 0000000000..66bb861c46 --- /dev/null +++ b/erpnext/www/lms/macros/hero.html @@ -0,0 +1,55 @@ +{% macro hero(title, description, has_access, back) %} +
+ +

{{ title }}

+

{{ description or ''}}

+

+ {% if frappe.session.user == 'Guest' %} + Sign Up + {% elif not has_access %} + + {% endif %} +

+
+ +{% block script %} + +{% endblock %} +{% endmacro %} \ No newline at end of file diff --git a/erpnext/www/lms/profile.html b/erpnext/www/lms/profile.html new file mode 100644 index 0000000000..9508daedb7 --- /dev/null +++ b/erpnext/www/lms/profile.html @@ -0,0 +1,64 @@ +{% extends "templates/base.html" %} +{% block title %}Profile{% endblock %} +{% from "www/lms/macros/hero.html" import hero %} + +{% block head_include %} + +{% endblock %} + +{% macro card(program) %} +
+ + + +
+{% endmacro %} + +{% block content %} +
+
+ +

{{ student.first_name }} {{ student.last_name or '' }}

+

{{ student.name }}

+
+
+
+ {% for program in progress %} + {{ card(program) }} + {% endfor %} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/erpnext/www/lms/profile.py b/erpnext/www/lms/profile.py new file mode 100644 index 0000000000..4788ea6e70 --- /dev/null +++ b/erpnext/www/lms/profile.py @@ -0,0 +1,26 @@ +from __future__ import unicode_literals +import erpnext.education.utils as utils +import frappe + +no_cache = 1 + +def get_context(context): + if frappe.session.user == "Guest": + frappe.local.flags.redirect_location = '/lms' + raise frappe.Redirect + + context.student = utils.get_current_student() + if not context.student: + context.student = frappe.get_doc('User', frappe.session.user) + context.progress = get_program_progress(context.student.name) + +def get_program_progress(student): + enrolled_programs = frappe.get_all("Program Enrollment", filters={'student':student}, fields=['program']) + student_progress = [] + for list_item in enrolled_programs: + program = frappe.get_doc("Program", list_item.program) + progress = utils.get_program_progress(program) + completion = utils.get_program_completion(program) + student_progress.append({'program': program.program_name, 'name': program.name, 'progress':progress, 'completion': completion}) + + return student_progress \ No newline at end of file diff --git a/erpnext/www/lms/program.html b/erpnext/www/lms/program.html new file mode 100644 index 0000000000..d1227788b8 --- /dev/null +++ b/erpnext/www/lms/program.html @@ -0,0 +1,87 @@ +{% extends "templates/base.html" %} +{% block title %}{{ program.program_name }}{% endblock %} +{% from "www/lms/macros/hero.html" import hero %} +{% from "www/lms/macros/card.html" import null_card %} + +{% block head_include %} + +{% endblock %} + + +{% macro card(course) %} +
+ +
+ {% if course.hero_image %} +
+ {% else %} +
+
+
+ {% endif %} +
+
{{ course.course_name }}
+
{{ course.description[:110] + '...' if course.description else '' }}
+
+ {% if has_access and progress[course.name] %} + + {% endif %} +
+
+
+{% endmacro %} + +{% block content %} +
+ {{ hero(program.program_name, program.description, has_access, {'name': 'Home', 'url': '/lms'}) }} +
+
+ {% for course in courses %} + {{ card(course) }} + {% endfor %} + {% if courses %} + {% for n in range(3 - ((courses|length)%3)) %} + {{ null_card() }} + {% endfor %} + {% endif %} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/erpnext/www/lms/program.py b/erpnext/www/lms/program.py new file mode 100644 index 0000000000..1fcb3d3028 --- /dev/null +++ b/erpnext/www/lms/program.py @@ -0,0 +1,23 @@ +from __future__ import unicode_literals +import erpnext.education.utils as utils +import frappe +from frappe import _ + +no_cache = 1 + +def get_context(context): + context.education_settings = frappe.get_single("Education Settings") + context.program = get_program(frappe.form_dict['program']) + context.courses = [frappe.get_doc("Course", course.course) for course in context.program.courses] + context.has_access = utils.allowed_program_access(frappe.form_dict['program']) + context.progress = get_course_progress(context.courses, context.program) + +def get_program(program_name): + try: + return frappe.get_doc('Program', program_name) + except frappe.DoesNotExistError: + frappe.throw(_("Program {0} does not exist.".format(program_name))) + +def get_course_progress(courses, program): + progress = {course.name: utils.get_course_progress(course, program) for course in courses} + return progress \ No newline at end of file diff --git a/erpnext/www/lms/topic.html b/erpnext/www/lms/topic.html new file mode 100644 index 0000000000..3a777765a1 --- /dev/null +++ b/erpnext/www/lms/topic.html @@ -0,0 +1,58 @@ +{% extends "templates/base.html" %} +{% block title %}Topic Title{% endblock %} +{% from "www/lms/macros/hero.html" import hero %} +{% from "www/lms/macros/card.html" import null_card %} + +{% block head_include %} + +{% endblock %} + + +{% macro card(content, index, length) %} +
+ +
+
+
{{ content.content_type or '' }}
+
{{ content.content.name }}
+
+ {% if has_access %} + + {% endif %} +
+
+
+{% endmacro %} + +{% block content %} +
+ {{ hero(topic.topic_name, topic.description, has_access, {'name': 'Course', 'url': '/lms/course?name=' + course +'&program=' + program}) }} +
+
+ {% for content in contents %} + {{ card(content, loop.index, topic.contents|length) }} + {% endfor %} + {% if contents %} + {% for n in range(3 - ((contents|length)%3)) %} + {{ null_card() }} + {% endfor %} + {% endif %} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/erpnext/www/lms/topic.py b/erpnext/www/lms/topic.py new file mode 100644 index 0000000000..0af0778312 --- /dev/null +++ b/erpnext/www/lms/topic.py @@ -0,0 +1,41 @@ +from __future__ import unicode_literals +import erpnext.education.utils as utils +import frappe + +no_cache = 1 + +def get_context(context): + course = frappe.form_dict['course'] + program = frappe.form_dict['program'] + topic = frappe.form_dict['topic'] + + context.program = program + context.course = course + context.topic = frappe.get_doc("Topic", topic) + context.contents = get_contents(context.topic, course, program) + context.has_access = utils.allowed_program_access(program) + +def get_contents(topic, course, program): + student = utils.get_current_student() + if student: + course_enrollment = utils.get_or_create_course_enrollment(course, program) + contents = topic.get_contents() + progress = [] + if contents: + for content in contents: + if content.doctype in ('Article', 'Video'): + if student: + status = utils.check_content_completion(content.name, content.doctype, course_enrollment.name) + else: + status = True + progress.append({'content': content, 'content_type': content.doctype, 'completed': status}) + elif content.doctype == 'Quiz': + if student: + status, score, result = utils.check_quiz_completion(content, course_enrollment.name) + else: + status = False + score = None + result = None + progress.append({'content': content, 'content_type': content.doctype, 'completed': status, 'score': score, 'result': result}) + + return progress \ No newline at end of file diff --git a/erpnext/www/test_lms.py b/erpnext/www/test_lms.py deleted file mode 100644 index e63f4c913e..0000000000 --- a/erpnext/www/test_lms.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies and Contributors -# See license.txt -from __future__ import unicode_literals -from erpnext.education.doctype.program.test_program import make_program_and_linked_courses - -import frappe -import unittest - -class TestLms(unittest.TestCase): - pass \ No newline at end of file