Merge pull request #1025 from pateljannat/categories-in-courses
feat: course categories
This commit is contained in:
@@ -31,12 +31,35 @@ describe("Course Creation", () => {
|
||||
.contains("Preview Video")
|
||||
.type("https://www.youtube.com/embed/-LPmw2Znl2c");
|
||||
cy.get("[id=tags]").type("Learning{enter}Frappe{enter}ERPNext{enter}");
|
||||
cy.get(".search-input").click().type("frappe");
|
||||
cy.wait(1000);
|
||||
cy.get("label")
|
||||
.contains("Category")
|
||||
.parent()
|
||||
.within(() => {
|
||||
cy.get("button").click();
|
||||
});
|
||||
cy.get("[id^=headlessui-combobox-option-")
|
||||
.should("be.visible")
|
||||
.first()
|
||||
.click();
|
||||
|
||||
/* Instructor */
|
||||
cy.get("label")
|
||||
.contains("Instructors")
|
||||
.parent()
|
||||
.within(() => {
|
||||
cy.get("input").click().type("frappe");
|
||||
cy.get("input")
|
||||
.invoke("attr", "aria-controls")
|
||||
.as("instructor_list_id");
|
||||
});
|
||||
cy.get("@instructor_list_id").then((instructor_list_id) => {
|
||||
cy.get(`[id^=${instructor_list_id}`)
|
||||
.should("be.visible")
|
||||
.within(() => {
|
||||
cy.get("[id^=headlessui-combobox-option-").first().click();
|
||||
});
|
||||
});
|
||||
|
||||
cy.get("label").contains("Published").click();
|
||||
cy.get("label").contains("Published On").type("2021-01-01");
|
||||
cy.button("Save").click();
|
||||
|
||||
151
frontend/src/components/Categories.vue
Normal file
151
frontend/src/components/Categories.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<div class="flex flex-col min-h-0">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-xl font-semibold mb-1">
|
||||
{{ label }}
|
||||
</div>
|
||||
<Button @click="() => showCategoryForm()">
|
||||
<template #icon>
|
||||
<Plus v-if="!showForm" class="h-3 w-3 stroke-1.5" />
|
||||
<X v-else class="h-3 w-3 stroke-1.5" />
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="showForm"
|
||||
class="flex items-center justify-between my-4 space-x-2"
|
||||
>
|
||||
<FormControl
|
||||
ref="categoryInput"
|
||||
v-model="category"
|
||||
:placeholder="__('Category Name')"
|
||||
class="flex-1"
|
||||
/>
|
||||
<Button @click="addCategory()" variant="subtle">
|
||||
{{ __('Add') }}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="overflow-y-scroll">
|
||||
<div class="text-base divide-y">
|
||||
<FormControl
|
||||
:value="cat.category"
|
||||
type="text"
|
||||
v-for="cat in categories.data"
|
||||
class="form-control"
|
||||
@change.stop="(e) => update(cat.name, e.target.value)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
createListResource,
|
||||
createResource,
|
||||
debounce,
|
||||
} from 'frappe-ui'
|
||||
import { Plus, X } from 'lucide-vue-next'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const showForm = ref(false)
|
||||
const category = ref(null)
|
||||
const categoryInput = ref(null)
|
||||
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
const categories = createListResource({
|
||||
doctype: 'LMS Category',
|
||||
fields: ['name', 'category'],
|
||||
auto: true,
|
||||
})
|
||||
|
||||
const newCategory = createResource({
|
||||
url: 'frappe.client.insert',
|
||||
makeParams(values) {
|
||||
return {
|
||||
doc: {
|
||||
doctype: 'LMS Category',
|
||||
category: category.value,
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const addCategory = () => {
|
||||
newCategory.submit(
|
||||
{},
|
||||
{
|
||||
onSuccess(data) {
|
||||
categories.reload()
|
||||
category.value = null
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const showCategoryForm = () => {
|
||||
showForm.value = !showForm.value
|
||||
setTimeout(() => {
|
||||
categoryInput.value.$el.querySelector('input').focus()
|
||||
}, 0)
|
||||
}
|
||||
|
||||
const updateCategory = createResource({
|
||||
url: 'frappe.client.rename_doc',
|
||||
makeParams(values) {
|
||||
return {
|
||||
doctype: 'LMS Category',
|
||||
old_name: values.name,
|
||||
new_name: values.category,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const update = (name, value) => {
|
||||
updateCategory.submit(
|
||||
{
|
||||
name: name,
|
||||
category: value,
|
||||
},
|
||||
{
|
||||
onSuccess() {
|
||||
categories.reload()
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.form-control input {
|
||||
padding: 1.25rem 0;
|
||||
border-color: transparent;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.form-control input:focus {
|
||||
outline: transparent;
|
||||
background: white;
|
||||
box-shadow: none;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.form-control input:hover {
|
||||
outline: transparent;
|
||||
background: white;
|
||||
box-shadow: none;
|
||||
border-color: transparent;
|
||||
}
|
||||
</style>
|
||||
@@ -6,7 +6,7 @@
|
||||
<h1 class="mb-3 px-2 pt-2 text-lg font-semibold">
|
||||
{{ __('Settings') }}
|
||||
</h1>
|
||||
<div v-for="tab in tabs">
|
||||
<div v-for="tab in tabs" :key="tab.label">
|
||||
<div
|
||||
v-if="!tab.hideLabel"
|
||||
class="mb-2 mt-3 flex cursor-pointer gap-1.5 px-1 text-base font-medium text-gray-600 transition-all duration-300 ease-in-out"
|
||||
@@ -17,6 +17,7 @@
|
||||
<SidebarLink
|
||||
v-for="item in tab.items"
|
||||
:link="item"
|
||||
:key="item.label"
|
||||
class="w-full"
|
||||
:class="
|
||||
activeTab?.label == item.label
|
||||
@@ -30,6 +31,7 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="activeTab && data.doc"
|
||||
:key="activeTab.label"
|
||||
class="flex flex-1 flex-col px-10 py-8"
|
||||
>
|
||||
<Members
|
||||
@@ -38,6 +40,11 @@
|
||||
:description="activeTab.description"
|
||||
v-model:show="show"
|
||||
/>
|
||||
<Categories
|
||||
v-else-if="activeTab.label === 'Categories'"
|
||||
:label="activeTab.label"
|
||||
:description="activeTab.description"
|
||||
/>
|
||||
<SettingDetails
|
||||
v-else
|
||||
:fields="activeTab.fields"
|
||||
@@ -53,13 +60,16 @@
|
||||
<script setup>
|
||||
import { Dialog, createDocumentResource } from 'frappe-ui'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useSettings } from '@/stores/settings'
|
||||
import SettingDetails from '../SettingDetails.vue'
|
||||
import SidebarLink from '@/components/SidebarLink.vue'
|
||||
import Members from '@/components/Members.vue'
|
||||
import Categories from '@/components/Categories.vue'
|
||||
|
||||
const show = defineModel()
|
||||
const doctype = ref('LMS Settings')
|
||||
const activeTab = ref(null)
|
||||
const settingsStore = useSettings()
|
||||
|
||||
const data = createDocumentResource({
|
||||
doctype: doctype.value,
|
||||
@@ -69,8 +79,8 @@ const data = createDocumentResource({
|
||||
auto: true,
|
||||
})
|
||||
|
||||
const tabs = computed(() => {
|
||||
let _tabs = [
|
||||
const tabsStructure = computed(() => {
|
||||
return [
|
||||
{
|
||||
label: 'Settings',
|
||||
hideLabel: true,
|
||||
@@ -80,6 +90,23 @@ const tabs = computed(() => {
|
||||
description: 'Manage the members of your learning system',
|
||||
icon: 'UserRoundPlus',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
hideLabel: true,
|
||||
items: [
|
||||
{
|
||||
label: 'Categories',
|
||||
description: 'Manage the members of your learning system',
|
||||
icon: 'Network',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
hideLabel: true,
|
||||
items: [
|
||||
{
|
||||
label: 'Payment Gateway',
|
||||
icon: 'DollarSign',
|
||||
@@ -125,8 +152,8 @@ const tabs = computed(() => {
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
hideLabel: true,
|
||||
label: 'Customise',
|
||||
hideLabel: false,
|
||||
items: [
|
||||
{
|
||||
label: 'Sidebar',
|
||||
@@ -168,12 +195,6 @@ const tabs = computed(() => {
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
hideLabel: true,
|
||||
items: [
|
||||
{
|
||||
label: 'Email Templates',
|
||||
icon: 'MailPlus',
|
||||
@@ -199,12 +220,6 @@ const tabs = computed(() => {
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
hideLabel: true,
|
||||
items: [
|
||||
{
|
||||
label: 'Signup',
|
||||
icon: 'LogIn',
|
||||
@@ -226,23 +241,28 @@ const tabs = computed(() => {
|
||||
],
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
return _tabs.map((tab) => {
|
||||
tab.items = tab.items.filter((item) => {
|
||||
if (item.condition) {
|
||||
return item.condition()
|
||||
}
|
||||
return true
|
||||
})
|
||||
return tab
|
||||
const tabs = computed(() => {
|
||||
return tabsStructure.value.map((tab) => {
|
||||
return {
|
||||
...tab,
|
||||
items: tab.items.filter((item) => {
|
||||
return !item.condition || item.condition()
|
||||
}),
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
watch(show, () => {
|
||||
watch(show, async () => {
|
||||
if (show.value) {
|
||||
activeTab.value = tabs.value[0].items[0]
|
||||
const currentTab = await tabs.value
|
||||
.flatMap((tab) => tab.items)
|
||||
.find((item) => item.label === settingsStore.activeTab)
|
||||
activeTab.value = currentTab || tabs.value[0].items[0]
|
||||
} else {
|
||||
activeTab.value = null
|
||||
settingsStore.isSettingsOpen = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -67,25 +67,20 @@ import LMSLogo from '@/components/Icons/LMSLogo.vue'
|
||||
import { sessionStore } from '@/stores/session'
|
||||
import { Dropdown } from 'frappe-ui'
|
||||
import Apps from '@/components/Apps.vue'
|
||||
import {
|
||||
ChevronDown,
|
||||
LogIn,
|
||||
LogOut,
|
||||
User,
|
||||
ArrowRightLeft,
|
||||
Settings,
|
||||
} from 'lucide-vue-next'
|
||||
import { ChevronDown, LogIn, LogOut, User, Settings } from 'lucide-vue-next'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { convertToTitleCase } from '../utils'
|
||||
import { usersStore } from '@/stores/user'
|
||||
import { ref, markRaw } from 'vue'
|
||||
import { useSettings } from '@/stores/settings'
|
||||
import { markRaw, watch, ref } from 'vue'
|
||||
import SettingsModal from '@/components/Modals/Settings.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const showSettingsModal = ref(false)
|
||||
const { logout, branding } = sessionStore()
|
||||
let { userResource } = usersStore()
|
||||
const settingsStore = useSettings()
|
||||
let { isLoggedIn } = sessionStore()
|
||||
const showSettingsModal = ref(false)
|
||||
|
||||
const props = defineProps({
|
||||
isCollapsed: {
|
||||
@@ -94,6 +89,13 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
watch(
|
||||
() => settingsStore.isSettingsOpen,
|
||||
(value) => {
|
||||
showSettingsModal.value = value
|
||||
}
|
||||
)
|
||||
|
||||
const userDropdownOptions = [
|
||||
{
|
||||
icon: User,
|
||||
@@ -118,7 +120,7 @@ const userDropdownOptions = [
|
||||
icon: Settings,
|
||||
label: 'Settings',
|
||||
onClick: () => {
|
||||
showSettingsModal.value = true
|
||||
settingsStore.isSettingsOpen = true
|
||||
},
|
||||
condition: () => {
|
||||
return userResource.data?.is_moderator
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
:items="[{ label: __('Batches'), route: { name: 'Batches' } }]"
|
||||
/>
|
||||
<div class="flex space-x-2">
|
||||
<div class="w-40">
|
||||
<div class="w-44">
|
||||
<Select
|
||||
v-if="categories.data?.length"
|
||||
v-model="currentCategory"
|
||||
:options="categories.data"
|
||||
:placeholder="__('Filter')"
|
||||
:placeholder="__('Category')"
|
||||
/>
|
||||
</div>
|
||||
<router-link
|
||||
|
||||
@@ -109,6 +109,14 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-1/2 mb-4">
|
||||
<Link
|
||||
doctype="LMS Category"
|
||||
v-model="course.category"
|
||||
:label="__('Category')"
|
||||
:onCreate="(value, close) => openSettings(close)"
|
||||
/>
|
||||
</div>
|
||||
<MultiSelect
|
||||
v-model="instructors"
|
||||
doctype="User"
|
||||
@@ -221,18 +229,20 @@ import {
|
||||
showToast,
|
||||
getFileSize,
|
||||
updateDocumentTitle,
|
||||
} from '../utils'
|
||||
} from '@/utils'
|
||||
import Link from '@/components/Controls/Link.vue'
|
||||
import { FileText, X } from 'lucide-vue-next'
|
||||
import { useRouter } from 'vue-router'
|
||||
import CourseOutline from '@/components/CourseOutline.vue'
|
||||
import MultiSelect from '@/components/Controls/MultiSelect.vue'
|
||||
import { capture } from '@/telemetry'
|
||||
import { useSettings } from '@/stores/settings'
|
||||
|
||||
const user = inject('$user')
|
||||
const newTag = ref('')
|
||||
const router = useRouter()
|
||||
const instructors = ref([])
|
||||
const settingsStore = useSettings()
|
||||
|
||||
const props = defineProps({
|
||||
courseName: {
|
||||
@@ -463,6 +473,12 @@ const removeImage = () => {
|
||||
course.course_image = null
|
||||
}
|
||||
|
||||
const openSettings = (close) => {
|
||||
close()
|
||||
settingsStore.activeTab = 'Categories'
|
||||
settingsStore.isSettingsOpen = true
|
||||
}
|
||||
|
||||
const check_permission = () => {
|
||||
let user_is_instructor = false
|
||||
if (user.data?.is_moderator) return
|
||||
|
||||
@@ -8,6 +8,15 @@
|
||||
:items="[{ label: __('Courses'), route: { name: 'Courses' } }]"
|
||||
/>
|
||||
<div class="flex space-x-2 justify-end">
|
||||
<div class="w-44">
|
||||
<FormControl
|
||||
v-if="categories.data?.length"
|
||||
type="select"
|
||||
v-model="currentCategory"
|
||||
:options="categories.data"
|
||||
:placeholder="__('Category')"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-36">
|
||||
<FormControl
|
||||
type="text"
|
||||
@@ -119,11 +128,19 @@ import {
|
||||
} from 'frappe-ui'
|
||||
import CourseCard from '@/components/CourseCard.vue'
|
||||
import { Plus, Search } from 'lucide-vue-next'
|
||||
import { ref, computed, inject } from 'vue'
|
||||
import { ref, computed, inject, onMounted, watch } from 'vue'
|
||||
import { updateDocumentTitle } from '@/utils'
|
||||
|
||||
const user = inject('$user')
|
||||
const searchQuery = ref('')
|
||||
const currentCategory = ref(null)
|
||||
|
||||
onMounted(() => {
|
||||
let queries = new URLSearchParams(location.search)
|
||||
if (queries.has('category')) {
|
||||
currentCategory.value = queries.get('category')
|
||||
}
|
||||
})
|
||||
|
||||
const courses = createResource({
|
||||
url: 'lms.lms.utils.get_courses',
|
||||
@@ -168,18 +185,57 @@ const addToTabs = (label) => {
|
||||
}
|
||||
|
||||
const getCourses = (type) => {
|
||||
let courseList = courses.data[type]
|
||||
if (searchQuery.value) {
|
||||
let query = searchQuery.value.toLowerCase()
|
||||
return courses.data[type].filter(
|
||||
courseList = courseList.filter(
|
||||
(course) =>
|
||||
course.title.toLowerCase().includes(query) ||
|
||||
course.short_introduction.toLowerCase().includes(query) ||
|
||||
course.tags.filter((tag) => tag.toLowerCase().includes(query)).length
|
||||
)
|
||||
}
|
||||
return courses.data[type]
|
||||
if (currentCategory.value && currentCategory.value != '') {
|
||||
courseList = courseList.filter(
|
||||
(course) => course.category == currentCategory.value
|
||||
)
|
||||
}
|
||||
return courseList
|
||||
}
|
||||
|
||||
const categories = createResource({
|
||||
url: 'lms.lms.api.get_categories',
|
||||
makeParams() {
|
||||
return {
|
||||
doctype: 'LMS Course',
|
||||
filters: {
|
||||
published: 1,
|
||||
},
|
||||
}
|
||||
},
|
||||
cache: ['courseCategories'],
|
||||
auto: true,
|
||||
transform(data) {
|
||||
data.unshift({
|
||||
label: '',
|
||||
value: null,
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
watch(
|
||||
() => currentCategory.value,
|
||||
() => {
|
||||
let queries = new URLSearchParams(location.search)
|
||||
if (currentCategory.value) {
|
||||
queries.set('category', currentCategory.value)
|
||||
} else {
|
||||
queries.delete('category')
|
||||
}
|
||||
history.pushState(null, '', `${location.pathname}?${queries.toString()}`)
|
||||
}
|
||||
)
|
||||
|
||||
const pageMeta = computed(() => {
|
||||
return {
|
||||
title: 'Courses',
|
||||
|
||||
12
frontend/src/stores/settings.js
Normal file
12
frontend/src/stores/settings.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useSettings = defineStore('settings', () => {
|
||||
const isSettingsOpen = ref(false)
|
||||
const activeTab = ref(null)
|
||||
|
||||
return {
|
||||
isSettingsOpen,
|
||||
activeTab,
|
||||
}
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
51
lms/fixtures/lms_category.json
Normal file
51
lms/fixtures/lms_category.json
Normal file
@@ -0,0 +1,51 @@
|
||||
[
|
||||
{
|
||||
"category": "Web Development",
|
||||
"docstatus": 0,
|
||||
"doctype": "LMS Category",
|
||||
"modified": "2024-09-20 12:58:16.841571",
|
||||
"name": "Web Development"
|
||||
},
|
||||
{
|
||||
"category": "Business",
|
||||
"docstatus": 0,
|
||||
"doctype": "LMS Category",
|
||||
"modified": "2024-09-20 12:58:32.304850",
|
||||
"name": "Business"
|
||||
},
|
||||
{
|
||||
"category": "Design",
|
||||
"docstatus": 0,
|
||||
"doctype": "LMS Category",
|
||||
"modified": "2024-09-20 12:59:12.621022",
|
||||
"name": "Design"
|
||||
},
|
||||
{
|
||||
"category": "Personal Development",
|
||||
"docstatus": 0,
|
||||
"doctype": "LMS Category",
|
||||
"modified": "2024-09-20 12:59:19.287404",
|
||||
"name": "Personal Development"
|
||||
},
|
||||
{
|
||||
"category": "Finance",
|
||||
"docstatus": 0,
|
||||
"doctype": "LMS Category",
|
||||
"modified": "2024-09-20 12:58:28.579714",
|
||||
"name": "Finance"
|
||||
},
|
||||
{
|
||||
"category": "Frontend",
|
||||
"docstatus": 0,
|
||||
"doctype": "LMS Category",
|
||||
"modified": "2024-05-08 14:05:16.979275",
|
||||
"name": "Frontend"
|
||||
},
|
||||
{
|
||||
"category": "Framework",
|
||||
"docstatus": 0,
|
||||
"doctype": "LMS Category",
|
||||
"modified": "2023-06-15 18:01:41.598282",
|
||||
"name": "Framework"
|
||||
}
|
||||
]
|
||||
@@ -115,7 +115,7 @@ scheduler_events = {
|
||||
"daily": ["lms.job.doctype.job_opportunity.job_opportunity.update_job_openings"],
|
||||
}
|
||||
|
||||
fixtures = ["Custom Field", "Function", "Industry"]
|
||||
fixtures = ["Custom Field", "Function", "Industry", "LMS Category"]
|
||||
|
||||
# Testing
|
||||
# -------
|
||||
|
||||
@@ -15,12 +15,13 @@
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Category",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-06-15 15:14:11.341961",
|
||||
"modified": "2024-09-23 19:33:49.593950",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Category",
|
||||
@@ -55,5 +56,6 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "category"
|
||||
"title_field": "category",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -16,10 +16,12 @@
|
||||
"field_order": [
|
||||
"title",
|
||||
"video_link",
|
||||
"image",
|
||||
"column_break_3",
|
||||
"instructors",
|
||||
"tags",
|
||||
"column_break_htgn",
|
||||
"image",
|
||||
"category",
|
||||
"status",
|
||||
"section_break_7",
|
||||
"published",
|
||||
@@ -237,6 +239,16 @@
|
||||
"fieldname": "certification_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Certification"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_htgn",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "category",
|
||||
"fieldtype": "Link",
|
||||
"label": "Category",
|
||||
"options": "LMS Category"
|
||||
}
|
||||
],
|
||||
"is_published_field": "published",
|
||||
@@ -263,7 +275,7 @@
|
||||
}
|
||||
],
|
||||
"make_attachments_public": 1,
|
||||
"modified": "2024-07-12 13:54:40.474097",
|
||||
"modified": "2024-09-21 10:23:58.633912",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Course",
|
||||
|
||||
@@ -722,17 +722,6 @@ def get_lesson_count(course):
|
||||
return lesson_count
|
||||
|
||||
|
||||
def get_restriction_details():
|
||||
user = frappe.db.get_value(
|
||||
"User", frappe.session.user, ["profile_complete", "username"], as_dict=True
|
||||
)
|
||||
return {
|
||||
"restrict": not user.profile_complete,
|
||||
"username": user.username,
|
||||
"prefix": frappe.get_hooks("profile_url_prefix")[0] or "/users/",
|
||||
}
|
||||
|
||||
|
||||
def get_all_memberships(member):
|
||||
return frappe.get_all(
|
||||
"LMS Enrollment",
|
||||
@@ -1220,6 +1209,7 @@ def get_course_details(course):
|
||||
"featured",
|
||||
"disable_self_learning",
|
||||
"published_on",
|
||||
"category",
|
||||
"status",
|
||||
"paid_course",
|
||||
"course_price",
|
||||
|
||||
Reference in New Issue
Block a user