Merge pull request #1132 from pateljannat/SCORM-2

feat: SCORM
This commit is contained in:
Jannat Patel
2024-11-15 20:18:59 +05:30
committed by GitHub
18 changed files with 694 additions and 140 deletions

View File

@@ -3,6 +3,10 @@
import json
import frappe
import zipfile
import os
import shutil
import xml.etree.ElementTree as ET
from frappe.translate import get_all_translations
from frappe import _
from frappe.query_builder import DocType
@@ -10,6 +14,7 @@ from frappe.query_builder.functions import Count
from frappe.utils import time_diff, now_datetime, get_datetime, flt
from typing import Optional
from lms.lms.utils import get_average_rating, get_lesson_count
from xml.dom.minidom import parseString
@frappe.whitelist()
@@ -876,3 +881,124 @@ def give_dicussions_permission():
"delete": 1,
}
).save(ignore_permissions=True)
@frappe.whitelist()
def upsert_chapter(title, course, is_scorm_package, scorm_package, name=None):
values = frappe._dict(
{"title": title, "course": course, "is_scorm_package": is_scorm_package}
)
if is_scorm_package:
scorm_package = frappe._dict(scorm_package)
extract_path = extract_package(course, title, scorm_package)
values.update(
{
"scorm_package": scorm_package.name,
"scorm_package_path": extract_path.split("public")[1],
"manifest_file": get_manifest_file(extract_path).split("public")[1],
"launch_file": get_launch_file(extract_path).split("public")[1],
}
)
if name:
chapter = frappe.get_doc("Course Chapter", name)
else:
chapter = frappe.new_doc("Course Chapter")
chapter.update(values)
chapter.save()
if is_scorm_package and not len(chapter.lessons):
add_lesson(title, chapter.name, course)
return chapter
def extract_package(course, title, scorm_package):
package = frappe.get_doc("File", scorm_package.name)
zip_path = package.get_full_path()
extract_path = frappe.get_site_path("public", "files", "scorm", course, title)
zipfile.ZipFile(zip_path).extractall(extract_path)
return extract_path
def get_manifest_file(extract_path):
manifest_file = None
for root, dirs, files in os.walk(extract_path):
for file in files:
if file == "imsmanifest.xml":
manifest_file = os.path.join(root, file)
break
if manifest_file:
break
return manifest_file
def get_launch_file(extract_path):
launch_file = None
manifest_file = get_manifest_file(extract_path)
if manifest_file:
with open(manifest_file) as file:
data = file.read()
dom = parseString(data)
resource = dom.getElementsByTagName("resource")
for res in resource:
if (
res.getAttribute("adlcp:scormtype") == "sco"
or res.getAttribute("adlcp:scormType") == "sco"
):
launch_file = res.getAttribute("href")
break
if launch_file:
launch_file = os.path.join(os.path.dirname(manifest_file), launch_file)
return launch_file
def add_lesson(title, chapter, course):
lesson = frappe.new_doc("Course Lesson")
lesson.update(
{
"title": title,
"chapter": chapter,
"course": course,
}
)
lesson.insert()
lesson_reference = frappe.new_doc("Lesson Reference")
lesson_reference.update(
{
"lesson": lesson.name,
"parent": chapter,
"parenttype": "Course Chapter",
"parentfield": "lessons",
}
)
lesson_reference.insert()
@frappe.whitelist()
def delete_chapter(chapter):
chapterInfo = frappe.db.get_value(
"Course Chapter", chapter, ["is_scorm_package", "scorm_package_path"], as_dict=True
)
if chapterInfo.is_scorm_package:
delete_scorm_package(chapterInfo.scorm_package_path)
frappe.db.delete("Chapter Reference", {"chapter": chapter})
frappe.db.delete("Lesson Reference", {"parent": chapter})
frappe.db.delete("Course Lesson", {"chapter": chapter})
frappe.db.delete("Course Chapter", chapter)
def delete_scorm_package(scorm_package_path):
scorm_package_path = frappe.get_site_path("public", scorm_package_path)
if os.path.exists(scorm_package_path):
shutil.rmtree(scorm_package_path)

View File

@@ -8,9 +8,17 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"course",
"column_break_3",
"title",
"column_break_3",
"course",
"course_title",
"scorm_section",
"is_scorm_package",
"scorm_package",
"scorm_package_path",
"column_break_dlnw",
"manifest_file",
"launch_file",
"section_break_5",
"lessons"
],
@@ -43,6 +51,56 @@
"fieldtype": "Table",
"label": "Lessons",
"options": "Lesson Reference"
},
{
"default": "0",
"fieldname": "is_scorm_package",
"fieldtype": "Check",
"label": "Is SCORM Package"
},
{
"depends_on": "is_scorm_package",
"fieldname": "manifest_file",
"fieldtype": "Code",
"label": "Manifest File",
"read_only": 1
},
{
"depends_on": "is_scorm_package",
"fieldname": "launch_file",
"fieldtype": "Code",
"label": "Launch File",
"read_only": 1
},
{
"fieldname": "scorm_section",
"fieldtype": "Section Break",
"label": "SCORM"
},
{
"fieldname": "scorm_package",
"fieldtype": "Link",
"label": "SCORM Package",
"options": "File",
"read_only": 1
},
{
"fieldname": "column_break_dlnw",
"fieldtype": "Column Break"
},
{
"depends_on": "is_scorm_package",
"fieldname": "scorm_package_path",
"fieldtype": "Code",
"label": "SCORM Package Path",
"read_only": 1
},
{
"fetch_from": "course.title",
"fieldname": "course_title",
"fieldtype": "Data",
"label": "Course Title",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
@@ -53,7 +111,7 @@
"link_fieldname": "chapter"
}
],
"modified": "2024-10-29 16:54:20.904683",
"modified": "2024-11-15 12:03:31.370943",
"modified_by": "Administrator",
"module": "LMS",
"name": "Course Chapter",
@@ -73,17 +131,14 @@
"write": 1
},
{
"create": 1,
"email": 1,
"export": 1,
"if_owner": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "LMS Student",
"select": 1,
"share": 1,
"write": 1
"share": 1
}
],
"search_fields": "title",

View File

@@ -8,12 +8,18 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"chapter",
"course",
"column_break_4",
"title",
"include_in_preview",
"index_label",
"column_break_4",
"chapter",
"is_scorm_package",
"course",
"section_break_11",
"content",
"body",
"column_break_cjmf",
"instructor_content",
"instructor_notes",
"section_break_6",
"youtube",
"column_break_9",
@@ -22,13 +28,7 @@
"question",
"column_break_15",
"file_type",
"section_break_11",
"content",
"body",
"column_break_cjmf",
"instructor_content",
"instructor_notes",
"help_section",
"column_break_syza",
"help"
],
"fields": [
@@ -59,12 +59,6 @@
"label": "Title",
"reqd": 1
},
{
"fieldname": "index_label",
"fieldtype": "Data",
"label": "Index Label",
"read_only": 1
},
{
"fieldname": "section_break_6",
"fieldtype": "Section Break",
@@ -74,14 +68,7 @@
"fieldname": "body",
"fieldtype": "Markdown Editor",
"ignore_xss_filter": 1,
"label": "Body",
"reqd": 1
},
{
"fieldname": "help_section",
"fieldtype": "Section Break",
"hidden": 1,
"label": "Help"
"label": "Body"
},
{
"fieldname": "help",
@@ -158,11 +145,23 @@
"fieldname": "instructor_content",
"fieldtype": "Text",
"label": "Instructor Content"
},
{
"fieldname": "column_break_syza",
"fieldtype": "Column Break"
},
{
"default": "0",
"fetch_from": "chapter.is_scorm_package",
"fieldname": "is_scorm_package",
"fieldtype": "Check",
"label": "Is SCORM Package",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-10-08 11:04:54.748773",
"modified": "2024-11-14 13:46:56.838659",
"modified_by": "Administrator",
"module": "LMS",
"name": "Course Lesson",

View File

@@ -52,7 +52,6 @@ class CourseLesson(Document):
ex.lesson = None
ex.course = None
ex.index_ = 0
ex.index_label = ""
ex.save(ignore_permissions=True)
def check_and_create_folder(self):

View File

@@ -1128,11 +1128,20 @@ def get_course_outline(course, progress=False):
chapter_details = frappe.db.get_value(
"Course Chapter",
chapter.chapter,
["name", "title"],
["name", "title", "is_scorm_package", "launch_file", "scorm_package"],
as_dict=True,
)
chapter_details["idx"] = chapter.idx
chapter_details.lessons = get_lessons(course, chapter_details, progress=progress)
if chapter_details.is_scorm_package:
chapter_details.scorm_package = frappe.db.get_value(
"File",
chapter_details.scorm_package,
["file_name", "file_size", "file_url"],
as_dict=1,
)
outline.append(chapter_details)
return outline
@@ -1146,9 +1155,12 @@ def get_lesson(course, chapter, lesson):
"Lesson Reference", {"parent": chapter_name, "idx": lesson}, "lesson"
)
lesson_details = frappe.db.get_value(
"Course Lesson", lesson_name, ["include_in_preview", "title"], as_dict=1
"Course Lesson",
lesson_name,
["include_in_preview", "title", "is_scorm_package"],
as_dict=1,
)
if not lesson_details:
if not lesson_details or lesson_details.is_scorm_package:
return {}
membership = get_membership(course)