Merge pull request #1145 from pateljannat/learning-paths
feat: learning paths
This commit is contained in:
@@ -93,15 +93,15 @@ def save_progress(lesson, course):
|
||||
|
||||
frappe.db.set_value("LMS Enrollment", membership, "current_lesson", lesson)
|
||||
|
||||
quiz_completed = get_quiz_progress(lesson)
|
||||
if not quiz_completed:
|
||||
return 0
|
||||
|
||||
if frappe.db.exists(
|
||||
"LMS Course Progress", {"lesson": lesson, "member": frappe.session.user}
|
||||
):
|
||||
return
|
||||
|
||||
quiz_completed = get_quiz_progress(lesson)
|
||||
if not quiz_completed:
|
||||
return 0
|
||||
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "LMS Course Progress",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import ceil
|
||||
|
||||
|
||||
class LMSEnrollment(Document):
|
||||
@@ -11,6 +12,9 @@ class LMSEnrollment(Document):
|
||||
self.validate_membership_in_same_batch()
|
||||
self.validate_membership_in_different_batch_same_course()
|
||||
|
||||
def on_update(self):
|
||||
self.update_program_progress()
|
||||
|
||||
def validate_membership_in_same_batch(self):
|
||||
filters = {"member": self.member, "course": self.course, "name": ["!=", self.name]}
|
||||
if self.batch_old:
|
||||
@@ -55,6 +59,26 @@ 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
|
||||
|
||||
average_progress = ceil(total_progress / len(courses))
|
||||
frappe.db.set_value("LMS Program Member", program.name, "progress", average_progress)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_membership(
|
||||
|
||||
0
lms/lms/doctype/lms_program/__init__.py
Normal file
0
lms/lms/doctype/lms_program/__init__.py
Normal file
8
lms/lms/doctype/lms_program/lms_program.js
Normal file
8
lms/lms/doctype/lms_program/lms_program.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2024, Frappe and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("LMS Program", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
84
lms/lms/doctype/lms_program/lms_program.json
Normal file
84
lms/lms/doctype/lms_program/lms_program.json
Normal file
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:title",
|
||||
"creation": "2024-11-18 12:27:13.283169",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"program_courses",
|
||||
"program_members"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "program_courses",
|
||||
"fieldtype": "Table",
|
||||
"label": "Program Courses",
|
||||
"options": "LMS Program Course"
|
||||
},
|
||||
{
|
||||
"fieldname": "program_members",
|
||||
"fieldtype": "Table",
|
||||
"label": "Program Members",
|
||||
"options": "LMS Program Member"
|
||||
},
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Title",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2024-11-20 12:26:02.214628",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Program",
|
||||
"naming_rule": "By fieldname",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Moderator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Course Creator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
32
lms/lms/doctype/lms_program/lms_program.py
Normal file
32
lms/lms/doctype/lms_program/lms_program.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# Copyright (c) 2024, Frappe and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class LMSProgram(Document):
|
||||
def validate(self):
|
||||
self.validate_program_courses()
|
||||
self.validate_program_members()
|
||||
|
||||
def validate_program_courses(self):
|
||||
courses = [row.course for row in self.program_courses]
|
||||
duplicates = {course for course in courses if courses.count(course) > 1}
|
||||
if len(duplicates):
|
||||
frappe.throw(
|
||||
_("Course {0} has already been added to this batch.").format(
|
||||
frappe.bold(next(iter(duplicates)))
|
||||
)
|
||||
)
|
||||
|
||||
def validate_program_members(self):
|
||||
members = [row.member for row in self.program_members]
|
||||
duplicates = {member for member in members if members.count(member) > 1}
|
||||
if len(duplicates):
|
||||
frappe.throw(
|
||||
_("Member {0} has already been added to this batch.").format(
|
||||
frappe.bold(next(iter(duplicates)))
|
||||
)
|
||||
)
|
||||
21
lms/lms/doctype/lms_program/test_lms_program.py
Normal file
21
lms/lms/doctype/lms_program/test_lms_program.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# Copyright (c) 2024, Frappe and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record depdendencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class TestLMSProgram(UnitTestCase):
|
||||
"""
|
||||
Unit tests for LMSProgram.
|
||||
Use this class for testing individual functions and methods.
|
||||
"""
|
||||
|
||||
pass
|
||||
0
lms/lms/doctype/lms_program_course/__init__.py
Normal file
0
lms/lms/doctype/lms_program_course/__init__.py
Normal file
42
lms/lms/doctype/lms_program_course/lms_program_course.json
Normal file
42
lms/lms/doctype/lms_program_course/lms_program_course.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2024-11-18 12:27:37.030302",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"course",
|
||||
"course_title"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "course",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Course",
|
||||
"options": "LMS Course",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "course.title",
|
||||
"fieldname": "course_title",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Course Title",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-11-18 12:43:46.800199",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Program Course",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
9
lms/lms/doctype/lms_program_course/lms_program_course.py
Normal file
9
lms/lms/doctype/lms_program_course/lms_program_course.py
Normal file
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2024, Frappe and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class LMSProgramCourse(Document):
|
||||
pass
|
||||
0
lms/lms/doctype/lms_program_member/__init__.py
Normal file
0
lms/lms/doctype/lms_program_member/__init__.py
Normal file
50
lms/lms/doctype/lms_program_member/lms_program_member.json
Normal file
50
lms/lms/doctype/lms_program_member/lms_program_member.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2024-11-18 12:29:13.615014",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"member",
|
||||
"full_name",
|
||||
"progress"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "member",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Member",
|
||||
"options": "User",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "member.full_name",
|
||||
"fieldname": "full_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Full Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "progress",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Progress"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-11-21 12:51:31.882576",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Program Member",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
9
lms/lms/doctype/lms_program_member/lms_program_member.py
Normal file
9
lms/lms/doctype/lms_program_member/lms_program_member.py
Normal file
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2024, Frappe and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class LMSProgramMember(Document):
|
||||
pass
|
||||
@@ -5,13 +5,15 @@
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"general_tab",
|
||||
"default_home",
|
||||
"send_calendar_invite_for_evaluations",
|
||||
"is_onboarding_complete",
|
||||
"column_break_zdel",
|
||||
"enable_learning_paths",
|
||||
"unsplash_access_key",
|
||||
"livecode_url",
|
||||
"section_break_szgq",
|
||||
"send_calendar_invite_for_evaluations",
|
||||
"show_day_view",
|
||||
"column_break_2",
|
||||
"show_dashboard",
|
||||
@@ -80,6 +82,7 @@
|
||||
{
|
||||
"fieldname": "mentor_request_section",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 1,
|
||||
"label": "Mentor Request"
|
||||
},
|
||||
{
|
||||
@@ -127,6 +130,7 @@
|
||||
{
|
||||
"fieldname": "section_break_szgq",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 1,
|
||||
"label": "Batch Settings"
|
||||
},
|
||||
{
|
||||
@@ -336,12 +340,23 @@
|
||||
"fieldname": "payments_app_is_not_installed",
|
||||
"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",
|
||||
"label": "General"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2024-10-01 12:15:49.800242",
|
||||
"modified": "2024-11-20 11:55:05.358421",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Settings",
|
||||
@@ -356,6 +371,13 @@
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "LMS Student",
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
|
||||
@@ -1751,3 +1751,81 @@ def enroll_in_batch(batch, payment_name=None):
|
||||
)
|
||||
|
||||
student.save(ignore_permissions=True)
|
||||
|
||||
|
||||
@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"]
|
||||
)
|
||||
|
||||
for program in programs:
|
||||
program_courses = frappe.get_all(
|
||||
"LMS Program Course", {"parent": program.name}, ["course"], order_by="idx"
|
||||
)
|
||||
program.courses = []
|
||||
for course in program_courses:
|
||||
program.courses.append(get_course_details(course.course))
|
||||
|
||||
program.members = frappe.db.count("LMS Program Member", {"parent": program.name})
|
||||
|
||||
return 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
|
||||
|
||||
program_courses = frappe.get_all(
|
||||
"LMS Program Course", {"parent": program}, ["course", "idx"], 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,
|
||||
)
|
||||
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
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"app": "lms",
|
||||
"charts": [
|
||||
{
|
||||
"chart_name": "New Signups",
|
||||
@@ -145,7 +146,7 @@
|
||||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2024-08-09 13:19:06.273056",
|
||||
"modified": "2024-11-21 12:16:25.886431",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS",
|
||||
@@ -212,5 +213,6 @@
|
||||
"type": "DocType"
|
||||
}
|
||||
],
|
||||
"title": "LMS"
|
||||
}
|
||||
"title": "LMS",
|
||||
"type": "Workspace"
|
||||
}
|
||||
Reference in New Issue
Block a user