diff --git a/.editorconfig b/.editorconfig index dd608df2..0984f25b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -26,4 +26,4 @@ indent_style = tab # HTML, CSS, javascript, JSON and YAML [*.{html,css,js,json,yml,yaml}] -indent_size = 2 +indent_size = 4 diff --git a/school/hooks.py b/school/hooks.py index 53f67397..0a11804c 100644 --- a/school/hooks.py +++ b/school/hooks.py @@ -162,6 +162,8 @@ update_website_context = [ jinja = { "methods": [ "school.page_renderers.get_profile_url", + "school.overrides.user.get_enrolled_courses", + "school.overrides.user.get_course_membership", "school.overrides.user.get_authored_courses", "school.overrides.user.get_palette", "school.lms.utils.get_membership", @@ -186,7 +188,8 @@ jinja = { "school.lms.utils.get_sorted_reviews", "school.lms.utils.is_instructor", "school.lms.utils.convert_number_to_character", - "school.lms.utils.get_signup_optin_checks" + "school.lms.utils.get_signup_optin_checks", + "school.lms.utils.get_popular_courses" ], "filters": [] } diff --git a/school/lms/doctype/chapter_reference/chapter_reference.json b/school/lms/doctype/chapter_reference/chapter_reference.json index a251b1a4..0c67d748 100644 --- a/school/lms/doctype/chapter_reference/chapter_reference.json +++ b/school/lms/doctype/chapter_reference/chapter_reference.json @@ -1,5 +1,6 @@ { "actions": [], + "autoname": "hash", "creation": "2021-07-27 16:25:02.903245", "doctype": "DocType", "editable_grid": 1, @@ -20,13 +21,15 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-09-30 10:35:30.014950", + "modified": "2022-03-15 09:39:41.937565", "modified_by": "Administrator", "module": "LMS", "name": "Chapter Reference", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/school/lms/doctype/course_chapter/course_chapter.json b/school/lms/doctype/course_chapter/course_chapter.json index 7dca741a..ee8544bf 100644 --- a/school/lms/doctype/course_chapter/course_chapter.json +++ b/school/lms/doctype/course_chapter/course_chapter.json @@ -59,7 +59,7 @@ "link_fieldname": "chapter" } ], - "modified": "2021-09-29 15:33:44.611229", + "modified": "2022-03-14 17:57:00.707416", "modified_by": "Administrator", "module": "LMS", "name": "Course Chapter", @@ -77,11 +77,26 @@ "role": "System Manager", "share": 1, "write": 1 + }, + { + "create": 1, + "email": 1, + "export": 1, + "if_owner": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "All", + "select": 1, + "share": 1, + "write": 1 } ], "search_fields": "title", + "show_title_field_in_link": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "title", "track_changes": 1 -} +} \ No newline at end of file diff --git a/school/lms/doctype/course_lesson/course_lesson.json b/school/lms/doctype/course_lesson/course_lesson.json index d63eb69b..c38059ac 100644 --- a/school/lms/doctype/course_lesson/course_lesson.json +++ b/school/lms/doctype/course_lesson/course_lesson.json @@ -1,6 +1,6 @@ { "actions": [], - "allow_import": 1, + "allow_events_in_timeline": 1, "allow_rename": 1, "autoname": "format:{####} {title}", "creation": "2021-05-03 06:21:12.995984", @@ -9,6 +9,7 @@ "engine": "InnoDB", "field_order": [ "chapter", + "course", "include_in_preview", "column_break_4", "title", @@ -69,11 +70,20 @@ { "fieldname": "help", "fieldtype": "HTML" + }, + { + "fetch_from": "chapter.course", + "fieldname": "course", + "fieldtype": "Link", + "hidden": 1, + "label": "Course", + "options": "LMS Course", + "read_only": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-10-11 15:07:38.134808", + "modified": "2022-03-14 18:56:31.969801", "modified_by": "Administrator", "module": "LMS", "name": "Course Lesson", @@ -89,11 +99,26 @@ "read": 1, "report": 1, "role": "System Manager", + "select": 1, + "share": 1, + "write": 1 + }, + { + "create": 1, + "email": 1, + "export": 1, + "if_owner": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "All", + "select": 1, "share": 1, "write": 1 } ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 -} +} \ No newline at end of file diff --git a/school/lms/doctype/course_lesson/course_lesson.py b/school/lms/doctype/course_lesson/course_lesson.py index 38adb6a3..ee0023b6 100644 --- a/school/lms/doctype/course_lesson/course_lesson.py +++ b/school/lms/doctype/course_lesson/course_lesson.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document from ...md import find_macros -from school.lms.utils import get_course_progress +from school.lms.utils import get_course_progress, get_lesson_url class CourseLesson(Document): def validate(self): @@ -108,3 +108,7 @@ def save_progress(lesson, course, status): progress = get_course_progress(course) frappe.db.set_value("LMS Batch Membership", membership, "progress", progress) return progress + +@frappe.whitelist() +def get_lesson_info(chapter): + return frappe.db.get_value("Course Chapter", chapter, "course") diff --git a/school/lms/doctype/lesson_reference/lesson_reference.json b/school/lms/doctype/lesson_reference/lesson_reference.json index a3e77ebb..86129694 100644 --- a/school/lms/doctype/lesson_reference/lesson_reference.json +++ b/school/lms/doctype/lesson_reference/lesson_reference.json @@ -1,5 +1,6 @@ { "actions": [], + "autoname": "hash", "creation": "2021-07-27 16:25:48.269536", "doctype": "DocType", "editable_grid": 1, @@ -20,13 +21,15 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-09-30 10:35:47.832547", + "modified": "2022-03-15 09:39:29.495991", "modified_by": "Administrator", "module": "LMS", "name": "Lesson Reference", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/school/lms/doctype/lms_course/lms_course.json b/school/lms/doctype/lms_course/lms_course.json index 84abccb2..671e6efb 100644 --- a/school/lms/doctype/lms_course/lms_course.json +++ b/school/lms/doctype/lms_course/lms_course.json @@ -7,10 +7,9 @@ "label": "Reindex Exercises" } ], - "allow_guest_to_view": 1, "allow_import": 1, "allow_rename": 1, - "creation": "2022-02-08 16:34:42.721203", + "creation": "2022-02-22 15:28:26.091549", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", @@ -21,13 +20,14 @@ "column_break_3", "instructors", "tags", + "status", "section_break_7", "is_published", - "column_break_9", + "column_break_10", "upcoming", - "column_break_11", + "column_break_12", "disable_self_learning", - "section_break_5", + "section_break_18", "short_introduction", "description", "chapters", @@ -43,7 +43,8 @@ "in_list_view": 1, "label": "Title", "reqd": 1, - "unique": 1 + "unique": 1, + "width": "200" }, { "fieldname": "description", @@ -66,10 +67,6 @@ "fieldtype": "Data", "label": "Video Embed Link" }, - { - "fieldname": "section_break_5", - "fieldtype": "Section Break" - }, { "fieldname": "short_introduction", "fieldtype": "Small Text", @@ -109,6 +106,7 @@ "fieldtype": "Table MultiSelect", "in_standard_filter": 1, "label": "Instructors", + "max_height": "50px", "options": "Course Instructor" }, { @@ -116,14 +114,6 @@ "fieldtype": "Section Break", "label": "Course Settings" }, - { - "fieldname": "column_break_9", - "fieldtype": "Column Break" - }, - { - "fieldname": "column_break_11", - "fieldtype": "Column Break" - }, { "fieldname": "certification_section", "fieldtype": "Section Break", @@ -148,9 +138,31 @@ "fieldtype": "Table", "label": "Related Courses", "options": "Related Courses" + }, + { + "default": "In Progress", + "fieldname": "status", + "fieldtype": "Select", + "hidden": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "options": "In Progress\nUnder Review\nApproved", + "read_only": 1 + }, + { + "fieldname": "section_break_18", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_10", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_12", + "fieldtype": "Column Break" } ], - "index_web_pages_for_search": 1, "is_published_field": "is_published", "links": [ { @@ -174,7 +186,7 @@ "link_fieldname": "course" } ], - "modified": "2022-02-16 11:50:20.661085", + "modified": "2022-03-15 10:16:53.796878", "modified_by": "Administrator", "module": "LMS", "name": "LMS Course", @@ -189,6 +201,20 @@ "read": 1, "report": 1, "role": "System Manager", + "select": 1, + "share": 1, + "write": 1 + }, + { + "create": 1, + "email": 1, + "export": 1, + "if_owner": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "All", + "select": 1, "share": 1, "write": 1 } diff --git a/school/lms/doctype/lms_course/lms_course.py b/school/lms/doctype/lms_course/lms_course.py index 24b2c9c2..94648505 100644 --- a/school/lms/doctype/lms_course/lms_course.py +++ b/school/lms/doctype/lms_course/lms_course.py @@ -12,6 +12,24 @@ from school.lms.utils import get_chapters class LMSCourse(Document): + def validate(self): + self.validate_instructors() + self.validate_status() + + def validate_instructors(self): + if self.is_new() and not self.instructors: + frappe.get_doc({ + "doctype": "Course Instructor", + "instructor": self.owner, + "parent": self.name, + "parentfield": "instructors", + "parenttype": "LMS Course" + }).save(ignore_permissions=True) + + def validate_status(self): + if self.is_published: + self.status = "Approved" + def on_update(self): if not self.upcoming and self.has_value_changed("upcoming"): self.send_email_to_interested_users() @@ -184,3 +202,11 @@ def search_course(text): }) """ return courses + +@frappe.whitelist() +def submit_for_review(course): + chapters = frappe.get_all("Chapter Reference", {"parent": course}) + if not len(chapters): + return "No Chp" + frappe.db.set_value("LMS Course", course, "status", "Under Review") + return "OK" diff --git a/school/lms/doctype/lms_course/test_lms_course.py b/school/lms/doctype/lms_course/test_lms_course.py index 64d27695..1dcefab0 100644 --- a/school/lms/doctype/lms_course/test_lms_course.py +++ b/school/lms/doctype/lms_course/test_lms_course.py @@ -12,8 +12,6 @@ class TestLMSCourse(unittest.TestCase): frappe.db.sql('delete from `tabLMS Course Mentor Mapping`') frappe.db.sql('delete from `tabLMS Course`') - - def test_new_course(self): course = new_course("Test Course") assert course.title == "Test Course" diff --git a/school/lms/doctype/lms_settings/lms_settings.json b/school/lms/doctype/lms_settings/lms_settings.json index 53197ad4..2fa1f3f5 100644 --- a/school/lms/doctype/lms_settings/lms_settings.json +++ b/school/lms/doctype/lms_settings/lms_settings.json @@ -6,6 +6,7 @@ "engine": "InnoDB", "field_order": [ "show_search", + "portal_course_creation", "search_placeholder", "column_break_2", "force_profile_completion", @@ -107,8 +108,14 @@ "fieldtype": "Column Break" }, { - "fieldname": "column_break_9", - "fieldtype": "Column Break" + "default": "0", + "fieldname": "portal_course_creation", + "fieldtype": "Check", + "label": "Enable Course Creation from Portal" + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break" }, { "default": "0", @@ -128,7 +135,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2022-03-09 12:40:27.464136", + "modified": "2022-03-10 18:45:58.372370", "modified_by": "Administrator", "module": "LMS", "name": "LMS Settings", @@ -149,4 +156,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/school/lms/doctype/lms_settings/lms_settings.py b/school/lms/doctype/lms_settings/lms_settings.py index 0681a991..8d9ef1ed 100644 --- a/school/lms/doctype/lms_settings/lms_settings.py +++ b/school/lms/doctype/lms_settings/lms_settings.py @@ -5,9 +5,10 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document +from frappe import _ class LMSSettings(Document): - pass + pass @frappe.whitelist() def check_profile_restriction(): diff --git a/school/lms/utils.py b/school/lms/utils.py index 7c77288f..4df6f045 100644 --- a/school/lms/utils.py +++ b/school/lms/utils.py @@ -331,3 +331,16 @@ def get_signup_optin_checks(): links.append("" + mapper[check].get("title") + "") return (", ").join(links) + +def get_popular_courses(): + courses = frappe.get_all("LMS Course", {"is_published": 1, "upcoming": 0}) + course_membership = [] + + for course in courses: + course_membership.append({ + "course": course.name, + "members": cint(frappe.db.count("LMS Batch Membership", {"course": course.name})) + }) + + course_membership = sorted(course_membership, key = lambda x: x.get("members"), reverse=True) + return course_membership[:3] diff --git a/school/lms/web_form/chapter/__init__.py b/school/lms/web_form/chapter/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/school/lms/web_form/chapter/chapter.js b/school/lms/web_form/chapter/chapter.js new file mode 100644 index 00000000..6d710013 --- /dev/null +++ b/school/lms/web_form/chapter/chapter.js @@ -0,0 +1,8 @@ +frappe.ready(function() { + frappe.web_form.after_save = () => { + frappe.msgprint(__("Chapter has been saved successfully. Go back to the course and add this chapter to the chapters table.")) + setTimeout(() => { + window.location.href = `/courses/${frappe.web_form.doc.course}`; + }, 3000); + } +}); diff --git a/school/lms/web_form/chapter/chapter.json b/school/lms/web_form/chapter/chapter.json new file mode 100644 index 00000000..fc2b8afc --- /dev/null +++ b/school/lms/web_form/chapter/chapter.json @@ -0,0 +1,89 @@ +{ + "accept_payment": 0, + "allow_comments": 0, + "allow_delete": 0, + "allow_edit": 1, + "allow_incomplete": 0, + "allow_multiple": 1, + "allow_print": 0, + "amount": 0.0, + "amount_based_on_field": 0, + "apply_document_permissions": 1, + "button_label": "Save", + "creation": "2022-03-07 18:41:07.058806", + "doc_type": "Course Chapter", + "docstatus": 0, + "doctype": "Web Form", + "idx": 0, + "is_multi_step_form": 0, + "is_standard": 1, + "login_required": 1, + "max_attachment_size": 0, + "modified": "2022-03-14 18:48:01.704325", + "modified_by": "Administrator", + "module": "LMS", + "name": "chapter", + "owner": "Administrator", + "payment_button_label": "Buy Now", + "published": 1, + "route": "chapter", + "route_to_success_link": 0, + "show_attachments": 0, + "show_in_grid": 0, + "show_sidebar": 0, + "sidebar_items": [], + "success_url": "/chapter", + "title": "Chapter", + "web_form_fields": [ + { + "allow_read_on_all_link_options": 0, + "fieldname": "course", + "fieldtype": "Link", + "hidden": 0, + "label": "Course", + "max_length": 0, + "max_value": 0, + "options": "LMS Course", + "read_only": 0, + "reqd": 1, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "title", + "fieldtype": "Data", + "hidden": 0, + "label": "Title", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 1, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "description", + "fieldtype": "Small Text", + "hidden": 0, + "label": "Description", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "lessons", + "fieldtype": "Table", + "hidden": 0, + "label": "Lessons", + "max_length": 0, + "max_value": 0, + "options": "Lesson Reference", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + } + ] +} \ No newline at end of file diff --git a/school/lms/web_form/chapter/chapter.py b/school/lms/web_form/chapter/chapter.py new file mode 100644 index 00000000..e1ada619 --- /dev/null +++ b/school/lms/web_form/chapter/chapter.py @@ -0,0 +1,5 @@ +import frappe + +def get_context(context): + # do your magic here + pass diff --git a/school/lms/web_form/course/__init__.py b/school/lms/web_form/course/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/school/lms/web_form/course/course.js b/school/lms/web_form/course/course.js new file mode 100644 index 00000000..ff539e0a --- /dev/null +++ b/school/lms/web_form/course/course.js @@ -0,0 +1,6 @@ +frappe.ready(function() { + frappe.web_form.after_save = () => { + let route = frappe.web_form.doc.name ? `/courses/${frappe.web_form.doc.name}` : `/course`; + window.location.href = route; + } +}); diff --git a/school/lms/web_form/course/course.json b/school/lms/web_form/course/course.json new file mode 100644 index 00000000..f12d692e --- /dev/null +++ b/school/lms/web_form/course/course.json @@ -0,0 +1,147 @@ +{ + "accept_payment": 0, + "allow_comments": 0, + "allow_delete": 0, + "allow_edit": 1, + "allow_incomplete": 0, + "allow_multiple": 1, + "allow_print": 0, + "amount": 0.0, + "amount_based_on_field": 0, + "apply_document_permissions": 1, + "button_label": "Save", + "creation": "2022-03-07 14:40:41.262163", + "custom_css": "", + "doc_type": "LMS Course", + "docstatus": 0, + "doctype": "Web Form", + "idx": 0, + "is_multi_step_form": 0, + "is_standard": 1, + "login_required": 1, + "max_attachment_size": 0, + "modified": "2022-03-11 12:32:22.512115", + "modified_by": "Administrator", + "module": "LMS", + "name": "course", + "owner": "Administrator", + "payment_button_label": "Buy Now", + "published": 1, + "route": "course", + "route_to_success_link": 0, + "show_attachments": 0, + "show_in_grid": 0, + "show_sidebar": 0, + "sidebar_items": [], + "success_url": "/course", + "title": "Course", + "web_form_fields": [ + { + "allow_read_on_all_link_options": 0, + "fieldname": "title", + "fieldtype": "Data", + "hidden": 0, + "label": "Title", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 1, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "tags", + "fieldtype": "Data", + "hidden": 0, + "label": "Tags", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "", + "fieldtype": "Column Break", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "video_link", + "fieldtype": "Data", + "hidden": 0, + "label": "Video Embed Link", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "image", + "fieldtype": "Attach Image", + "hidden": 0, + "label": "Preview Image", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "", + "fieldtype": "Section Break", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "short_introduction", + "fieldtype": "Small Text", + "hidden": 0, + "label": "Short Introduction", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 1, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "description", + "fieldtype": "Text", + "hidden": 0, + "label": "Description", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 1, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "chapters", + "fieldtype": "Table", + "hidden": 0, + "label": "Chapters", + "max_length": 0, + "max_value": 0, + "options": "Chapter Reference", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + } + ] +} \ No newline at end of file diff --git a/school/lms/web_form/course/course.py b/school/lms/web_form/course/course.py new file mode 100644 index 00000000..e1ada619 --- /dev/null +++ b/school/lms/web_form/course/course.py @@ -0,0 +1,5 @@ +import frappe + +def get_context(context): + # do your magic here + pass diff --git a/school/lms/web_form/lesson/__init__.py b/school/lms/web_form/lesson/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/school/lms/web_form/lesson/lesson.js b/school/lms/web_form/lesson/lesson.js new file mode 100644 index 00000000..0044ba2d --- /dev/null +++ b/school/lms/web_form/lesson/lesson.js @@ -0,0 +1,16 @@ +frappe.ready(function() { + frappe.web_form.after_save = () => { + frappe.call({ + method: "school.lms.doctype.course_lesson.course_lesson.get_lesson_info", + args: { + "chapter": frappe.web_form.doc.chapter + }, + callback: (data) => { + frappe.msgprint(__(`Lesson has been saved successfully. Go back to the chapter and add this lesson to the lessons table.`)); + setTimeout(() => { + window.location.href = `/courses/${data.message}`; + }, 3000); + } + }); + }; +}); diff --git a/school/lms/web_form/lesson/lesson.json b/school/lms/web_form/lesson/lesson.json new file mode 100644 index 00000000..2c519587 --- /dev/null +++ b/school/lms/web_form/lesson/lesson.json @@ -0,0 +1,91 @@ +{ + "accept_payment": 0, + "allow_comments": 0, + "allow_delete": 0, + "allow_edit": 1, + "allow_incomplete": 0, + "allow_multiple": 1, + "allow_print": 0, + "amount": 0.0, + "amount_based_on_field": 0, + "apply_document_permissions": 1, + "button_label": "Save", + "creation": "2022-03-07 18:41:42.549831", + "custom_css": "", + "doc_type": "Course Lesson", + "docstatus": 0, + "doctype": "Web Form", + "idx": 0, + "introduction_text": "
Create lessons for your course. You can add some additional content to the lesson using a special syntax. The table below mentions all types of dynamic content that you can add to the lessons and the syntax for the same.
| Content Type | Syntax |
| Video | {{ Video(\"url_of_source\") }} |
| YouTube Video | {{ YouTubeVideo(\"unique_embed_id\") }} |
| Exercise | {{ Exercise(\"exercise_name\") }} |
| Quiz | {{ Quiz(\"lms_quiz_name\") }} |
| Assignment | {{ Assignment(\"id-filetype\") }} |