Merge pull request #1686 from pateljannat/program-refactor
refactor: learning path
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
from lms.lms.doctype.lms_enrollment.lms_enrollment import update_program_progress
|
||||
from lms.lms.utils import get_course_progress
|
||||
|
||||
|
||||
@@ -19,3 +20,4 @@ class LMSCourseProgress(Document):
|
||||
"name",
|
||||
)
|
||||
frappe.db.set_value("LMS Enrollment", membership, "progress", progress)
|
||||
update_program_progress(self.member)
|
||||
|
||||
@@ -13,7 +13,7 @@ class LMSEnrollment(Document):
|
||||
self.validate_membership_in_different_batch_same_course()
|
||||
|
||||
def on_update(self):
|
||||
self.update_program_progress()
|
||||
update_program_progress(self.member)
|
||||
|
||||
def validate_membership_in_same_batch(self):
|
||||
filters = {"member": self.member, "course": self.course, "name": ["!=", self.name]}
|
||||
@@ -59,21 +59,20 @@ class LMSEnrollment(Document):
|
||||
)
|
||||
)
|
||||
|
||||
def update_program_progress(self):
|
||||
programs = frappe.get_all("LMS Program Member", {"member": self.member}, ["parent", "name"])
|
||||
|
||||
for program in programs:
|
||||
total_progress = 0
|
||||
courses = frappe.get_all("LMS Program Course", {"parent": program.parent}, pluck="course")
|
||||
for course in courses:
|
||||
progress = frappe.db.get_value(
|
||||
"LMS Enrollment", {"course": course, "member": self.member}, "progress"
|
||||
)
|
||||
progress = progress or 0
|
||||
total_progress += progress
|
||||
def update_program_progress(member):
|
||||
programs = frappe.get_all("LMS Program Member", {"member": member}, ["parent", "name"])
|
||||
|
||||
average_progress = ceil(total_progress / len(courses))
|
||||
frappe.db.set_value("LMS Program Member", program.name, "progress", average_progress)
|
||||
for program in programs:
|
||||
total_progress = 0
|
||||
courses = frappe.get_all("LMS Program Course", {"parent": program.parent}, pluck="course")
|
||||
for course in courses:
|
||||
progress = frappe.db.get_value("LMS Enrollment", {"course": course, "member": member}, "progress")
|
||||
progress = progress or 0
|
||||
total_progress += progress
|
||||
|
||||
average_progress = ceil(total_progress / len(courses))
|
||||
frappe.db.set_value("LMS Program Member", program.name, "progress", average_progress)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -161,8 +161,8 @@
|
||||
"link_fieldname": "payment"
|
||||
}
|
||||
],
|
||||
"modified": "2025-03-13 15:31:38.019002",
|
||||
"modified_by": "Administrator",
|
||||
"modified": "2025-08-19 10:33:15.457678",
|
||||
"modified_by": "sayali@frappe.io",
|
||||
"module": "LMS",
|
||||
"name": "LMS Payment",
|
||||
"owner": "Administrator",
|
||||
@@ -180,9 +180,11 @@
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"show_title_field_in_link": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "billing_name"
|
||||
}
|
||||
"title_field": "billing_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
|
||||
@@ -7,8 +7,17 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"published",
|
||||
"column_break_cwjx",
|
||||
"enforce_course_order",
|
||||
"column_break_mikl",
|
||||
"section_break_vhhu",
|
||||
"program_courses",
|
||||
"program_members"
|
||||
"program_members",
|
||||
"section_break_pppe",
|
||||
"course_count",
|
||||
"column_break_qwhf",
|
||||
"member_count"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -30,12 +39,61 @@
|
||||
"label": "Title",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "published",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Published"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enforce_course_order",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Enforce Course Order"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_vhhu",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_cwjx",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_pppe",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "course_count",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Course Count"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_qwhf",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "member_count",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Member Count"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_mikl",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2024-11-28 22:06:16.742867",
|
||||
"modified_by": "Administrator",
|
||||
"modified": "2025-08-20 12:28:57.238902",
|
||||
"modified_by": "sayali@frappe.io",
|
||||
"module": "LMS",
|
||||
"name": "LMS Program",
|
||||
"naming_rule": "By fieldname",
|
||||
@@ -76,10 +134,21 @@
|
||||
"role": "Course Creator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "LMS Student",
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "title",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ class LMSProgram(Document):
|
||||
def validate(self):
|
||||
self.validate_program_courses()
|
||||
self.validate_program_members()
|
||||
self.update_count()
|
||||
|
||||
def validate_program_courses(self):
|
||||
courses = [row.course for row in self.program_courses]
|
||||
@@ -30,3 +31,13 @@ class LMSProgram(Document):
|
||||
frappe.bold(next(iter(duplicates)))
|
||||
)
|
||||
)
|
||||
|
||||
def update_count(self):
|
||||
course_count = len(self.program_courses)
|
||||
member_count = len(self.program_members)
|
||||
|
||||
if self.course_count != course_count:
|
||||
self.course_count = course_count
|
||||
|
||||
if self.member_count != member_count:
|
||||
self.member_count = member_count
|
||||
|
||||
@@ -27,16 +27,18 @@
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-11-18 12:43:46.800199",
|
||||
"modified_by": "Administrator",
|
||||
"modified": "2025-08-13 17:32:43.554055",
|
||||
"modified_by": "sayali@frappe.io",
|
||||
"module": "LMS",
|
||||
"name": "LMS Program Course",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,16 +35,18 @@
|
||||
"label": "Progress"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-11-21 12:51:31.882576",
|
||||
"modified_by": "Administrator",
|
||||
"modified": "2025-08-13 17:33:00.265037",
|
||||
"modified_by": "sayali@frappe.io",
|
||||
"module": "LMS",
|
||||
"name": "LMS Program Member",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
"persona_captured",
|
||||
"column_break_zdel",
|
||||
"allow_guest_access",
|
||||
"enable_learning_paths",
|
||||
"prevent_skipping_videos",
|
||||
"column_break_bjis",
|
||||
"unsplash_access_key",
|
||||
@@ -339,12 +338,6 @@
|
||||
"fieldtype": "HTML",
|
||||
"label": "Payments app is not installed"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enable_learning_paths",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Learning Paths"
|
||||
},
|
||||
{
|
||||
"fieldname": "general_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
@@ -429,7 +422,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2025-07-01 17:01:58.466698",
|
||||
"modified": "2025-08-12 16:47:49.983018",
|
||||
"modified_by": "sayali@frappe.io",
|
||||
"module": "LMS",
|
||||
"name": "LMS Settings",
|
||||
|
||||
139
lms/lms/utils.py
139
lms/lms/utils.py
@@ -1118,6 +1118,7 @@ def get_course_details(course):
|
||||
"lessons",
|
||||
"enrollments",
|
||||
"rating",
|
||||
"card_gradient",
|
||||
],
|
||||
as_dict=1,
|
||||
)
|
||||
@@ -1270,7 +1271,6 @@ def get_lesson(course, chapter, lesson):
|
||||
progress = get_progress(course, lesson_details.name)
|
||||
|
||||
lesson_details.chapter_title = frappe.db.get_value("Course Chapter", chapter_name, "title")
|
||||
lesson_details.rendered_content = render_html(lesson_details)
|
||||
neighbours = get_neighbour_lesson(course, chapter, lesson)
|
||||
lesson_details.next = neighbours["next"]
|
||||
lesson_details.progress = progress
|
||||
@@ -1919,80 +1919,93 @@ def update_certificate_purchase(course, payment_name):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_programs():
|
||||
if has_course_moderator_role() or has_course_instructor_role() or has_course_evaluator_role():
|
||||
programs = frappe.get_all("LMS Program", fields=["name"])
|
||||
else:
|
||||
programs = frappe.get_all(
|
||||
"LMS Program Member", {"member": frappe.session.user}, ["parent as name", "progress"]
|
||||
enrolled_programs = frappe.get_all(
|
||||
"LMS Program Member", {"member": frappe.session.user}, ["parent as name", "progress"]
|
||||
)
|
||||
for program in enrolled_programs:
|
||||
program.update(
|
||||
frappe.db.get_value(
|
||||
"LMS Program", program.name, ["name", "course_count", "member_count"], as_dict=True
|
||||
)
|
||||
)
|
||||
|
||||
for program in programs:
|
||||
program_courses = frappe.get_all(
|
||||
"LMS Program Course", {"parent": program.name}, ["course"], order_by="idx"
|
||||
)
|
||||
program.courses = []
|
||||
previous_progress = 0
|
||||
for i, course in enumerate(program_courses):
|
||||
details = get_course_details(course.course)
|
||||
if i == 0:
|
||||
details.eligible = True
|
||||
elif previous_progress == 100:
|
||||
details.eligible = True
|
||||
else:
|
||||
details.eligible = False
|
||||
published_programs = frappe.get_all(
|
||||
"LMS Program",
|
||||
{
|
||||
"published": 1,
|
||||
},
|
||||
["name", "course_count", "member_count"],
|
||||
)
|
||||
|
||||
previous_progress = details.membership.progress if details.membership else 0
|
||||
program.courses.append(details)
|
||||
programs_to_remove = []
|
||||
for program in published_programs:
|
||||
if program.name in [p.name for p in enrolled_programs]:
|
||||
programs_to_remove.append(program)
|
||||
published_programs = [program for program in published_programs if program not in programs_to_remove]
|
||||
|
||||
program.members = frappe.db.count("LMS Program Member", {"parent": program.name})
|
||||
|
||||
return programs
|
||||
return {
|
||||
"enrolled": enrolled_programs,
|
||||
"published": published_programs,
|
||||
}
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def enroll_in_program_course(program, course):
|
||||
enrollment = frappe.db.exists("LMS Enrollment", {"member": frappe.session.user, "course": course})
|
||||
|
||||
if enrollment:
|
||||
enrollment = frappe.db.get_value("LMS Enrollment", enrollment, ["name", "current_lesson"], as_dict=1)
|
||||
enrollment.current_lesson = get_lesson_index(enrollment.current_lesson)
|
||||
return enrollment
|
||||
|
||||
def get_program_details(program_name):
|
||||
program = frappe.db.get_value(
|
||||
"LMS Program",
|
||||
program_name,
|
||||
[
|
||||
"name",
|
||||
"member_count",
|
||||
"course_count",
|
||||
"published",
|
||||
"allow_self_enrollment",
|
||||
"enforce_course_order",
|
||||
],
|
||||
as_dict=1,
|
||||
)
|
||||
program_courses = frappe.get_all(
|
||||
"LMS Program Course", {"parent": program}, ["course", "idx"], order_by="idx"
|
||||
"LMS Program Course", {"parent": program_name}, ["course"], order_by="idx"
|
||||
)
|
||||
current_course_idx = [
|
||||
program_course.idx for program_course in program_courses if program_course.course == course
|
||||
][0]
|
||||
|
||||
for program_course in program_courses:
|
||||
if program_course.idx < current_course_idx:
|
||||
enrollment = frappe.db.get_value(
|
||||
"LMS Enrollment",
|
||||
{"member": frappe.session.user, "course": program_course.course},
|
||||
["name", "progress"],
|
||||
as_dict=1,
|
||||
program.courses = []
|
||||
previous_progress = 0
|
||||
for i, course in enumerate(program_courses):
|
||||
details = get_course_details(course.course)
|
||||
if i == 0:
|
||||
details.eligible = True
|
||||
elif previous_progress == 100:
|
||||
details.eligible = True
|
||||
else:
|
||||
details.eligible = False
|
||||
|
||||
previous_progress = details.membership.progress if details.membership else 0
|
||||
program.courses.append(details)
|
||||
if frappe.session.user != "Guest":
|
||||
program.progress = frappe.db.get_value(
|
||||
"LMS Program Member",
|
||||
{"parent": program_name, "member": frappe.session.user},
|
||||
"progress",
|
||||
)
|
||||
if enrollment and enrollment.progress != 100:
|
||||
frappe.throw(
|
||||
_("Please complete the previous courses in the program to enroll in this course.")
|
||||
)
|
||||
elif not enrollment:
|
||||
frappe.throw(
|
||||
_("Please complete the previous courses in the program to enroll in this course.")
|
||||
)
|
||||
else:
|
||||
continue
|
||||
|
||||
enrollment = frappe.new_doc("LMS Enrollment")
|
||||
enrollment.update(
|
||||
{
|
||||
"member": frappe.session.user,
|
||||
"course": course,
|
||||
}
|
||||
)
|
||||
enrollment.save()
|
||||
return enrollment
|
||||
return program
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def enroll_in_program(program):
|
||||
if frappe.session.user == "Guest":
|
||||
frappe.throw(_("Please login to enroll in the program."))
|
||||
if not frappe.db.exists("LMS Program Member", {"parent": program, "member": frappe.session.user}):
|
||||
program_member = frappe.new_doc("LMS Program Member")
|
||||
program_member.update(
|
||||
{
|
||||
"parent": program,
|
||||
"parenttype": "LMS Program",
|
||||
"parentfield": "members",
|
||||
"member": frappe.session.user,
|
||||
}
|
||||
)
|
||||
program_member.save(ignore_permissions=True)
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
|
||||
@@ -109,4 +109,5 @@ lms.patches.v2_0.link_zoom_account_to_live_class
|
||||
lms.patches.v2_0.link_zoom_account_to_batch
|
||||
lms.patches.v2_0.sidebar_for_certified_members
|
||||
lms.patches.v2_0.move_batch_instructors_to_evaluators
|
||||
lms.patches.v2_0.enable_programming_exercises_in_sidebar
|
||||
lms.patches.v2_0.enable_programming_exercises_in_sidebar
|
||||
lms.patches.v2_0.count_in_program
|
||||
18
lms/patches/v2_0/count_in_program.py
Normal file
18
lms/patches/v2_0/count_in_program.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
programs = frappe.get_all("LMS Program", pluck="name")
|
||||
|
||||
for program in programs:
|
||||
course_count = frappe.db.count(
|
||||
"LMS Program Course",
|
||||
{"parent": program, "parenttype": "LMS Program", "parentfield": "program_courses"},
|
||||
)
|
||||
frappe.db.set_value("LMS Program", program, "course_count", course_count)
|
||||
|
||||
member_count = frappe.db.count(
|
||||
"LMS Program Member",
|
||||
{"parent": program, "parenttype": "LMS Program", "parentfield": "program_members"},
|
||||
)
|
||||
frappe.db.set_value("LMS Program", program, "member_count", member_count)
|
||||
Reference in New Issue
Block a user