Compare commits

...

22 Commits

Author SHA1 Message Date
Frappe PR Bot
b74c1670ca chore(release): Bumped to Version 2.6.0 2024-09-25 05:47:45 +00:00
Jannat Patel
04552bdef6 Merge pull request #1025 from pateljannat/categories-in-courses
feat: course categories
2024-09-24 12:55:41 +05:30
Jannat Patel
ad5bf89b35 test: enter instructor value 2024-09-24 12:32:07 +05:30
Jannat Patel
88b38dfd83 test: category test in course form in UI 2024-09-24 12:22:34 +05:30
Jannat Patel
75e9ca395f chore: removed unnecessary custom fields 2024-09-24 10:59:27 +05:30
Jannat Patel
6fb206cc4e feat: updating category from settings 2024-09-24 10:33:23 +05:30
Jannat Patel
62cb198492 Merge branch 'develop' of https://github.com/frappe/lms into categories-in-courses 2024-09-23 19:23:49 +05:30
Jannat Patel
9609329f01 Merge pull request #1028 from pateljannat/fix-signup-customisations
fix: signup conditions
2024-09-23 19:12:29 +05:30
Jannat Patel
c93808af94 fix: signup conditions 2024-09-23 18:41:35 +05:30
Jannat Patel
58866260ec Merge branch 'develop' of https://github.com/frappe/lms into categories-in-courses 2024-09-23 18:39:19 +05:30
Jannat Patel
e6157ff411 Merge pull request #1027 from pateljannat/refactor-signup-customisations
refactor: signup customisations
2024-09-23 18:23:04 +05:30
Jannat Patel
8cca8920ee chore: removed unnecessary file 2024-09-23 18:07:08 +05:30
Jannat Patel
ab039dbd46 refactor: signup customisations 2024-09-23 18:04:36 +05:30
Jannat Patel
9853ab3fd9 Merge pull request #1024 from frappe/pot_develop_2024-09-20
chore: update POT file
2024-09-23 16:11:57 +05:30
Jannat Patel
dc2bf9f13e feat: category settings 2024-09-23 16:11:17 +05:30
Jannat Patel
7c90ca4040 feat: category in settings 2024-09-20 22:15:59 +05:30
frappe-pr-bot
75a90e1f39 chore: update POT file 2024-09-20 16:04:14 +00:00
Jannat Patel
bc4b17cc3d Merge pull request #1022 from pateljannat/evaluator_name_issue
fix: evaluator name issue
2024-09-19 15:18:05 +05:30
Jannat Patel
8c454a333e fix: evaluator name issue 2024-09-19 14:19:55 +05:30
Jannat Patel
3cda563583 fix: padding of settings modal 2024-09-18 14:46:29 +05:30
Jannat Patel
545326a02a Merge pull request #1021 from pateljannat/fix-help-video-url
fix: url for lesson help videos
2024-09-18 14:42:33 +05:30
Jannat Patel
14ce5d7e23 fix: url for lesson help videos 2024-09-18 11:49:38 +05:30
26 changed files with 5247 additions and 2202 deletions

View File

@@ -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();

View File

@@ -19,6 +19,7 @@
"@editorjs/paragraph": "^2.11.3",
"@editorjs/simple-image": "^1.6.0",
"chart.js": "^4.4.1",
"codemirror-editor-vue3": "^2.8.0",
"dayjs": "^1.11.6",
"feather-icons": "^4.28.0",
"frappe-ui": "^0.1.69",

View 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>

View File

@@ -27,8 +27,8 @@ const props = defineProps({
})
const file = computed(() => {
if (props.type == 'youtube') return '/Youtube.mp4'
if (props.type == 'quiz') return '/Quiz.mp4'
if (props.type == 'upload') return '/Upload.mp4'
if (props.type == 'youtube') return '/assets/lms/frontend/Youtube.mp4'
if (props.type == 'quiz') return '/assets/lms/frontend/Quiz.mp4'
if (props.type == 'upload') return '/assets/lms/frontend/Upload.mp4'
})
</script>

View File

@@ -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,7 +31,8 @@
</div>
<div
v-if="activeTab && data.doc"
class="flex flex-1 flex-col px-10 pt-8"
:key="activeTab.label"
class="flex flex-1 flex-col px-10 py-8"
>
<Members
v-if="activeTab.label === '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,56 +220,19 @@ const tabs = computed(() => {
},
],
},
],
},
{
label: 'Settings',
hideLabel: true,
items: [
{
label: 'Signup',
icon: 'LogIn',
description:
'Customize the signup page to inform users about your terms and policies',
fields: [
{
label: 'Show terms of use on signup',
name: 'terms_of_use',
type: 'checkbox',
label: 'Custom Content',
name: 'custom_signup_content',
type: 'Code',
mode: 'htmlmixed',
rows: 10,
},
{
label: 'Terms of Use Page',
name: 'terms_page',
type: 'Link',
doctype: 'Web Page',
},
{
label: 'Show privacy policy on signup',
name: 'privacy_policy',
type: 'checkbox',
},
{
label: 'Privacy Policy Page',
name: 'privacy_policy_page',
type: 'Link',
doctype: 'Web Page',
},
{
type: 'Column Break',
},
{
label: 'Show cookie policy on signup',
name: 'cookie_policy',
type: 'checkbox',
},
{
label: 'Cookie Policy Page',
name: 'cookie_policy_page',
type: 'Link',
doctype: 'Web Page',
},
{
label: 'Ask user category during signup',
label: 'Ask user category',
name: 'user_category',
type: 'checkbox',
},
@@ -257,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>

View File

@@ -8,9 +8,15 @@
{{ __(description) }}
</div>
</div>
<div class="flex justify-between my-5">
<div
class="my-5"
:class="{ 'flex justify-between w-full': columns.length > 1 }"
>
<div v-for="(column, index) in columns" :key="index">
<div class="flex flex-col space-y-5 w-72">
<div
class="flex flex-col space-y-5"
:class="columns.length > 1 ? 'w-72' : 'w-full'"
>
<div v-for="field in column">
<Link
v-if="field.type == 'Link'"
@@ -18,12 +24,25 @@
:doctype="field.doctype"
:label="field.label"
/>
<Codemirror
v-else-if="field.type == 'Code'"
v-model:value="field.value"
:label="field.label"
:height="200"
:options="{
mode: field.mode,
theme: 'seti',
}"
/>
<FormControl
v-else
:key="field.name"
v-model="field.value"
:label="field.label"
:type="field.type"
:rows="field.rows"
/>
</div>
</div>
@@ -41,6 +60,9 @@
import { FormControl, Button } from 'frappe-ui'
import { computed } from 'vue'
import Link from '@/components/Controls/Link.vue'
import Codemirror from 'codemirror-editor-vue3'
import 'codemirror/theme/seti.css'
import 'codemirror/mode/htmlmixed/htmlmixed.js'
const props = defineProps({
fields: {
@@ -94,3 +116,13 @@ const update = () => {
props.data.save.submit()
}
</script>
<style>
.CodeMirror pre.CodeMirror-line,
.CodeMirror pre.CodeMirror-line-like {
font-family: revert;
}
.CodeMirror {
border-radius: 12px;
}
</style>

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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',

View 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,
}
})

2183
frontend/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1 @@
__version__ = "2.5.0"
__version__ = "2.6.0"

File diff suppressed because it is too large Load Diff

View 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"
}
]

View File

@@ -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
# -------

View File

@@ -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
}

View File

@@ -13,6 +13,7 @@ from frappe.utils import (
get_datetime,
nowtime,
get_time,
get_fullname,
)
from lms.lms.utils import get_evaluator
import json
@@ -32,6 +33,7 @@ class LMSCertificateRequest(Document):
def set_evaluator(self):
if not self.evaluator:
self.evaluator = get_evaluator(self.course, self.batch_name)
self.evaluator_name = get_fullname(self.evaluator)
def validate_unavailability(self):
if self.evaluator:

View File

@@ -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",

View File

@@ -23,15 +23,9 @@
"show_emails",
"signup_settings_tab",
"signup_settings_section",
"terms_of_use",
"terms_page",
"user_category",
"column_break_9",
"privacy_policy",
"privacy_policy_page",
"column_break_12",
"cookie_policy",
"cookie_policy_page",
"custom_signup_content",
"user_category",
"sidebar_tab",
"items_in_sidebar_section",
"courses",
@@ -92,60 +86,14 @@
"fieldtype": "Column Break",
"label": "Show Tab in Batch"
},
{
"default": "0",
"fieldname": "terms_of_use",
"fieldtype": "Check",
"label": "Show Terms of Use on Signup"
},
{
"depends_on": "terms_of_use",
"fieldname": "terms_page",
"fieldtype": "Link",
"label": "Terms of Use Page",
"mandatory_depends_on": "terms_of_use",
"options": "Web Page"
},
{
"fieldname": "signup_settings_section",
"fieldtype": "Section Break"
},
{
"default": "0",
"fieldname": "privacy_policy",
"fieldtype": "Check",
"label": "Show Privacy Policy on Signup"
},
{
"depends_on": "privacy_policy",
"fieldname": "privacy_policy_page",
"fieldtype": "Link",
"label": "Privacy Policy Page",
"mandatory_depends_on": "privacy_policy",
"options": "Web Page"
},
{
"fieldname": "column_break_12",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_9",
"fieldtype": "Column Break"
},
{
"default": "0",
"fieldname": "cookie_policy",
"fieldtype": "Check",
"label": "Show Cookie Policy on Signup"
},
{
"depends_on": "cookie_policy",
"fieldname": "cookie_policy_page",
"fieldtype": "Link",
"label": "Cookie Policy Page",
"mandatory_depends_on": "cookie_policy",
"options": "Web Page"
},
{
"default": "0",
"fieldname": "user_category",
@@ -378,12 +326,17 @@
"fieldtype": "Table",
"label": "Sidebar Items",
"options": "LMS Sidebar Item"
},
{
"fieldname": "custom_signup_content",
"fieldtype": "HTML Editor",
"label": "Custom Signup Content"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-08-13 19:02:58.714080",
"modified": "2024-09-23 17:57:01.350020",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Settings",

View File

@@ -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",

View File

@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Frappe LMS VERSION\n"
"Report-Msgid-Bugs-To: jannat@frappe.io\n"
"POT-Creation-Date: 2024-09-13 16:04+0000\n"
"PO-Revision-Date: 2024-09-13 16:04+0000\n"
"POT-Creation-Date: 2024-09-20 16:04+0000\n"
"PO-Revision-Date: 2024-09-20 16:04+0000\n"
"Last-Translator: jannat@frappe.io\n"
"Language-Team: jannat@frappe.io\n"
"MIME-Version: 1.0\n"
@@ -212,7 +212,7 @@ msgstr ""
msgid "Assessment Type"
msgstr ""
#: lms/doctype/lms_batch/lms_batch.py:71
#: lms/doctype/lms_batch/lms_batch.py:66
msgid "Assessment {0} has already been added to this batch."
msgstr ""
@@ -387,7 +387,7 @@ msgstr ""
msgid "Batch Updated"
msgstr ""
#: lms/doctype/lms_batch/lms_batch.py:42
#: lms/doctype/lms_batch/lms_batch.py:37
msgid "Batch end date cannot be before the batch start date"
msgstr ""
@@ -948,11 +948,11 @@ msgstr ""
msgid "Course Title"
msgstr ""
#: lms/doctype/lms_batch/lms_batch.py:327
#: lms/doctype/lms_batch/lms_batch.py:324
msgid "Course already added to the batch."
msgstr ""
#: lms/doctype/lms_batch/lms_batch.py:60
#: lms/doctype/lms_batch/lms_batch.py:55
msgid "Course {0} has already been added to this batch."
msgstr ""
@@ -1248,7 +1248,7 @@ msgstr ""
msgid "Enrolled successfully"
msgstr ""
#: lms/doctype/lms_batch/lms_batch.py:92
#: lms/doctype/lms_batch/lms_batch.py:89
msgid "Enrollment Confirmation for the Next Training Batch"
msgstr ""
@@ -1297,7 +1297,7 @@ msgstr ""
msgid "Evaluation Request"
msgstr ""
#: lms/doctype/lms_batch/lms_batch.py:89
#: lms/doctype/lms_batch/lms_batch.py:73
msgid "Evaluation end date cannot be less than the batch end date."
msgstr ""
@@ -2788,7 +2788,7 @@ msgstr ""
msgid "Please click on the following button to set your new password"
msgstr ""
#: lms/doctype/lms_batch/lms_batch.py:238
#: lms/doctype/lms_batch/lms_batch.py:235
msgid "Please enable Zoom Settings to use this feature."
msgstr ""
@@ -3165,19 +3165,19 @@ msgstr ""
msgid "Route"
msgstr ""
#: lms/doctype/lms_batch/lms_batch.py:167
#: lms/doctype/lms_batch/lms_batch.py:164
msgid "Row #{0} Date cannot be outside the batch duration."
msgstr ""
#: lms/doctype/lms_batch/lms_batch.py:162
#: lms/doctype/lms_batch/lms_batch.py:159
msgid "Row #{0} End time cannot be outside the batch duration."
msgstr ""
#: lms/doctype/lms_batch/lms_batch.py:144
#: lms/doctype/lms_batch/lms_batch.py:141
msgid "Row #{0} Start time cannot be greater than or equal to end time."
msgstr ""
#: lms/doctype/lms_batch/lms_batch.py:153
#: lms/doctype/lms_batch/lms_batch.py:150
msgid "Row #{0} Start time cannot be outside the batch duration."
msgstr ""
@@ -3524,7 +3524,7 @@ msgstr ""
msgid "Student Name"
msgstr ""
#: lms/doctype/lms_batch/lms_batch.py:49
#: lms/doctype/lms_batch/lms_batch.py:44
msgid "Student {0} has already been added to this batch."
msgstr ""
@@ -3697,7 +3697,7 @@ msgstr ""
msgid "The course {0} is now available on {1}."
msgstr ""
#: lms/doctype/lms_certificate_request/lms_certificate_request.py:51
#: lms/doctype/lms_certificate_request/lms_certificate_request.py:53
msgid "The evaluator of this course is unavailable from {0} to {1}. Please select a date after {1}"
msgstr ""
@@ -3705,7 +3705,7 @@ msgstr ""
msgid "The quiz has a time limit. For each question you will be given {0} seconds."
msgstr ""
#: lms/doctype/lms_certificate_request/lms_certificate_request.py:69
#: lms/doctype/lms_certificate_request/lms_certificate_request.py:71
msgid "The slot is already booked by another participant."
msgstr ""
@@ -3713,7 +3713,7 @@ msgstr ""
msgid "The status of your application has changed."
msgstr ""
#: lms/doctype/lms_batch/lms_batch.py:135
#: lms/doctype/lms_batch/lms_batch.py:132
msgid "There are no seats available in this batch."
msgstr ""
@@ -4106,7 +4106,7 @@ msgstr ""
msgid "Write a review"
msgstr ""
#: lms/doctype/lms_certificate_request/lms_certificate_request.py:93
#: lms/doctype/lms_certificate_request/lms_certificate_request.py:95
msgid "You already have an evaluation on {0} at {1} for the course {2}."
msgstr ""
@@ -4139,11 +4139,11 @@ msgstr ""
msgid "You can find their resume attached to this email."
msgstr ""
#: lms/doctype/lms_certificate_request/lms_certificate_request.py:113
#: lms/doctype/lms_certificate_request/lms_certificate_request.py:115
msgid "You cannot schedule evaluations after {0}."
msgstr ""
#: lms/doctype/lms_certificate_request/lms_certificate_request.py:102
#: lms/doctype/lms_certificate_request/lms_certificate_request.py:104
msgid "You cannot schedule evaluations for past slots."
msgstr ""
@@ -4208,7 +4208,7 @@ msgstr ""
msgid "Your evaluation for the course {0} has been scheduled on {1} at {2} {3}."
msgstr ""
#: lms/doctype/lms_certificate_request/lms_certificate_request.py:123
#: lms/doctype/lms_certificate_request/lms_certificate_request.py:125
msgid "Your evaluation slot has been booked"
msgstr ""

View File

@@ -90,4 +90,4 @@ lms.patches.v1_0.set_published_on
lms.patches.v2_0.fix_progress_percentage
lms.patches.v2_0.add_discussion_topic_titles
lms.patches.v2_0.sidebar_settings
lms.patches.v2_0.delete_certificate_request_notification
lms.patches.v2_0.delete_certificate_request_notification #18-09-2024

View File

@@ -2,4 +2,10 @@ import frappe
def execute():
frappe.db.delete("Notification", "Certificate Request Creation")
delete_notification("Certificate Request Creation")
delete_notification("Certificate Request Reminder")
def delete_notification(notification_name):
if frappe.db.exists("Notification", notification_name):
frappe.db.delete("Notification", notification_name)

View File

@@ -227,8 +227,7 @@ def assignment_renderer(detail):
def show_custom_signup():
if frappe.db.get_single_value(
"LMS Settings", "terms_of_use"
) or frappe.db.get_single_value("LMS Settings", "privacy_policy"):
settings = frappe.get_single("LMS Settings")
if settings.custom_signup_content or settings.user_category:
return "lms/templates/signup-form.html"
return "frappe/templates/signup.html"

View File

@@ -1,3 +1,4 @@
{% set custom_signup_content = frappe.db.get_single_value("LMS Settings", "custom_signup_content") %}
<form class="signup-form" role="form">
<div class="page-card-body">
<div class="form-group">
@@ -31,6 +32,7 @@
</div>
{% endif %}
{% if custom_signup_content %}
<div class="form-group">
<div class="checkbox">
<label>
@@ -39,11 +41,12 @@
data-fieldtype="Check" data-fieldname="terms" id="signup-terms" required>
</span>
<span class="label-area">
{{ _("I have read and agree to your {0}").format(get_signup_optin_checks()) }}
{{ custom_signup_content }}
</span>
</label>
</div>
</div>
{% endif %}
</div>
<div class="page-card-actions">
<button class="btn btn-sm btn-primary btn-block btn-signup"