feat: course details page
This commit is contained in:
@@ -68,10 +68,8 @@ import { BookOpen, Users, Star } from 'lucide-vue-next'
|
|||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import UserAvatar from '@/components/UserAvatar.vue'
|
import UserAvatar from '@/components/UserAvatar.vue'
|
||||||
import { sessionStore } from '@/stores/session'
|
import { sessionStore } from '@/stores/session'
|
||||||
import { usersStore } from '@/stores/user'
|
|
||||||
|
|
||||||
const { isLoggedIn } = sessionStore()
|
const { isLoggedIn, getUser } = sessionStore()
|
||||||
const { getUser } = usersStore()
|
|
||||||
const user = computed(() => isLoggedIn && getUser())
|
const user = computed(() => isLoggedIn && getUser())
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
47
frontend/src/components/CourseCardOverlay.vue
Normal file
47
frontend/src/components/CourseCardOverlay.vue
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<template>
|
||||||
|
<div class="border border-gray-200" style="width: 300px;">
|
||||||
|
<iframe v-if="course.data.video_link" :src="video_link" />
|
||||||
|
<div>
|
||||||
|
<Button variant="solid" class="w-full">
|
||||||
|
<span>
|
||||||
|
{{ __("Start Learning") }}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<Users class="h-4 w-4 text-gray-700"/>
|
||||||
|
<span class="ml-1">
|
||||||
|
{{ course.data.enrollment_count }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<Star class="h-5 w-5 fill-orange-500 text-gray-100"/>
|
||||||
|
<span class="ml-1">
|
||||||
|
{{ course.data.avg_rating }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<BookOpen class="h-4 w-4 text-gray-700"/>
|
||||||
|
<span class="ml-1">
|
||||||
|
{{ course.data.lesson_count }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { BookOpen, Users, Star } from 'lucide-vue-next'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
const props = defineProps({
|
||||||
|
course: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const video_link = computed(() => {
|
||||||
|
if (props.course.data.video_link) {
|
||||||
|
return "https://www.youtube.com/embed/" + props.course.data.video_link;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
21
frontend/src/components/CourseOutline.vue
Normal file
21
frontend/src/components/CourseOutline.vue
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<template>
|
||||||
|
{{ outline }}
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { createResource } from "frappe-ui";
|
||||||
|
const props = defineProps({
|
||||||
|
courseName: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(props);
|
||||||
|
const outline = createResource({
|
||||||
|
url: "lms.lms.utils.get_course_outline",
|
||||||
|
cache: ["course_outline", props.courseName],
|
||||||
|
params: {
|
||||||
|
course: props.courseName
|
||||||
|
},
|
||||||
|
auto: true,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -33,7 +33,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import LMSLogo from '@/components/Icons/LMSLogo.vue'
|
import LMSLogo from '@/components/Icons/LMSLogo.vue'
|
||||||
import { sessionStore } from '@/stores/session'
|
import { sessionStore } from '@/stores/session'
|
||||||
import { usersStore } from '@/stores/user'
|
|
||||||
import { Dropdown } from 'frappe-ui'
|
import { Dropdown } from 'frappe-ui'
|
||||||
import { ChevronDown } from 'lucide-vue-next'
|
import { ChevronDown } from 'lucide-vue-next'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
@@ -45,16 +44,17 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const { logout, isLoggedIn } = sessionStore()
|
const { getUser, logout } = sessionStore()
|
||||||
const { getUser } = usersStore()
|
let { isLoggedIn } = sessionStore();
|
||||||
|
|
||||||
const user = computed(() => isLoggedIn && getUser())
|
const user = computed(() => isLoggedIn && getUser())
|
||||||
const userDropdownOptions = [
|
const userDropdownOptions = [
|
||||||
{
|
{
|
||||||
icon: 'log-out',
|
icon: 'log-out',
|
||||||
label: 'Log out',
|
label: 'Log out',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
logout.submit()
|
logout.submit().then(() => {
|
||||||
|
isLoggedIn = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
condition: () => {
|
condition: () => {
|
||||||
return isLoggedIn
|
return isLoggedIn
|
||||||
|
|||||||
@@ -1,7 +1,90 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div v-if="course.data" class="h-screen text-base">
|
||||||
Course Detail
|
<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 class="m-5">
|
||||||
|
<div>
|
||||||
|
<div class="text-3xl font-semibold">
|
||||||
|
{{ course.data.title }}
|
||||||
|
</div>
|
||||||
|
<div class="mt-1">
|
||||||
|
{{ course.data.short_introduction }}
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between mt-3 w-1/3">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<Star class="h-5 w-5 text-gray-100 fill-orange-500"/>
|
||||||
|
<span class="ml-1">
|
||||||
|
{{ course.data.avg_rating }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
·
|
||||||
|
<div class="flex items-center">
|
||||||
|
<Users class="h-4 w-4 text-gray-700"/>
|
||||||
|
<span class="ml-1">
|
||||||
|
{{ course.data.enrollment_count }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
·
|
||||||
|
<div class="flex items-center">
|
||||||
|
<BookOpen class="h-4 w-4 text-gray-700 mr-1"/>
|
||||||
|
<span v-if="course.data.instructors.length == 1">
|
||||||
|
{{ course.data.instructors[0].full_name }}
|
||||||
|
</span>
|
||||||
|
<span v-if="course.data.instructors.length == 2">
|
||||||
|
{{ course.data.instructors[0].first_name }} and {{ course.data.instructors[1].first_name }}
|
||||||
|
</span>
|
||||||
|
<span v-if="course.data.instructors.length > 2">
|
||||||
|
{{ course.data.instructors[0].first_name }} and {{ course.data.instructors.length - 1 }} others
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-[70%,20%] gap-10">
|
||||||
|
<div>
|
||||||
|
<div v-html="course.data.description"></div>
|
||||||
|
<CourseOutline :courseName="course.data.name"/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<CourseCardOverlay :course="course"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { createResource, Breadcrumbs } from "frappe-ui";
|
||||||
|
import { computed } from "vue";
|
||||||
|
import { BookOpen, Users, Star } from 'lucide-vue-next'
|
||||||
|
import CourseCardOverlay from '@/components/CourseCardOverlay.vue';
|
||||||
|
import CourseOutline from '@/components/CourseOutline.vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
courseName: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
console.log(props.courseName)
|
||||||
|
const course = createResource({
|
||||||
|
url: "lms.lms.utils.get_course_details",
|
||||||
|
cache: ["course", props.courseName],
|
||||||
|
params: {
|
||||||
|
course: props.courseName
|
||||||
|
},
|
||||||
|
auto: true,
|
||||||
|
});
|
||||||
|
console.log(course)
|
||||||
|
const breadcrumbs = computed(() => {
|
||||||
|
let items = [{ label: "All Courses", route: { name: "Courses" } }]
|
||||||
|
items.push({
|
||||||
|
label: course?.data?.title,
|
||||||
|
route: { name: "CourseDetail", params: { course: course?.data?.name } },
|
||||||
|
})
|
||||||
|
return items
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
@@ -42,8 +42,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #default="{ tab }">
|
<template #default="{ tab }">
|
||||||
<div v-if="tab.courses && tab.courses.value.length" class="grid grid-cols-3 gap-8 mt-5" >
|
<div v-if="tab.courses && tab.courses.value.length" class="grid grid-cols-3 gap-8 mt-5">
|
||||||
<router-link v-for="course in tab.courses.value" :to="{ name: 'CourseDetail', params: { course: course.name } }">
|
<router-link v-for="course in tab.courses.value" :to="{ name: 'CourseDetail', params: { courseName: course.name } }">
|
||||||
<CourseCard :course="course" />
|
<CourseCard :course="course" />
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
@@ -62,17 +62,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { sessionStore } from '@/stores/session'
|
||||||
import { createListResource, Breadcrumbs, Tabs, Badge, Select } from 'frappe-ui';
|
import { createListResource, Breadcrumbs, Tabs, Badge, Select } from 'frappe-ui';
|
||||||
import CourseCard from '@/components/CourseCard.vue';
|
import CourseCard from '@/components/CourseCard.vue';
|
||||||
import { Plus } from 'lucide-vue-next'
|
import { Plus } from 'lucide-vue-next'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { usersStore } from '@/stores/user'
|
|
||||||
import { sessionStore } from '@/stores/session'
|
|
||||||
|
|
||||||
const { isLoggedIn } = sessionStore()
|
const { isLoggedIn, getUser } = sessionStore()
|
||||||
const { getUser } = usersStore()
|
|
||||||
const user = computed(() => isLoggedIn && getUser())
|
const user = computed(() => isLoggedIn && getUser())
|
||||||
|
|
||||||
const courses = createListResource({
|
const courses = createListResource({
|
||||||
type: 'list',
|
type: 'list',
|
||||||
cache: "courses",
|
cache: "courses",
|
||||||
@@ -81,39 +78,58 @@ const courses = createListResource({
|
|||||||
auto: true,
|
auto: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const is_moderator = computed(() => {
|
||||||
|
if (user && user.value?.roles?.includes('Moderator')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const is_instructor = computed(() => {
|
||||||
|
if (user && user.value?.roles?.includes('Course Creator')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
const tabIndex = ref(0)
|
const tabIndex = ref(0)
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
label: 'Live',
|
label: 'Live',
|
||||||
courses: computed(() => courses.data?.live || []),
|
courses: computed(() => courses.data?.live || []),
|
||||||
count: computed(() => courses.data?.live?.length),
|
count: computed(() => courses.data?.live?.length),
|
||||||
show: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Upcoming',
|
label: 'Upcoming',
|
||||||
courses: computed(() => courses.data?.upcoming),
|
courses: computed(() => courses.data?.upcoming),
|
||||||
count: computed(() => courses.data?.upcoming?.length),
|
count: computed(() => courses.data?.upcoming?.length),
|
||||||
show: true
|
}
|
||||||
},
|
];
|
||||||
{
|
|
||||||
|
if (user.value) {
|
||||||
|
tabs.push({
|
||||||
label: 'Enrolled',
|
label: 'Enrolled',
|
||||||
courses: computed(() => courses.data?.enrolled),
|
courses: computed(() => courses.data?.enrolled),
|
||||||
count: computed(() => courses.data?.enrolled?.length),
|
count: computed(() => courses.data?.enrolled?.length),
|
||||||
show: user
|
});
|
||||||
},
|
|
||||||
{
|
if (is_moderator.value || is_instructor.value || courses.data?.created?.length) {
|
||||||
label: 'Created',
|
tabs.push({
|
||||||
courses: computed(() => courses.data?.created),
|
label: 'Created',
|
||||||
count: computed(() => courses.data?.created?.length),
|
courses: computed(() => courses.data?.created),
|
||||||
show: computed(() => user && (user.roles.includes('Course Creator') || user.roles.includes('Moderator')))
|
count: computed(() => courses.data?.created?.length),
|
||||||
},
|
});
|
||||||
{
|
};
|
||||||
label: 'Under Review',
|
|
||||||
courses: computed(() => courses.data?.under_review),
|
if (is_moderator.value) {
|
||||||
count: computed(() => courses.data?.under_review?.length),
|
tabs.push({
|
||||||
show: computed(() => user && user.roles.includes('Moderator'))
|
label: 'Under Review',
|
||||||
},
|
courses: computed(() => courses.data?.under_review),
|
||||||
];
|
count: computed(() => courses.data?.under_review?.length),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const orderOptions = [
|
const orderOptions = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import { usersStore } from '@/stores/user'
|
|
||||||
import { sessionStore } from '@/stores/session'
|
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
@@ -14,7 +12,7 @@ const routes = [
|
|||||||
component: () => import('@/pages/Courses.vue'),
|
component: () => import('@/pages/Courses.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/courses/:course',
|
path: '/courses/:courseName',
|
||||||
name: 'CourseDetail',
|
name: 'CourseDetail',
|
||||||
component: () => import('@/pages/CourseDetail.vue'),
|
component: () => import('@/pages/CourseDetail.vue'),
|
||||||
props: true,
|
props: true,
|
||||||
@@ -26,9 +24,4 @@ let router = createRouter({
|
|||||||
routes,
|
routes,
|
||||||
})
|
})
|
||||||
|
|
||||||
router.beforeEach(async (to, from) => {
|
|
||||||
const { users } = usersStore()
|
|
||||||
await users.promise
|
|
||||||
})
|
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import router from '@/router'
|
|||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
|
|
||||||
export const sessionStore = defineStore('lms-session', () => {
|
export const sessionStore = defineStore('lms-session', () => {
|
||||||
const { users } = usersStore()
|
const { user, usersByName } = usersStore()
|
||||||
|
|
||||||
function sessionUser() {
|
function currentUser() {
|
||||||
let cookies = new URLSearchParams(document.cookie.split('; ').join('&'))
|
let cookies = new URLSearchParams(document.cookie.split('; ').join('&'))
|
||||||
let _sessionUser = cookies.get('user_id')
|
let _sessionUser = cookies.get('user_id')
|
||||||
if (_sessionUser === 'Guest') {
|
if (_sessionUser === 'Guest') {
|
||||||
@@ -16,9 +16,18 @@ export const sessionStore = defineStore('lms-session', () => {
|
|||||||
return _sessionUser
|
return _sessionUser
|
||||||
}
|
}
|
||||||
|
|
||||||
let user = ref(sessionUser())
|
let sessionUser = ref(currentUser())
|
||||||
|
const isLoggedIn = ref(!!sessionUser.value)
|
||||||
|
|
||||||
const isLoggedIn = computed(() => !!user.value)
|
function getUser() {
|
||||||
|
if (!sessionUser.value) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (usersByName[sessionUser.value]) {
|
||||||
|
return usersByName[sessionUser.value]
|
||||||
|
}
|
||||||
|
return user.value
|
||||||
|
}
|
||||||
|
|
||||||
const login = createResource({
|
const login = createResource({
|
||||||
url: 'login',
|
url: 'login',
|
||||||
@@ -26,8 +35,8 @@ export const sessionStore = defineStore('lms-session', () => {
|
|||||||
throw new Error('Invalid email or password')
|
throw new Error('Invalid email or password')
|
||||||
},
|
},
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
users.reload()
|
user.reload()
|
||||||
user.value = sessionUser()
|
sessionUser.value = currentUser()
|
||||||
login.reset()
|
login.reset()
|
||||||
router.replace({ path: '/' })
|
router.replace({ path: '/' })
|
||||||
},
|
},
|
||||||
@@ -36,15 +45,16 @@ export const sessionStore = defineStore('lms-session', () => {
|
|||||||
const logout = createResource({
|
const logout = createResource({
|
||||||
url: 'logout',
|
url: 'logout',
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
users.reset()
|
user.reset()
|
||||||
user.value = null
|
sessionUser.value = null
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user,
|
sessionUser,
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
login,
|
login,
|
||||||
logout,
|
logout,
|
||||||
|
getUser,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,22 +1,19 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { createResource } from 'frappe-ui'
|
import { createResource } from 'frappe-ui'
|
||||||
import { sessionStore } from './session'
|
|
||||||
import { reactive } from 'vue'
|
import { reactive } from 'vue'
|
||||||
|
|
||||||
export const usersStore = defineStore('lms-users', () => {
|
export const usersStore = defineStore('lms-users', () => {
|
||||||
const session = sessionStore()
|
|
||||||
|
|
||||||
let usersByName = reactive({})
|
let usersByName = reactive({})
|
||||||
|
|
||||||
const users = createResource({
|
const user = createResource({
|
||||||
url: 'lms.lms.api.get_user_info',
|
url: 'lms.lms.api.get_user_info',
|
||||||
cache: 'Users',
|
cache: 'Users',
|
||||||
initialData: [],
|
initialData: [],
|
||||||
transform(users) {
|
auto: true,
|
||||||
for (let user of users) {
|
transform: (data) => {
|
||||||
usersByName[user.name] = user
|
if (data?.name && !usersByName[data.name]) {
|
||||||
|
usersByName[data.name] = data
|
||||||
}
|
}
|
||||||
return users
|
|
||||||
},
|
},
|
||||||
onError(error) {
|
onError(error) {
|
||||||
if (error && error.exc_type === 'AuthenticationError') {
|
if (error && error.exc_type === 'AuthenticationError') {
|
||||||
@@ -25,27 +22,8 @@ export const usersStore = defineStore('lms-users', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
function getUser(email) {
|
|
||||||
if (!email || email === 'sessionUser') {
|
|
||||||
email = session.user
|
|
||||||
}
|
|
||||||
if (!email) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
if (!usersByName[email]) {
|
|
||||||
usersByName[email] = {
|
|
||||||
name: email,
|
|
||||||
email: email,
|
|
||||||
full_name: email.split('@')[0],
|
|
||||||
user_image: null,
|
|
||||||
roles: ['LMS Student'],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return usersByName[email]
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
users,
|
user,
|
||||||
getUser,
|
usersByName,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -143,22 +143,18 @@ def add_mentor_to_subgroup(subgroup, email):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
@frappe.whitelist(allow_guest=True)
|
||||||
def get_user_info(user=None):
|
def get_user_info():
|
||||||
if frappe.session.user == "Guest":
|
if frappe.session.user == "Guest":
|
||||||
frappe.throw("Authentication failed", exc=frappe.AuthenticationError)
|
return None
|
||||||
filters = {}
|
|
||||||
if user:
|
|
||||||
filters["name"] = user
|
|
||||||
|
|
||||||
users = frappe.qb.get_query(
|
user = frappe.db.get_value(
|
||||||
"User",
|
"User",
|
||||||
filters=filters,
|
frappe.session.user,
|
||||||
fields=["name", "email", "enabled", "user_image", "full_name", "user_type"],
|
["name", "email", "enabled", "user_image", "full_name", "user_type"],
|
||||||
order_by="full_name asc",
|
as_dict=1,
|
||||||
distinct=True,
|
)
|
||||||
).run(as_dict=1)
|
|
||||||
user["roles"] = frappe.get_roles(user.name)
|
user["roles"] = frappe.get_roles(user.name)
|
||||||
return users
|
return user
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
|||||||
117
lms/lms/utils.py
117
lms/lms/utils.py
@@ -1152,59 +1152,65 @@ def change_currency(amount, currency, country=None):
|
|||||||
@frappe.whitelist(allow_guest=True)
|
@frappe.whitelist(allow_guest=True)
|
||||||
def get_courses():
|
def get_courses():
|
||||||
"""Returns the list of courses."""
|
"""Returns the list of courses."""
|
||||||
courses = frappe.get_all(
|
courses = []
|
||||||
"LMS Course",
|
course_list = frappe.get_all("LMS Course", pluck="name")
|
||||||
fields=[
|
for course in course_list:
|
||||||
"name",
|
courses.append(get_course_details(course))
|
||||||
"title",
|
|
||||||
"short_introduction",
|
|
||||||
"image",
|
|
||||||
"published",
|
|
||||||
"upcoming",
|
|
||||||
"status",
|
|
||||||
"paid_course",
|
|
||||||
"course_price",
|
|
||||||
"currency",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
courses = get_course_details(courses)
|
|
||||||
courses = get_categorized_courses(courses)
|
courses = get_categorized_courses(courses)
|
||||||
return courses
|
return courses
|
||||||
|
|
||||||
|
|
||||||
def get_course_details(courses):
|
@frappe.whitelist(allow_guest=True)
|
||||||
for course in courses:
|
def get_course_details(course):
|
||||||
course.tags = get_tags(course.name)
|
print(course)
|
||||||
course.lesson_count = get_lesson_count(course.name)
|
course = frappe.db.get_value(
|
||||||
|
"LMS Course",
|
||||||
|
course,
|
||||||
|
[
|
||||||
|
"name",
|
||||||
|
"title",
|
||||||
|
"description",
|
||||||
|
"image",
|
||||||
|
"video_link",
|
||||||
|
"short_introduction",
|
||||||
|
"published",
|
||||||
|
"upcoming",
|
||||||
|
"status",
|
||||||
|
],
|
||||||
|
as_dict=1,
|
||||||
|
)
|
||||||
|
print(course)
|
||||||
|
course.tags = get_tags(course.name)
|
||||||
|
course.lesson_count = get_lesson_count(course.name)
|
||||||
|
|
||||||
course.enrollment_count = frappe.db.count(
|
course.enrollment_count = frappe.db.count(
|
||||||
"LMS Enrollment", {"course": course.name, "member_type": "Student"}
|
"LMS Enrollment", {"course": course.name, "member_type": "Student"}
|
||||||
|
)
|
||||||
|
|
||||||
|
avg_rating = get_average_rating(course.name) or 0
|
||||||
|
course.avg_rating = frappe.utils.flt(
|
||||||
|
avg_rating, frappe.get_system_settings("float_precision") or 3
|
||||||
|
)
|
||||||
|
|
||||||
|
course.instructors = get_instructors(course.name)
|
||||||
|
if course.paid_course:
|
||||||
|
course.price = frappe.utils.fmt_money(course.course_price, 0, course.currency)
|
||||||
|
else:
|
||||||
|
course.price = _("Free")
|
||||||
|
|
||||||
|
if frappe.session.user == "Guest":
|
||||||
|
course.membership = None
|
||||||
|
course.is_instructor = False
|
||||||
|
else:
|
||||||
|
course.membership = frappe.db.get_value(
|
||||||
|
"LMS Enrollment",
|
||||||
|
{"member": frappe.session.user, "course": course.name},
|
||||||
|
["name", "course", "current_lesson", "progress"],
|
||||||
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
course.is_instructor = is_instructor(course.name)
|
||||||
avg_rating = get_average_rating(course.name) or 0
|
return course
|
||||||
course.avg_rating = frappe.utils.flt(
|
|
||||||
avg_rating, frappe.get_system_settings("float_precision") or 3
|
|
||||||
)
|
|
||||||
|
|
||||||
course.instructors = get_instructors(course.name)
|
|
||||||
if course.paid_course:
|
|
||||||
course.price = frappe.utils.fmt_money(course.course_price, 0, course.currency)
|
|
||||||
else:
|
|
||||||
course.price = _("Free")
|
|
||||||
|
|
||||||
if frappe.session.user == "Guest":
|
|
||||||
course.membership = None
|
|
||||||
course.is_instructor = False
|
|
||||||
else:
|
|
||||||
course.membership = frappe.db.get_value(
|
|
||||||
"LMS Enrollment",
|
|
||||||
{"member": frappe.session.user, "course": course.name},
|
|
||||||
["name", "course", "current_lesson", "progress"],
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
course.is_instructor = is_instructor(course.name)
|
|
||||||
return courses
|
|
||||||
|
|
||||||
|
|
||||||
def get_categorized_courses(courses):
|
def get_categorized_courses(courses):
|
||||||
@@ -1234,3 +1240,22 @@ def get_categorized_courses(courses):
|
|||||||
"created": created,
|
"created": created,
|
||||||
"under_review": under_review,
|
"under_review": under_review,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def get_course_outline(course):
|
||||||
|
"""Returns the course outline."""
|
||||||
|
outline = []
|
||||||
|
chapters = frappe.get_all(
|
||||||
|
"Chapter Reference", {"parent": course}, ["chapter"], order_by="idx"
|
||||||
|
)
|
||||||
|
for chapter in chapters:
|
||||||
|
chapter_details = frappe.db.get_value(
|
||||||
|
"Course Chapter",
|
||||||
|
chapter.chapter,
|
||||||
|
["name", "title", "description", "idx"],
|
||||||
|
as_dict=True,
|
||||||
|
)
|
||||||
|
chapter_details.lessons = get_lessons(chapter.chapter)
|
||||||
|
outline.append(chapter_details)
|
||||||
|
return outline
|
||||||
|
|||||||
Reference in New Issue
Block a user