feat: record lesson progress

This commit is contained in:
Jannat Patel
2024-11-15 19:14:34 +05:30
parent 73754bd104
commit cf014bca3c
16 changed files with 491 additions and 109 deletions

View File

@@ -13,6 +13,6 @@ module.exports = defineConfig({
openMode: 0,
},
e2e: {
baseUrl: "http://test:8000",
baseUrl: "http://lms1:8000",
},
});

View File

@@ -1,18 +1,18 @@
<template>
<div
class="flex h-full flex-col justify-between transition-all duration-300 ease-in-out bg-gray-50"
:class="isSidebarCollapsed ? 'w-14' : 'w-56'"
:class="sidebarStore.isSidebarCollapsed ? 'w-14' : 'w-56'"
>
<div
class="flex flex-col overflow-hidden"
:class="isSidebarCollapsed ? 'items-center' : ''"
:class="sidebarStore.isSidebarCollapsed ? 'items-center' : ''"
>
<UserDropdown :isCollapsed="isSidebarCollapsed" />
<UserDropdown :isCollapsed="sidebarStore.isSidebarCollapsed" />
<div class="flex flex-col" v-if="sidebarSettings.data">
<SidebarLink
v-for="link in sidebarLinks"
:link="link"
:isCollapsed="isSidebarCollapsed"
:isCollapsed="sidebarStore.isSidebarCollapsed"
class="mx-2 my-0.5"
/>
</div>
@@ -22,11 +22,11 @@
>
<div
class="flex items-center justify-between pr-2 cursor-pointer"
:class="isSidebarCollapsed ? 'pl-3' : 'pl-4'"
:class="sidebarStore.isSidebarCollapsed ? 'pl-3' : 'pl-4'"
@click="showWebPages = !showWebPages"
>
<div
v-if="!isSidebarCollapsed"
v-if="!sidebarStore.isSidebarCollapsed"
class="flex items-center text-sm text-gray-600 my-1"
>
<span class="grid h-5 w-6 flex-shrink-0 place-items-center">
@@ -53,7 +53,7 @@
<SidebarLink
v-for="link in sidebarSettings.data.web_pages"
:link="link"
:isCollapsed="isSidebarCollapsed"
:isCollapsed="sidebarStore.isSidebarCollapsed"
class="mx-2 my-0.5"
:showControls="isModerator ? true : false"
@openModal="openPageModal"
@@ -64,17 +64,19 @@
</div>
<SidebarLink
:link="{
label: isSidebarCollapsed ? 'Expand' : 'Collapse',
label: sidebarStore.isSidebarCollapsed ? 'Expand' : 'Collapse',
}"
:isCollapsed="isSidebarCollapsed"
@click="isSidebarCollapsed = !isSidebarCollapsed"
:isCollapsed="sidebarStore.isSidebarCollapsed"
@click="toggleSidebar()"
class="m-2"
>
<template #icon>
<span class="grid h-5 w-6 flex-shrink-0 place-items-center">
<CollapseSidebar
class="h-4.5 w-4.5 text-gray-700 duration-300 ease-in-out"
:class="{ '[transform:rotateY(180deg)]': isSidebarCollapsed }"
:class="{
'[transform:rotateY(180deg)]': sidebarStore.isSidebarCollapsed,
}"
/>
</span>
</template>
@@ -96,12 +98,14 @@ import { ref, onMounted, inject, watch } from 'vue'
import { getSidebarLinks } from '../utils'
import { usersStore } from '@/stores/user'
import { sessionStore } from '@/stores/session'
import { useSidebar } from '@/stores/sidebar'
import { ChevronRight, Plus } from 'lucide-vue-next'
import { createResource, Button } from 'frappe-ui'
import PageModal from '@/components/Modals/PageModal.vue'
const { user, sidebarSettings } = sessionStore()
const { userResource } = usersStore()
let sidebarStore = useSidebar()
const socket = inject('$socket')
const unreadCount = ref(0)
const sidebarLinks = ref(getSidebarLinks())
@@ -214,5 +218,8 @@ watch(userResource, () => {
}
})
let isSidebarCollapsed = ref(getSidebarFromStorage())
const toggleSidebar = () => {
console.log(sidebarStore.isSidebarCollapsed)
sidebarStore.isSidebarCollapsed = !sidebarStore.isSidebarCollapsed
}
</script>

View File

@@ -140,7 +140,7 @@ function enrollStudent() {
showToast(
__('Please Login'),
__('You need to login first to enroll for this course'),
'circle-warn'
'alert-circle'
)
setTimeout(() => {
window.location.href = `/login?redirect-to=${window.location.pathname}`

View File

@@ -16,7 +16,7 @@
</div>
<div
:class="{
'shadow rounded-md pt-2 px-2': showOutline && outline.data?.length,
'shadow rounded-md py-2 px-2': showOutline && outline.data?.length,
}"
>
<Disclosure
@@ -25,21 +25,42 @@
:key="chapter.name"
:defaultOpen="openChapterDetail(chapter.idx)"
>
<DisclosureButton ref="" class="flex w-full p-2">
<DisclosureButton ref="" class="flex items-center w-full p-2 group">
<ChevronRight
:class="{
'rotate-90 transform duration-200': open,
'duration-200': !open,
hidden: chapter.is_scorm_package,
open: index == 1,
}"
class="h-4 w-4 text-gray-900 stroke-1 mr-2"
class="h-4 w-4 text-gray-900 stroke-1"
/>
<div class="text-base text-left font-medium leading-5">
<div
class="text-base text-left font-medium leading-5 ml-2"
@click="redirectToChapter(chapter)"
>
{{ chapter.title }}
</div>
<div class="flex ml-auto space-x-4">
<Tooltip :text="__('Edit Chapter')" placement="bottom">
<FilePenLine
v-if="allowEdit"
@click.prevent="openChapterModal(chapter)"
class="h-4 w-4 text-gray-900 invisible group-hover:visible"
/>
</Tooltip>
<Tooltip :text="__('Delete Chapter')" placement="bottom">
<Trash2
v-if="allowEdit"
@click.prevent="trashChapter(chapter.name)"
class="h-4 w-4 text-red-500 invisible group-hover:visible"
/>
</Tooltip>
</div>
</DisclosureButton>
<DisclosurePanel>
<DisclosurePanel v-if="!chapter.is_scorm_package">
<Draggable
v-if="!chapter.is_scorm_package"
:list="chapter.lessons"
:disabled="!allowEdit"
item-key="name"
@@ -103,9 +124,6 @@
{{ __('Add Lesson') }}
</Button>
</router-link>
<Button class="ml-2" @click="openChapterModal(chapter)">
{{ __('Edit Chapter') }}
</Button>
</div>
</DisclosurePanel>
</Disclosure>
@@ -119,24 +137,26 @@
/>
</template>
<script setup>
import { Button, createResource } from 'frappe-ui'
import { ref, getCurrentInstance } from 'vue'
import { Button, createResource, Tooltip } from 'frappe-ui'
import { getCurrentInstance, inject, ref } from 'vue'
import Draggable from 'vuedraggable'
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'
import {
ChevronRight,
MonitorPlay,
HelpCircle,
FileText,
Check,
ChevronRight,
FileText,
FilePenLine,
HelpCircle,
MonitorPlay,
Trash2,
} from 'lucide-vue-next'
import { useRoute } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
import ChapterModal from '@/components/Modals/ChapterModal.vue'
import { showToast } from '@/utils'
const route = useRoute()
const expandAll = ref(true)
const router = useRouter()
const user = inject('$user')
const showChapterModal = ref(false)
const currentChapter = ref(null)
const app = getCurrentInstance()
@@ -206,8 +226,10 @@ const updateLessonIndex = createResource({
const trashLesson = (lessonName, chapterName) => {
$dialog({
title: __('Delete Lesson'),
message: __('Are you sure you want to delete this lesson?'),
title: __('Delete this lesson?'),
message: __(
'Deleting this lesson will permanently remove it from the course. This action cannot be undone. Are you sure you want to continue?'
),
actions: [
{
label: __('Delete'),
@@ -246,6 +268,61 @@ const updateOutline = (e) => {
idx: e.newIndex,
})
}
const deleteChapter = createResource({
url: 'lms.lms.api.delete_chapter',
makeParams(values) {
return {
chapter: values.chapter,
}
},
onSuccess() {
outline.reload()
showToast('Success', 'Chapter deleted successfully', 'check')
},
})
const trashChapter = (chapterName) => {
$dialog({
title: __('Delete this chapter?'),
message: __(
'Deleting this chapter will also delete all its lessons and permanently remove it from the course. This action cannot be undone. Are you sure you want to continue?'
),
actions: [
{
label: __('Delete'),
theme: 'red',
variant: 'solid',
onClick(close) {
deleteChapter.submit({ chapter: chapterName })
close()
},
},
],
})
}
const redirectToChapter = (chapter) => {
event.preventDefault()
if (props.allowEdit) return
if (!chapter.is_scorm_package) return
if (!user.data) {
showToast(
__('You are not enrolled'),
__('Please enroll for this course to view this lesson'),
'alert-circle'
)
return
}
router.push({
name: 'SCORMChapter',
params: {
courseName: props.courseName,
chapterName: chapter.name,
},
})
}
</script>
<style>
.outline-lesson:has(.router-link-active) {

View File

@@ -15,8 +15,13 @@
}"
>
<template #body-content>
<div class="space-y-4">
<FormControl ref="chapterInput" label="Title" v-model="chapter.title" />
<div class="space-y-4 text-base">
<FormControl
ref="chapterInput"
label="Title"
v-model="chapter.title"
:required="true"
/>
<FormControl
:label="__('Is SCORM Package')"
v-model="chapter.is_scorm_package"
@@ -97,25 +102,14 @@ const chapter = reactive({
})
const chapterResource = createResource({
url: 'lms.lms.api.add_chapter',
url: 'lms.lms.api.upsert_chapter',
makeParams(values) {
return {
title: chapter.title,
course: props.course,
is_scorm_package: chapter.is_scorm_package,
scorm_package: chapter.scorm_package,
}
},
})
const chapterEditResource = createResource({
url: 'frappe.client.set_value',
makeParams(values) {
return {
doctype: 'Course Chapter',
name: props.chapterDetail?.name,
fieldname: 'title',
value: chapter.title,
}
},
})
@@ -140,12 +134,7 @@ const addChapter = async (close) => {
{},
{
validate() {
if (!chapter.title) {
return __('Title is required')
}
if (chapter.is_scorm_package && !chapter.scorm_package) {
return __('Please upload a SCORM package')
}
return validateChapter()
},
onSuccess: (data) => {
capture('chapter_created')
@@ -175,6 +164,15 @@ const addChapter = async (close) => {
)
}
const validateChapter = () => {
if (!chapter.title) {
return __('Title is required')
}
if (chapter.is_scorm_package && !chapter.scorm_package) {
return __('Please upload a SCORM package')
}
}
const cleanChapter = () => {
chapter.title = ''
chapter.is_scorm_package = 0
@@ -182,7 +180,7 @@ const cleanChapter = () => {
}
const editChapter = (close) => {
chapterEditResource.submit(
chapterResource.submit(
{},
{
validate() {
@@ -206,6 +204,8 @@ watch(
() => props.chapterDetail,
(newChapter) => {
chapter.title = newChapter?.title
chapter.is_scorm_package = newChapter?.is_scorm_package
chapter.scorm_package = newChapter?.scorm_package
}
)

View File

@@ -70,7 +70,11 @@
class="ProseMirror prose prose-table:table-fixed prose-td:p-2 prose-th:p-2 prose-td:border prose-th:border prose-td:border-gray-300 prose-th:border-gray-300 prose-td:relative prose-th:relative prose-th:bg-gray-100 prose-sm max-w-none !whitespace-normal"
></div>
<div class="mt-10">
<CourseOutline :courseName="course.data.name" :showOutline="true" />
<CourseOutline
:title="__('Course Outline')"
:courseName="course.data.name"
:showOutline="true"
/>
</div>
<CourseReviews
:courseName="course.data.name"

View File

@@ -244,7 +244,10 @@ const lesson = createResource({
auto: true,
onSuccess(data) {
if (Object.keys(data).length === 0) {
router.push({ name: 'Courses' })
router.push({
name: 'CourseDetail',
params: { courseName: props.courseName },
})
return
}
lessonProgress.value = data.membership?.progress

View File

@@ -0,0 +1,205 @@
<template>
<header
class="sticky top-0 z-10 flex items-center justify-between border-b bg-white px-3 py-2.5 sm:px-5"
>
<Breadcrumbs class="h-7" :items="breadcrumbs" />
</header>
<div
v-if="
readyToRender &&
(enrollment.data?.length ||
user.data?.is_moderator ||
user.data?.is_instructor)
"
>
<iframe :src="chapter.doc.launch_file" class="w-full h-screen" />
</div>
<div v-else-if="!enrollment.data?.length">
<div class="text-center pt-10 px-5 md:px-0 pb-10">
<div class="text-center">
<div class="mb-4">
{{
__(
'You are not enrolled in this course. Please enroll to access this lesson.'
)
}}
</div>
<Button variant="solid" @click="enrollStudent()">
{{ __('Start Learning') }}
</Button>
</div>
</div>
</div>
</template>
<script setup>
import {
Breadcrumbs,
Button,
call,
createDocumentResource,
createListResource,
createResource,
} from 'frappe-ui'
import { computed, inject, onBeforeMount, ref } from 'vue'
import { useSidebar } from '@/stores/sidebar'
import { updateDocumentTitle } from '@/utils'
const sidebarStore = useSidebar()
const user = inject('$user')
const readyToRender = ref(false)
const props = defineProps({
courseName: {
type: String,
required: true,
},
chapterName: {
type: String,
required: true,
},
})
onBeforeMount(() => {
sidebarStore.isSidebarCollapsed = true
window.API_1484_11 = {
Initialize: () => 'true',
Terminate: () => 'true',
GetValue: (key) => {
console.log(`GetValue called for key: ${key}`)
return getDataFromLMS(key)
},
SetValue: (key, value) => {
console.log(`SetValue called for key: ${key} to value: ${value}`)
saveDataToLMS(key, value)
return 'true'
},
Commit: () => 'true',
GetLastError: () => '0',
GetErrorString: () => '',
GetDiagnostic: () => '',
}
window.API = {
LMSInitialize: () => 'true',
LMSFinish: () => 'true',
LMSGetValue: (key) => {
console.log(`GET: ${key}`)
return getDataFromLMS(key)
},
LMSSetValue: (key, value) => {
console.log(`SET: ${key} to value: ${value}`)
saveDataToLMS(key, value)
return 'true'
},
LMSCommit: () => 'true',
LMSGetLastError: () => '0',
LMSGetErrorString: () => '',
LMSGetDiagnostic: () => '',
}
console.log(window.API_1484_11)
})
const getDataFromLMS = (key) => {
if (key == 'cmi.core.lesson_status') {
if (progress.data?.status == 'Complete') {
return 'passed'
}
return 'incomplete'
}
return ''
}
const saveDataToLMS = (key, value) => {
if (key == 'cmi.core.lesson_status' && value == 'passed') {
saveProgress()
}
}
const enrollment = createListResource({
doctype: 'LMS Enrollment',
fields: ['member', 'course'],
filters: {
course: props.courseName,
member: user.data?.name,
},
auto: true,
cache: ['enrollments', props.courseName, user.data?.name],
})
const chapter = createDocumentResource({
doctype: 'Course Chapter',
name: props.chapterName,
auto: true,
cache: ['chapter', props.chapterName],
onSuccess(data) {
progress.submit()
},
})
const saveProgress = () => {
call('lms.lms.doctype.course_lesson.course_lesson.save_progress', {
lesson: chapter.doc.lessons[0].lesson,
course: props.courseName,
})
}
const progress = createResource({
url: 'frappe.client.get_value',
makeParams(values) {
return {
doctype: 'LMS Course Progress',
fieldname: 'status',
filters: {
member: user.data?.name,
lesson: chapter.doc.lessons[0].lesson,
chapter: chapter.doc.name,
course: chapter.doc?.course,
},
}
},
onSuccess(data) {
readyToRender.value = true
},
})
const enrollStudent = () => {
enrollment.insert.submit(
{
course: props.courseName,
member: user.data?.name,
},
{
onSuccess(data) {
window.location.reload()
},
}
)
}
const breadcrumbs = computed(() => {
return [
{
label: 'Courses',
route: { name: 'Courses' },
},
{
label: chapter.doc?.course_title,
route: { name: 'CourseDetail', params: { courseName: props.courseName } },
},
{
label: chapter.doc?.title,
},
]
})
const pageMeta = computed(() => {
return {
title: chapter?.doc?.title,
description: __('This is a chapter in the course {0}').format(
chapter?.doc?.course_title
),
}
})
updateDocumentTitle(pageMeta)
</script>

View File

@@ -27,6 +27,12 @@ const routes = [
component: () => import('@/pages/Lesson.vue'),
props: true,
},
{
path: '/courses/:courseName/learn/:chapterName',
name: 'SCORMChapter',
component: () => import('@/pages/SCORMChapter.vue'),
props: true,
},
{
path: '/batches',
name: 'Batches',

View File

@@ -0,0 +1,10 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useSidebar = defineStore('sidebar', () => {
const isSidebarCollapsed = ref(false)
return {
isSidebarCollapsed,
}
})

View File

@@ -93,7 +93,7 @@ export function showToast(title, text, icon, iconClasses = null) {
if (!iconClasses) {
if (icon == 'check') {
iconClasses = 'bg-green-600 text-white rounded-md p-px'
} else if (icon == 'circle-warn') {
} else if (icon == 'alert-circle') {
iconClasses = 'bg-yellow-600 text-white rounded-md p-px'
} else {
iconClasses = 'bg-red-600 text-white rounded-md p-px'

View File

@@ -5,6 +5,7 @@ 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 _
@@ -883,38 +884,47 @@ def give_dicussions_permission():
@frappe.whitelist()
def add_chapter(title, course, is_scorm_package, scorm_package):
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}
)
scorm_package = frappe._dict(scorm_package)
if is_scorm_package:
package = frappe.get_doc("File", scorm_package.name)
zip_path = package.get_full_path()
# Extract the zip file
extract_path = frappe.get_site_path("public", "files", "scorm", course, title)
zipfile.ZipFile(zip_path).extractall(extract_path)
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,
"manifest_file": get_manifest_file(extract_path),
"launch_file": get_launch_file(extract_path),
"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],
}
)
chapter = frappe.new_doc("Course Chapter")
print(values.title)
if name:
chapter = frappe.get_doc("Course Chapter", name)
else:
chapter = frappe.new_doc("Course Chapter")
chapter.update(values)
print(chapter.title)
chapter.insert()
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):
@@ -930,16 +940,17 @@ def get_manifest_file(extract_path):
def get_launch_file(extract_path):
launch_file = None
manifest_file = get_manifest_file(extract_path)
print(extract_path)
if manifest_file:
with open(manifest_file) as file:
data = file.read()
print(data)
dom = parseString(data)
resource = dom.getElementsByTagName("resource")
for res in resource:
if res.getAttribute("adlcp:scormtype") == "sco":
if (
res.getAttribute("adlcp:scormtype") == "sco"
or res.getAttribute("adlcp:scormType") == "sco"
):
launch_file = res.getAttribute("href")
break
@@ -947,3 +958,47 @@ def get_launch_file(extract_path):
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

@@ -11,6 +11,7 @@
"title",
"column_break_3",
"course",
"course_title",
"scorm_section",
"is_scorm_package",
"scorm_package",
@@ -93,6 +94,13 @@
"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,
@@ -103,7 +111,7 @@
"link_fieldname": "chapter"
}
],
"modified": "2024-11-11 16:25:45.586160",
"modified": "2024-11-15 12:03:31.370943",
"modified_by": "Administrator",
"module": "LMS",
"name": "Course Chapter",
@@ -123,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", "is_scorm_package", "launch_file"],
["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)