Merge pull request #1440 from frappe/develop

chore: merge 'develop' into 'main'
This commit is contained in:
Jannat Patel
2025-04-16 18:55:21 +05:30
committed by GitHub
35 changed files with 10521 additions and 3810 deletions
-1
View File
@@ -68,7 +68,6 @@ declare module 'vue' {
NoPermission: typeof import('./src/components/NoPermission.vue')['default'] NoPermission: typeof import('./src/components/NoPermission.vue')['default']
NoSidebarLayout: typeof import('./src/components/NoSidebarLayout.vue')['default'] NoSidebarLayout: typeof import('./src/components/NoSidebarLayout.vue')['default']
NotPermitted: typeof import('./src/components/NotPermitted.vue')['default'] NotPermitted: typeof import('./src/components/NotPermitted.vue')['default']
OnboardingBanner: typeof import('./src/components/OnboardingBanner.vue')['default']
PageModal: typeof import('./src/components/Modals/PageModal.vue')['default'] PageModal: typeof import('./src/components/Modals/PageModal.vue')['default']
PaymentSettings: typeof import('./src/components/PaymentSettings.vue')['default'] PaymentSettings: typeof import('./src/components/PaymentSettings.vue')['default']
ProgressBar: typeof import('./src/components/ProgressBar.vue')['default'] ProgressBar: typeof import('./src/components/ProgressBar.vue')['default']
@@ -38,7 +38,7 @@
<div class="mb-4"> <div class="mb-4">
<Button @click="openFileSelector" :loading="uploading"> <Button @click="openFileSelector" :loading="uploading">
{{ {{
uploading ? `Uploading ${progress}%` : 'Upload an zip file' uploading ? `Uploading ${progress}%` : 'Upload an ZIP file'
}} }}
</Button> </Button>
</div> </div>
@@ -350,6 +350,20 @@ const tabsStructure = computed(() => {
}, },
], ],
}, },
{
label: 'SEO',
icon: 'Search',
fields: [
{
label: 'Meta Description',
name: 'meta_description',
type: 'textarea',
rows: 5,
description:
"This description will be shown on lists and pages that don't have meta description",
},
],
},
], ],
}, },
] ]
@@ -1,159 +0,0 @@
<template>
<div v-if="showOnboardingBanner && onboardingDetails.data">
<Tooltip :text="__('Skip Onboarding')" placement="left">
<X
class="w-4 h-4 stroke-1 absolute top-2 right-2 cursor-pointer mr-1"
@click="skipOnboarding.reload()"
/>
</Tooltip>
<div class="flex items-center justify-evenly bg-surface-gray-2 p-10">
<div
@click="redirectToCourseForm()"
class="flex items-center space-x-2"
:class="{
'cursor-pointer': !onboardingDetails.data.course_created?.length,
}"
>
<span
v-if="onboardingDetails.data.course_created?.length"
class="py-1 px-1 bg-surface-white rounded-full"
>
<Check class="h-4 w-4 stroke-2 text-ink-green-3" />
</span>
<span
v-else
class="font-semibold bg-surface-white px-2 py-1 rounded-full"
>
1
</span>
<span class="text-lg font-semibold">
{{ __('Create a course') }}
</span>
</div>
<div
@click="redirectToChapterForm()"
class="flex items-center space-x-2"
:class="{
'cursor-pointer':
onboardingDetails.data.course_created?.length &&
!onboardingDetails.data.chapter_created?.length,
'text-ink-gray-3': !onboardingDetails.data.course_created?.length,
}"
>
<span
v-if="onboardingDetails.data.chapter_created?.length"
class="py-1 px-1 bg-surface-white rounded-full"
>
<Check class="h-4 w-4 stroke-2 text-ink-green-3" />
</span>
<span
v-else
class="font-semibold bg-surface-white px-2 py-1 rounded-full"
>
2
</span>
<span class="text-lg font-semibold">
{{ __('Add a chapter') }}
</span>
</div>
<div
@click="redirectToLessonForm()"
class="flex items-center space-x-2"
:class="{
'cursor-pointer':
onboardingDetails.data.course_created?.length &&
onboardingDetails.data.chapter_created?.length,
'text-ink-gray-3':
!onboardingDetails.data.course_created?.length ||
!onboardingDetails.data.chapter_created?.length,
}"
>
<span
v-if="onboardingDetails.data.lesson_created?.length"
class="py-1 px-1 bg-surface-white rounded-full"
>
<Check class="h-4 w-4 stroke-2 text-ink-green-3" />
</span>
<span class="font-semibold bg-surface-white px-2 py-1 rounded-full">
3
</span>
<span class="text-lg font-semibold">
{{ __('Add a lesson') }}
</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { Check, X } from 'lucide-vue-next'
import { useRouter } from 'vue-router'
import { useSettings } from '@/stores/settings'
import { createResource, Tooltip } from 'frappe-ui'
const showOnboardingBanner = ref(false)
const settings = useSettings()
const onboardingDetails = settings.onboardingDetails
const router = useRouter()
watch(onboardingDetails, () => {
if (!onboardingDetails.data?.is_onboarded) {
showOnboardingBanner.value = true
} else {
showOnboardingBanner.value = false
}
})
const redirectToCourseForm = () => {
if (onboardingDetails.data?.course_created.length) {
return
} else {
router.push({ name: 'CourseForm', params: { courseName: 'new' } })
}
}
const redirectToChapterForm = () => {
if (!onboardingDetails.data?.course_created.length) {
return
} else {
router.push({
name: 'CourseForm',
params: {
courseName: onboardingDetails.data?.first_course,
},
})
}
}
const redirectToLessonForm = () => {
if (!onboardingDetails.data?.course_created.length) {
return
} else if (!onboardingDetails.data?.chapter_created.length) {
return
} else {
router.push({
name: 'LessonForm',
params: {
courseName: onboardingDetails.data?.first_course,
chapterNumber: 1,
lessonNumber: 1,
},
})
}
}
const skipOnboarding = createResource({
url: 'frappe.client.set_value',
makeParams() {
return {
doctype: 'LMS Settings',
name: 'LMS Settings',
fieldname: 'is_onboarding_complete',
value: 1,
}
},
onSuccess(data) {
onboardingDetails.reload()
},
})
</script>
+109 -95
View File
@@ -8,98 +8,109 @@
{{ __('Save') }} {{ __('Save') }}
</Button> </Button>
</header> </header>
<div class="w-1/2 mx-auto py-5"> <div class="w-3/4 mx-auto py-5">
<div class=""> <div class="">
<div class="text-lg font-semibold mb-4"> <div class="text-lg font-semibold mb-4">
{{ __('Details') }} {{ __('Details') }}
</div> </div>
<div class="space-y-4 mb-4"> <div class="space-y-10 mb-4">
<FormControl <div class="grid grid-cols-2 gap-10">
v-model="batch.title"
:label="__('Title')"
:required="true"
class="w-full"
/>
<div class="flex items-center space-x-5">
<FormControl <FormControl
v-model="batch.published" v-model="batch.title"
type="checkbox" :label="__('Title')"
:label="__('Published')" :required="true"
class="w-full"
/> />
<FormControl <MultiSelect
v-model="batch.allow_self_enrollment" v-model="instructors"
type="checkbox" doctype="User"
:label="__('Allow self enrollment')" :label="__('Instructors')"
/> :required="true"
<FormControl :filters="{ ignore_user_type: 1 }"
v-model="batch.certification"
type="checkbox"
:label="__('Certification')"
/> />
</div> </div>
</div>
</div> <div class="grid grid-cols-2 gap-10">
<div class="mb-4"> <div class="flex flex-col space-y-5">
<div class="text-xs text-ink-gray-5 mb-2"> <FormControl
{{ __('Meta Image') }} v-model="batch.published"
</div> type="checkbox"
<FileUploader :label="__('Published')"
v-if="!batch.image" />
:fileTypes="['image/*']" <FormControl
:validateFile="validateFile" v-model="batch.allow_self_enrollment"
@success="(file) => saveImage(file)" type="checkbox"
> :label="__('Allow self enrollment')"
<template v-slot="{ file, progress, uploading, openFileSelector }"> />
<div class="flex items-center"> <FormControl
<div class="border rounded-md w-fit py-5 px-20"> v-model="batch.certification"
<Image class="size-5 stroke-1 text-ink-gray-7" /> type="checkbox"
:label="__('Certification')"
/>
</div>
<div>
<div class="text-xs text-ink-gray-5 mb-2">
{{ __('Meta Image') }}
</div> </div>
<div class="ml-4"> <FileUploader
<Button @click="openFileSelector"> v-if="!batch.image"
{{ __('Upload') }} :fileTypes="['image/*']"
</Button> :validateFile="validateFile"
<div class="mt-2 text-ink-gray-5 text-sm"> @success="(file) => saveImage(file)"
{{ >
__( <template
'Appears when the batch URL is shared on any online platform' v-slot="{ file, progress, uploading, openFileSelector }"
) >
}} <div class="flex items-center">
<div class="border rounded-md w-fit py-5 px-20">
<Image class="size-5 stroke-1 text-ink-gray-7" />
</div>
<div class="ml-4">
<Button @click="openFileSelector">
{{ __('Upload') }}
</Button>
<div class="mt-2 text-ink-gray-5 text-sm">
{{
__(
'Appears when the batch URL is shared on any online platform'
)
}}
</div>
</div>
</div>
</template>
</FileUploader>
<div v-else class="mb-4">
<div class="flex items-center">
<img
:src="batch.image.file_url"
class="border rounded-md w-40"
/>
<div class="ml-4">
<Button @click="removeImage()">
{{ __('Remove') }}
</Button>
<div class="mt-2 text-ink-gray-5 text-sm">
{{
__(
'Appears when the batch URL is shared on any online platform'
)
}}
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</template>
</FileUploader>
<div v-else class="mb-4">
<div class="flex items-center">
<img :src="batch.image.file_url" class="border rounded-md w-40" />
<div class="ml-4">
<Button @click="removeImage()">
{{ __('Remove') }}
</Button>
<div class="mt-2 text-ink-gray-5 text-sm">
{{
__(
'Appears when the batch URL is shared on any online platform'
)
}}
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
<MultiSelect
v-model="instructors"
doctype="User"
:label="__('Instructors')"
:required="true"
:filters="{ ignore_user_type: 1 }"
/>
<div class="my-10"> <div class="my-10">
<div class="text-lg font-semibold mb-4"> <div class="text-lg font-semibold mb-4">
{{ __('Date and Time') }} {{ __('Date and Time') }}
</div> </div>
<div class="grid grid-cols-2 gap-10"> <div class="grid grid-cols-3 gap-10">
<div> <div>
<FormControl <FormControl
v-model="batch.start_date" v-model="batch.start_date"
@@ -115,14 +126,6 @@
class="mb-4" class="mb-4"
:required="true" :required="true"
/> />
<FormControl
v-model="batch.timezone"
:label="__('Timezone')"
type="text"
:placeholder="__('Example: IST (+5:30)')"
class="mb-4"
:required="true"
/>
</div> </div>
<div> <div>
<FormControl <FormControl
@@ -140,6 +143,16 @@
:required="true" :required="true"
/> />
</div> </div>
<div>
<FormControl
v-model="batch.timezone"
:label="__('Timezone')"
type="text"
:placeholder="__('Example: IST (+5:30)')"
class="mb-4"
:required="true"
/>
</div>
</div> </div>
</div> </div>
@@ -147,7 +160,7 @@
<div class="text-lg font-semibold mb-4"> <div class="text-lg font-semibold mb-4">
{{ __('Settings') }} {{ __('Settings') }}
</div> </div>
<div class="grid grid-cols-2 gap-10"> <div class="grid grid-cols-3 gap-10">
<div> <div>
<FormControl <FormControl
v-model="batch.seat_count" v-model="batch.seat_count"
@@ -162,11 +175,6 @@
type="date" type="date"
class="mb-4" class="mb-4"
/> />
<Link
doctype="Email Template"
:label="__('Email Template')"
v-model="batch.confirmation_email_template"
/>
</div> </div>
<div> <div>
<FormControl <FormControl
@@ -191,6 +199,13 @@
v-model="batch.category" v-model="batch.category"
/> />
</div> </div>
<div>
<Link
doctype="Email Template"
:label="__('Email Template')"
v-model="batch.confirmation_email_template"
/>
</div>
</div> </div>
</div> </div>
@@ -198,17 +213,16 @@
<div class="text-lg font-semibold mb-4"> <div class="text-lg font-semibold mb-4">
{{ __('Payment') }} {{ __('Payment') }}
</div> </div>
<div> <FormControl
<FormControl v-model="batch.paid_batch"
v-model="batch.paid_batch" type="checkbox"
type="checkbox" :label="__('Paid Batch')"
:label="__('Paid Batch')" />
/> <div class="grid grid-cols-3 gap-10 mt-4">
<FormControl <FormControl
v-model="batch.amount" v-model="batch.amount"
:label="__('Amount')" :label="__('Amount')"
type="number" type="number"
class="my-4"
/> />
<Link <Link
doctype="Currency" doctype="Currency"
@@ -445,7 +459,7 @@ const createNewBatch = () => {
}) })
}, },
onError(err) { onError(err) {
showToast('Error', err.messages?.[0] || err, 'x') showToast('Message', err.messages?.[0] || err, 'alert-circle')
}, },
} }
) )
@@ -464,7 +478,7 @@ const editBatchDetails = () => {
}) })
}, },
onError(err) { onError(err) {
showToast('Error', err.messages?.[0] || err, 'x') showToast('Message', err.messages?.[0] || err, 'alert-circle')
}, },
} }
) )
+1 -5
View File
@@ -310,11 +310,7 @@ const course = reactive({
}) })
onMounted(() => { onMounted(() => {
if ( if (!user.data?.is_moderator && !user.data?.is_instructor) {
props.courseName == 'new' &&
!user.data?.is_moderator &&
!user.data?.is_instructor
) {
router.push({ name: 'Courses' }) router.push({ name: 'Courses' })
} }
+1 -2
View File
@@ -50,8 +50,7 @@ export const sessionStore = defineStore('lms-session', () => {
brand.name = data.app_name brand.name = data.app_name
brand.logo = data.app_logo brand.logo = data.app_logo
brand.favicon = brand.favicon =
data.favicon?.file_url || data.favicon?.file_url || '/assets/lms/frontend/learning.svg'
'/assets/lms/frontend/public/learning.svg'
}, },
}) })
+1 -1
View File
@@ -109,7 +109,7 @@ export function showToast(title, text, icon, iconClasses = null) {
icon: icon, icon: icon,
iconClasses: iconClasses, iconClasses: iconClasses,
position: icon == 'check' ? 'bottom-right' : 'top-center', position: icon == 'check' ? 'bottom-right' : 'top-center',
timeout: 5, timeout: icon != 'check' ? 10 : 5,
}) })
} }
+1 -1
View File
@@ -1 +1 @@
__version__ = "2.26.0" __version__ = "2.27.0"
+1 -1
View File
@@ -246,7 +246,7 @@ on_login = "lms.lms.user.on_login"
add_to_apps_screen = [ add_to_apps_screen = [
{ {
"name": "lms", "name": "lms",
"logo": "/assets/lms/images/lms-logo.png", "logo": "/assets/lms/frontend/learning.svg",
"title": "Learning", "title": "Learning",
"route": "/lms", "route": "/lms",
"has_permission": "lms.lms.api.check_app_permission", "has_permission": "lms.lms.api.check_app_permission",
+6 -1
View File
@@ -53,7 +53,12 @@ class LMSBatch(Document):
if self.paid_batch: if self.paid_batch:
installed_apps = frappe.get_installed_apps() installed_apps = frappe.get_installed_apps()
if "payments" not in installed_apps: if "payments" not in installed_apps:
frappe.throw(_("Please install the Payments app to create a paid batches.")) documentation_link = "https://docs.frappe.io/learning/setting-up-payment-gateway"
frappe.throw(
_(
"Please install the Payments App to create a paid batch. Refer to the documentation for more details. {0}"
).format(documentation_link)
)
def validate_amount_and_currency(self): def validate_amount_and_currency(self):
if self.paid_batch and (not self.amount or not self.currency): if self.paid_batch and (not self.amount or not self.currency):
+6 -1
View File
@@ -50,7 +50,12 @@ class LMSCourse(Document):
if self.paid_course: if self.paid_course:
installed_apps = frappe.get_installed_apps() installed_apps = frappe.get_installed_apps()
if "payments" not in installed_apps: if "payments" not in installed_apps:
frappe.throw(_("Please install the Payments app to create a paid courses.")) documentation_link = "https://docs.frappe.io/learning/setting-up-payment-gateway"
frappe.throw(
_(
"Please install the Payments App to create a paid course. Refer to the documentation for more details. {0}"
).format(documentation_link)
)
def validate_certification(self): def validate_certification(self):
if self.enable_certification and self.paid_certificate: if self.enable_certification and self.paid_certificate:
+5 -4
View File
@@ -20,10 +20,11 @@ frappe.ui.form.on("LMS Settings", {
frm.get_field("payments_app_is_not_installed").html(` frm.get_field("payments_app_is_not_installed").html(`
<div class="alert alert-warning"> <div class="alert alert-warning">
Please install the Please install the
<a target="_blank" style="color: var(--alert-text-warning); background: var(--alert-bg-warning);" href="https://frappecloud.com/marketplace/apps/payments"> <a target="_blank" style="text-decoration: underline; color: var(--alert-text-warning); background: var(--alert-bg-warning);" href="https://frappecloud.com/marketplace/apps/payments">Payments app</a>
Payments app to enable payment gateway. Refer to the
</a> <a target="_blank" style="text-decoration: underline; color: var(--alert-text-warning); background: var(--alert-bg-warning);" href="https://docs.frappe.io/learning/setting-up-payment-gateway">Documentation</a>
to enable payment gateway. for more information.
</div>
`); `);
}, },
}); });
+16 -11
View File
@@ -8,7 +8,6 @@
"general_tab", "general_tab",
"default_home", "default_home",
"send_calendar_invite_for_evaluations", "send_calendar_invite_for_evaluations",
"is_onboarding_complete",
"column_break_zdel", "column_break_zdel",
"allow_guest_access", "allow_guest_access",
"enable_learning_paths", "enable_learning_paths",
@@ -60,7 +59,9 @@
"batch_confirmation_template", "batch_confirmation_template",
"column_break_uwsp", "column_break_uwsp",
"assignment_submission_template", "assignment_submission_template",
"payment_reminder_template" "payment_reminder_template",
"seo_tab",
"meta_description"
], ],
"fields": [ "fields": [
{ {
@@ -107,13 +108,6 @@
"fieldtype": "Check", "fieldtype": "Check",
"label": "Identify User Persona" "label": "Identify User Persona"
}, },
{
"default": "0",
"fieldname": "is_onboarding_complete",
"fieldtype": "Check",
"label": "Is Onboarding Complete",
"read_only": 1
},
{ {
"default": "0", "default": "0",
"fieldname": "default_home", "fieldname": "default_home",
@@ -372,14 +366,25 @@
"fieldname": "disable_signup", "fieldname": "disable_signup",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Disable Signup" "label": "Disable Signup"
},
{
"fieldname": "seo_tab",
"fieldtype": "Tab Break",
"label": "SEO"
},
{
"description": "This description will be shown on lists and pages without meta description",
"fieldname": "meta_description",
"fieldtype": "Small Text",
"label": "Meta Description"
} }
], ],
"grid_page_length": 50, "grid_page_length": 50,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2025-04-07 18:05:52.000651", "modified": "2025-04-10 16:17:00.658698",
"modified_by": "Administrator", "modified_by": "sayali@frappe.io",
"module": "LMS", "module": "LMS",
"name": "LMS Settings", "name": "LMS Settings",
"owner": "Administrator", "owner": "Administrator",
+7 -2
View File
@@ -8,7 +8,6 @@ import requests
from frappe import _ from frappe import _
from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_result from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_result
from frappe.desk.doctype.notification_log.notification_log import make_notification_logs from frappe.desk.doctype.notification_log.notification_log import make_notification_logs
from frappe.desk.search import get_user_groups
from frappe.desk.notifications import extract_mentions from frappe.desk.notifications import extract_mentions
from frappe.utils import ( from frappe.utils import (
add_months, add_months,
@@ -20,7 +19,6 @@ from frappe.utils import (
format_date, format_date,
get_datetime, get_datetime,
getdate, getdate,
validate_phone_number,
get_fullname, get_fullname,
pretty_date, pretty_date,
get_time_str, get_time_str,
@@ -1390,6 +1388,13 @@ def get_batch_details(batch):
batch_details.instructors = get_instructors(batch) batch_details.instructors = get_instructors(batch)
batch_details.accept_enrollments = batch_details.start_date > getdate() batch_details.accept_enrollments = batch_details.start_date > getdate()
if (
not batch_details.accept_enrollments
and batch_details.start_date == getdate()
and get_time_str(batch_details.start_time) > nowtime()
):
batch_details.accept_enrollments = True
batch_details.courses = frappe.get_all( batch_details.courses = frappe.get_all(
"Batch Course", filters={"parent": batch}, fields=["course", "title", "evaluator"] "Batch Course", filters={"parent": batch}, fields=["course", "title", "evaluator"]
) )
+208 -182
View File
File diff suppressed because it is too large Load Diff
+209 -183
View File
File diff suppressed because it is too large Load Diff
+210 -184
View File
File diff suppressed because it is too large Load Diff
+209 -183
View File
File diff suppressed because it is too large Load Diff
+208 -182
View File
File diff suppressed because it is too large Load Diff
+210 -184
View File
File diff suppressed because it is too large Load Diff
+212 -186
View File
File diff suppressed because it is too large Load Diff
+208 -182
View File
File diff suppressed because it is too large Load Diff
+212 -186
View File
File diff suppressed because it is too large Load Diff
+207 -181
View File
File diff suppressed because it is too large Load Diff
+263 -237
View File
File diff suppressed because it is too large Load Diff
+459 -334
View File
File diff suppressed because it is too large Load Diff
+207 -181
View File
File diff suppressed because it is too large Load Diff
+208 -182
View File
File diff suppressed because it is too large Load Diff
+6261
View File
File diff suppressed because it is too large Load Diff
+209 -183
View File
File diff suppressed because it is too large Load Diff
+207 -181
View File
File diff suppressed because it is too large Load Diff
+209 -183
View File
File diff suppressed because it is too large Load Diff
+209 -183
View File
File diff suppressed because it is too large Load Diff
+27 -28
View File
@@ -3,7 +3,6 @@ import re
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from frappe import _ from frappe import _
from frappe.utils.telemetry import capture from frappe.utils.telemetry import capture
from frappe.utils import cint
no_cache = 1 no_cache = 1
@@ -15,22 +14,24 @@ def get_context():
or "/assets/lms/frontend/favicon.png" or "/assets/lms/frontend/favicon.png"
) )
title = frappe.db.get_single_value("Website Settings", "app_name") or "Frappe Learning" title = frappe.db.get_single_value("Website Settings", "app_name") or "Frappe Learning"
description = frappe.db.get_single_value("LMS Settings", "meta_description")
csrf_token = frappe.sessions.get_csrf_token() csrf_token = frappe.sessions.get_csrf_token()
frappe.db.commit() frappe.db.commit()
context = frappe._dict() context = frappe._dict()
context.csrf_token = csrf_token context.csrf_token = csrf_token
context.meta = get_meta(app_path, title, favicon) context.meta = get_meta(app_path, title, favicon, description)
capture("active_site", "lms") capture("active_site", "lms")
context.title = title context.title = title
context.favicon = favicon context.favicon = favicon
return context return context
def get_meta(app_path, title, favicon): def get_meta(app_path, title, favicon, description):
meta = {} meta = frappe._dict()
if app_path: if app_path:
meta = get_meta_from_document(app_path, favicon) meta = get_meta_from_document(app_path)
route_meta = frappe.get_all("Website Meta Tag", {"parent": app_path}, ["key", "value"]) route_meta = frappe.get_all("Website Meta Tag", {"parent": app_path}, ["key", "value"])
@@ -47,22 +48,32 @@ def get_meta(app_path, title, favicon):
elif row.key == "link": elif row.key == "link":
meta["link"] = row.value meta["link"] = row.value
if not meta.get("title"):
meta["title"] = title
if not meta.get("description"):
meta["description"] = description
if not meta.get("image"):
meta["image"] = favicon
if not meta.get("keywords"):
meta["keywords"] = ""
if not meta: if not meta:
meta = { meta = {
"title": title, "title": title,
"image": favicon, "image": favicon,
"description": "Easy to use Learning Management System", "description": description,
} }
return meta return meta
def get_meta_from_document(app_path, favicon): def get_meta_from_document(app_path):
if app_path == "courses": if app_path == "courses":
return { return {
"title": _("Course List"), "title": _("Course List"),
"image": favicon,
"description": "This page lists all the courses published on our website",
"keywords": "All Courses, Courses, Learn", "keywords": "All Courses, Courses, Learn",
"link": "/courses", "link": "/courses",
} }
@@ -72,7 +83,6 @@ def get_meta_from_document(app_path, favicon):
return { return {
"title": _("New Course"), "title": _("New Course"),
"image": frappe.db.get_single_value("Website Settings", "banner_image"), "image": frappe.db.get_single_value("Website Settings", "banner_image"),
"description": "Create a new course",
"keywords": "New Course, Create Course", "keywords": "New Course, Create Course",
"link": "/lms/courses/new/edit", "link": "/lms/courses/new/edit",
} }
@@ -99,8 +109,6 @@ def get_meta_from_document(app_path, favicon):
if app_path == "batches": if app_path == "batches":
return { return {
"title": _("Batches"), "title": _("Batches"),
"image": favicon,
"description": "This page lists all the batches published on our website",
"keywords": "All Batches, Batches, Learn", "keywords": "All Batches, Batches, Learn",
"link": "/batches", "link": "/batches",
} }
@@ -130,8 +138,6 @@ def get_meta_from_document(app_path, favicon):
if "new/edit" in app_path: if "new/edit" in app_path:
return { return {
"title": _("New Batch"), "title": _("New Batch"),
"image": favicon,
"description": "Create a new batch",
"keywords": "New Batch, Create Batch", "keywords": "New Batch, Create Batch",
"link": "/lms/batches/new/edit", "link": "/lms/batches/new/edit",
} }
@@ -157,8 +163,6 @@ def get_meta_from_document(app_path, favicon):
if app_path == "job-openings": if app_path == "job-openings":
return { return {
"title": _("Job Openings"), "title": _("Job Openings"),
"image": favicon,
"description": "This page lists all the job openings published on our website",
"keywords": "Job Openings, Jobs, Vacancies", "keywords": "Job Openings, Jobs, Vacancies",
"link": "/job-openings", "link": "/job-openings",
} }
@@ -171,6 +175,11 @@ def get_meta_from_document(app_path, favicon):
["job_title", "company_logo", "description"], ["job_title", "company_logo", "description"],
as_dict=True, as_dict=True,
) )
if job_opening.description:
soup = BeautifulSoup(job_opening.description, "html.parser")
job_opening.description = soup.get_text()
return { return {
"title": job_opening.job_title, "title": job_opening.job_title,
"image": job_opening.company_logo, "image": job_opening.company_logo,
@@ -182,8 +191,6 @@ def get_meta_from_document(app_path, favicon):
if app_path == "statistics": if app_path == "statistics":
return { return {
"title": _("Statistics"), "title": _("Statistics"),
"image": favicon,
"description": "This page lists all the statistics of this platform",
"keywords": "Enrollment Count, Completion, Signups", "keywords": "Enrollment Count, Completion, Signups",
"link": "/statistics", "link": "/statistics",
} }
@@ -231,8 +238,6 @@ def get_meta_from_document(app_path, favicon):
if app_path == "quizzes": if app_path == "quizzes":
return { return {
"title": _("Quizzes"), "title": _("Quizzes"),
"image": favicon,
"description": _("Test your knowledge with interactive quizzes and more."),
"keywords": "Quizzes, interactive quizzes, online quizzes", "keywords": "Quizzes, interactive quizzes, online quizzes",
"link": "/quizzes", "link": "/quizzes",
} }
@@ -248,8 +253,6 @@ def get_meta_from_document(app_path, favicon):
if quiz: if quiz:
return { return {
"title": quiz.title, "title": quiz.title,
"image": favicon,
"description": "Test your knowledge with interactive quizzes.",
"keywords": quiz.title, "keywords": quiz.title,
"link": f"/quizzes/{quiz_name}", "link": f"/quizzes/{quiz_name}",
} }
@@ -257,8 +260,6 @@ def get_meta_from_document(app_path, favicon):
if app_path == "assignments": if app_path == "assignments":
return { return {
"title": _("Assignments"), "title": _("Assignments"),
"image": favicon,
"description": _("Test your knowledge with interactive assignments and more."),
"keywords": "Assignments, interactive assignments, online assignments", "keywords": "Assignments, interactive assignments, online assignments",
"link": "/assignments", "link": "/assignments",
} }
@@ -274,8 +275,6 @@ def get_meta_from_document(app_path, favicon):
if assignment: if assignment:
return { return {
"title": assignment.title, "title": assignment.title,
"image": favicon,
"description": "Test your knowledge with interactive assignments.",
"keywords": assignment.title, "keywords": assignment.title,
"link": f"/assignments/{assignment_name}", "link": f"/assignments/{assignment_name}",
} }
@@ -283,8 +282,8 @@ def get_meta_from_document(app_path, favicon):
if app_path == "programs": if app_path == "programs":
return { return {
"title": _("Programs"), "title": _("Programs"),
"image": favicon,
"description": "This page lists all the programs published on our website",
"keywords": "All Programs, Programs, Learn", "keywords": "All Programs, Programs, Learn",
"link": "/programs", "link": "/programs",
} }
return {}