Merge pull request #2001 from frappe/feat/config-fe-base-path

feat: configurable frontend base path
This commit is contained in:
Jannat Patel
2026-01-30 12:40:43 +05:30
committed by GitHub
35 changed files with 177 additions and 78 deletions
+1
View File
@@ -12,4 +12,5 @@ node_modules
package-lock.json
lms/public/frontend
lms/www/lms.html
lms/www/_lms.html
frappe-ui
+1 -1
View File
@@ -7,7 +7,7 @@
"dev": "vite",
"serve": "vite preview",
"build": "vite build --base=/assets/lms/frontend/ && yarn copy-html-entry && yarn copy-colors-json",
"copy-html-entry": "cp ../lms/public/frontend/index.html ../lms/www/lms.html",
"copy-html-entry": "cp ../lms/public/frontend/index.html ../lms/www/_lms.html",
"copy-colors-json": "cp node_modules/frappe-ui/tailwind/colors.json src/utils/frappe-ui-colors.json"
},
"dependencies": {
+6 -2
View File
@@ -65,6 +65,7 @@ import { Dialog, FormControl } from 'frappe-ui'
import { nextTick, onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import { Link } from 'frappe-ui/frappe'
import { getLmsRoute } from '@/utils/basePath'
const show = ref(false)
const quiz = ref(null)
@@ -94,7 +95,10 @@ const addAssessment = () => {
}
const redirectToForm = () => {
if (props.type == 'quiz') window.open('/lms/quizzes?new=true', '_blank')
else window.open('/lms/assignments?new=true', '_blank')
if (props.type == 'quiz') {
window.open(getLmsRoute('quizzes?new=true'), '_blank')
} else {
window.open(getLmsRoute('assignments?new=true'), '_blank')
}
}
</script>
+4 -1
View File
@@ -248,6 +248,7 @@ import DateRange from '@/components/Common/DateRange.vue'
import BulkCertificates from '@/components/Modals/BulkCertificates.vue'
import BatchFeedback from '@/components/BatchFeedback.vue'
import dayjs from 'dayjs/esm'
import { getLmsRoute } from '@/utils/basePath'
const user = inject('$user')
const showAnnouncementModal = ref(false)
@@ -357,7 +358,9 @@ const isStudent = computed(() => {
})
const redirectToLogin = () => {
window.location.href = `/login?redirect-to=/lms/batches/${props.batchName}`
window.location.href = `/login?redirect-to=${getLmsRoute(
`batches/${props.batchName}`
)}`
}
const openAnnouncementModal = () => {
+10 -5
View File
@@ -207,14 +207,18 @@
:text="access.data.message"
:buttonLabel="type == 'course' ? 'Checkout Course' : 'Checkout Batch'"
:buttonLink="
type == 'course' ? `/lms/courses/${name}` : `/lms/batches/${name}`
type == 'course'
? getLmsRoute(`courses/${name}`)
: getLmsRoute(`batches/${name}`)
"
/>
</div>
<div v-else-if="!user.data?.name">
<NotPermitted
text="Please login to access this page."
:buttonLink="`/login?redirect-to=/lms/billing/${type}/${name}`"
:buttonLink="`/login?redirect-to=${getLmsRoute(
`billing/${type}/${name}`
)}`"
/>
</div>
</div>
@@ -235,6 +239,7 @@ import Link from '@/components/Controls/Link.vue'
import NotPermitted from '@/components/NotPermitted.vue'
import { X } from 'lucide-vue-next'
import { useTelemetry } from 'frappe-ui/frappe'
import { getLmsRoute } from '@/utils/basePath'
const user = inject('$user')
const { brand } = sessionStore()
@@ -441,11 +446,11 @@ const changeCurrency = (country) => {
const redirectTo = computed(() => {
if (props.type == 'course') {
return `/lms/courses/${props.name}`
return getLmsRoute(`courses/${props.name}`)
} else if (props.type == 'batch') {
return `/lms/batches/${props.name}`
return getLmsRoute(`batches/${props.name}`)
} else if (props.type == 'certificate') {
return `/lms/courses/${props.name}/certification`
return getLmsRoute(`courses/${props.name}/certification`)
}
})
+4 -1
View File
@@ -379,6 +379,7 @@ import CourseOutline from '@/components/CourseOutline.vue'
import UserAvatar from '@/components/UserAvatar.vue'
import Notes from '@/components/Notes/Notes.vue'
import InlineLessonMenu from '@/components/Notes/InlineLessonMenu.vue'
import { getLmsRoute } from '@/utils/basePath'
const user = inject('$user')
const socket = inject('$socket')
@@ -904,7 +905,9 @@ watch(allowDiscussions, () => {
})
const redirectToLogin = () => {
window.location.href = `/login?redirect-to=/lms/courses/${props.courseName}`
window.location.href = `/login?redirect-to=${getLmsRoute(
`courses/${props.courseName}`
)}`
}
usePageMeta(() => {
+4 -1
View File
@@ -122,6 +122,7 @@ import { X, LinkedinIcon, Twitter } from 'lucide-vue-next'
import { sessionStore } from '@/stores/session'
import { decodeEntities } from '@/utils'
import DOMPurify from 'dompurify'
import { getLmsRoute } from '@/utils/basePath'
const dayjs = inject('$dayjs')
const { branding } = sessionStore()
@@ -158,7 +159,9 @@ const badges = createResource({
const shareOnSocial = (badge, medium) => {
let shareUrl
const url = encodeURIComponent(
`${window.location.origin}/lms/badges/${badge.badge}/${props.profile.data?.email}`
`${window.location.origin}${getLmsRoute(
`badges/${badge.badge}/${props.profile.data?.email}`
)}`
)
const summary = `I am happy to announce that I earned the ${
badge.badge
@@ -158,6 +158,7 @@ import { sessionStore } from '@/stores/session'
import { useRouter } from 'vue-router'
import { openSettings } from '@/utils'
import { useSettings } from '@/stores/settings'
import { getLmsRoute } from '@/utils/basePath'
const user = inject<any>('$user')
const code = ref<string | null>('')
@@ -255,7 +256,10 @@ const updateBoilerPlate = () => {
const checkIfUserIsPermitted = (doc: any = null) => {
if (!user.data) {
window.location.href = `/login?redirect-to=/lms/programming-exercises/${props.exerciseID}/submission/${props.submissionID}`
const redirectPath = getLmsRoute(
`programming-exercises/${props.exerciseID}/submission/${props.submissionID}`
)
window.location.href = `/login?redirect-to=${redirectPath}`
}
if (!doc) return
+2 -1
View File
@@ -2,6 +2,7 @@ import { createRouter, createWebHistory } from 'vue-router'
import { usersStore } from './stores/user'
import { sessionStore } from './stores/session'
import { useSettings } from './stores/settings'
import { getLmsBasePath } from './utils/basePath'
const routes = [
{
@@ -262,7 +263,7 @@ const routes = [
]
let router = createRouter({
history: createWebHistory('/lms'),
history: createWebHistory(`/${getLmsBasePath()}`),
routes,
})
+5 -1
View File
@@ -5,6 +5,7 @@ import translationPlugin from '../translation'
import { usersStore } from '@/stores/user'
import { call } from 'frappe-ui'
import router from '@/router'
import { getLmsRoute } from '@/utils/basePath'
export class Assignment {
constructor({ data, api, readOnly }) {
@@ -53,7 +54,10 @@ export class Assignment {
fieldname: ['name'],
}).then((data) => {
let submission = data.name || 'new'
this.wrapper.innerHTML = `<iframe src="/lms/assignment-submission/${assignment}/${submission}?fromLesson=1" class="w-full h-[500px]"></iframe>`
const submissionPath = getLmsRoute(
`assignment-submission/${assignment}/${submission}?fromLesson=1`
)
this.wrapper.innerHTML = `<iframe src="${submissionPath}" class="w-full h-[500px]"></iframe>`
})
return
}
+12
View File
@@ -0,0 +1,12 @@
export function getLmsBasePath() {
return window.lms_path || 'lms'
}
export function getLmsRoute(path = '') {
const base = getLmsBasePath()
if (!path) {
return base
}
const normalized = path.startsWith('/') ? path.slice(1) : path
return `/${base}/${normalized}`
}
+6 -2
View File
@@ -4,6 +4,7 @@ import translationPlugin from '@/translation'
import ProgrammingExerciseModal from '@/pages/ProgrammingExercises/ProgrammingExerciseModal.vue';
import { call } from 'frappe-ui';
import { usersStore } from '@/stores/user'
import { getLmsRoute } from '@/utils/basePath'
export class Program {
@@ -73,7 +74,10 @@ export class Program {
fieldname: ['name'],
}).then((data: { name: string }) => {
let submission = data.name || 'new'
this.wrapper.innerHTML = `<iframe src="/lms/programming-exercises/${exercise}/submission/${submission}?fromLesson=1" class="w-full h-[900px] border rounded-md"></iframe>`
const submissionPath = getLmsRoute(
`programming-exercises/${exercise}/submission/${submission}?fromLesson=1`
)
this.wrapper.innerHTML = `<iframe src="${submissionPath}" class="w-full h-[900px] border rounded-md"></iframe>`
})
return
}
@@ -100,4 +104,4 @@ export class Program {
exercise: this.data.exercise,
}
}
}
}
+3 -1
View File
@@ -5,6 +5,7 @@ import { usersStore } from '../stores/user'
import translationPlugin from '../translation'
import { CircleHelp } from 'lucide-vue-next'
import router from '@/router'
import { getLmsRoute } from '@/utils/basePath'
export class Quiz {
constructor({ data, api, readOnly }) {
@@ -42,7 +43,8 @@ export class Quiz {
renderQuiz(quiz) {
if (this.readOnly) {
this.wrapper.innerHTML = `<iframe src="/lms/quiz/${quiz}?fromLesson=1" class="w-full h-[500px]"></iframe>`
const quizPath = getLmsRoute(`quiz/${quiz}?fromLesson=1`)
this.wrapper.innerHTML = `<iframe src="${quizPath}" class="w-full h-[500px]"></iframe>`
return
}
this.wrapper.innerHTML = `<div class='border rounded-md p-4 text-center bg-surface-menu-bar mb-4'>
+1 -1
View File
@@ -18,7 +18,7 @@ export default defineConfig(async ({ mode }) => {
lucideIcons: true,
jinjaBootData: true,
buildConfig: {
indexHtmlPath: '../lms/www/lms.html',
indexHtmlPath: '../lms/www/_lms.html',
},
}),
vue(),
+25 -10
View File
@@ -1,3 +1,5 @@
import frappe
from . import __version__ as app_version
app_name = "frappe_lms"
@@ -6,11 +8,17 @@ app_publisher = "Frappe"
app_description = "Frappe LMS App"
app_icon_url = "/assets/lms/images/lms-logo.png"
app_icon_title = "Learning"
app_icon_route = "/lms"
app_color = "grey"
app_email = "jannat@frappe.io"
app_license = "AGPL"
def get_lms_path():
return (frappe.conf.get("lms_path") or "lms").strip("/")
app_icon_route = f"/{get_lms_path()}"
# Includes in <head>
# ------------------
@@ -163,7 +171,8 @@ override_whitelisted_methods = {
# Add all simple route rules here
website_route_rules = [
{"from_route": "/lms/<path:app_path>", "to_route": "lms"},
{"from_route": f"/{get_lms_path()}/<path:app_path>", "to_route": "_lms"},
{"from_route": f"/{get_lms_path()}", "to_route": "_lms"},
{
"from_route": "/courses/<course_name>/<certificate_id>",
"to_route": "certificate",
@@ -172,24 +181,25 @@ website_route_rules = [
website_redirects = [
{"source": "/update-profile", "target": "/edit-profile"},
{"source": "/courses", "target": "/lms/courses"},
{"source": "/courses", "target": f"/{get_lms_path()}/courses"},
{
"source": r"^/courses/.*$",
"target": "/lms/courses",
"target": f"/{get_lms_path()}/courses",
},
{"source": "/batches", "target": "/lms/batches"},
{"source": "/batches", "target": f"/{get_lms_path()}/batches"},
{
"source": r"/batches/(.*)",
"target": "/lms/batches",
"target": f"/{get_lms_path()}/batches",
"match_with_query_string": True,
},
{"source": "/job-openings", "target": "/lms/job-openings"},
{"source": "/job-openings", "target": f"/{get_lms_path()}/job-openings"},
{
"source": r"/job-openings/(.*)",
"target": "/lms/job-openings",
"target": f"/{get_lms_path()}/job-openings",
"match_with_query_string": True,
},
{"source": "/statistics", "target": "/lms/statistics"},
{"source": "/statistics", "target": f"/{get_lms_path()}/statistics"},
{"source": "_lms", "target": f"/{get_lms_path()}"},
]
update_website_context = [
@@ -202,11 +212,16 @@ jinja = {
"lms.lms.utils.get_instructors",
"lms.lms.utils.get_lesson_index",
"lms.lms.utils.get_lesson_url",
"lms.lms.utils.get_lms_route",
"lms.lms.utils.is_instructor",
"lms.lms.utils.get_palette",
],
"filters": [],
}
extend_bootinfo = [
"lms.lms.utils.extend_bootinfo",
]
## Specify the additional tabs to be included in the user profile page.
## Each entry must be a subclass of lms.lms.plugins.ProfileTab
# profile_tabs = []
@@ -255,7 +270,7 @@ add_to_apps_screen = [
"name": "lms",
"logo": "/assets/lms/frontend/learning.svg",
"title": "Learning",
"route": "/lms",
"route": f"/{get_lms_path()}",
"has_permission": "lms.lms.api.check_app_permission",
}
]
+2 -1
View File
@@ -38,6 +38,7 @@ from lms.lms.utils import (
get_course_details,
get_instructors,
get_lesson_count,
get_lms_route,
has_course_instructor_role,
has_evaluator_role,
has_moderator_role,
@@ -1668,7 +1669,7 @@ def get_pwa_manifest():
"name": title,
"short_name": title,
"description": "Easy to use, 100% open source Learning Management System",
"start_url": "/lms",
"start_url": get_lms_route(),
"icons": [
{
"src": banner_image or "/assets/lms/frontend/manifest/manifest-icon-192.maskable.png",
@@ -7,6 +7,8 @@ from frappe.desk.doctype.notification_log.notification_log import make_notificat
from frappe.model.document import Document
from frappe.utils import validate_url
from lms.lms.utils import get_lms_route
class LMSAssignmentSubmission(Document):
def validate(self):
@@ -72,7 +74,7 @@ class LMSAssignmentSubmission(Document):
"document_name": self.name,
"from_user": self.evaluator,
"type": "Alert",
"link": f"/lms/assignment-submission/{self.assignment}/{self.name}",
"link": get_lms_route(f"assignment-submission/{self.assignment}/{self.name}"),
}
)
make_notification_logs(notification, [self.member])
+2 -1
View File
@@ -40,8 +40,9 @@ frappe.ui.form.on("LMS Batch", {
},
refresh: (frm) => {
const lmsPath = frappe.boot.lms_path || "lms";
frm.add_web_link(
`/lms/batches/details/${frm.doc.name}`,
`/${lmsPath}/batches/details/${frm.doc.name}`,
"See on website"
);
},
+3 -2
View File
@@ -18,6 +18,7 @@ from lms.lms.utils import (
get_instructors,
get_lesson_index,
get_lesson_url,
get_lms_route,
get_quiz_details,
update_payment_record,
)
@@ -164,7 +165,7 @@ def send_email_notification_for_published_batch(batch):
"medium": batch.medium,
"timezone": batch.timezone,
"instructors": instructors,
"batch_url": f"{frappe.utils.get_url()}/lms/batches/details/{batch.name}",
"batch_url": frappe.utils.get_url(get_lms_route(f"batches/details/{batch.name}")),
}
frappe.sendmail(
@@ -193,7 +194,7 @@ def send_system_notification_for_published_batch(batch):
"document_name": batch.name,
"from_user": instructors[0] if instructors else None,
"type": "Alert",
"link": f"/lms/batches/details/{batch.name}",
"link": get_lms_route(f"batches/details/{batch.name}"),
}
)
make_notification_logs(notification, students)
+5 -1
View File
@@ -20,7 +20,11 @@ frappe.ui.form.on("LMS Course", {
});
},
refresh: (frm) => {
frm.add_web_link(`/lms/courses/${frm.doc.name}`, "See on Website");
const lmsPath = frappe.boot.lms_path || "lms";
frm.add_web_link(
`/${lmsPath}/courses/${frm.doc.name}`,
"See on Website"
);
if (!frm.doc.currency)
frappe.db
+10 -4
View File
@@ -9,7 +9,13 @@ from frappe.desk.doctype.notification_log.notification_log import make_notificat
from frappe.model.document import Document
from frappe.utils import cint, today
from ...utils import generate_slug, get_instructors, update_payment_record, validate_image
from ...utils import (
generate_slug,
get_instructors,
get_lms_route,
update_payment_record,
validate_image,
)
class LMSCourse(Document):
@@ -107,7 +113,7 @@ class LMSCourse(Document):
subject = self.title + " is available!"
args = {
"title": self.title,
"course_link": f"/lms/courses/{self.name}",
"course_link": get_lms_route(f"courses/{self.name}"),
"app_name": frappe.db.get_single_value("System Settings", "app_name"),
"site_url": frappe.utils.get_url(),
}
@@ -172,7 +178,7 @@ def send_email_notification_for_published_courses(courses):
"title": course.title,
"short_introduction": course.short_introduction,
"instructors": instructors,
"course_url": f"{frappe.utils.get_url()}/lms/courses/{course.name}",
"course_url": frappe.utils.get_url(get_lms_route(f"courses/{course.name}")),
}
frappe.sendmail(
@@ -202,7 +208,7 @@ def send_system_notification_for_published_courses(courses):
"document_name": course.name,
"from_user": instructors[0] if instructors else None,
"type": "Alert",
"link": f"/lms/courses/{course.name}",
"link": get_lms_route(f"courses/{course.name}"),
}
)
make_notification_logs(notification, students)
+5 -1
View File
@@ -7,6 +7,8 @@ from frappe.email.doctype.email_template.email_template import get_email_templat
from frappe.model.document import Document
from frappe.utils import add_days, flt, nowdate
from lms.lms.utils import get_lms_route
class LMSPayment(Document):
pass
@@ -76,7 +78,9 @@ def send_mail(payment):
"title": frappe.db.get_value(
payment.payment_for_document_type, payment.payment_for_document, "title"
),
"link": f"/lms/billing/{ payment.payment_for_document_type.split(' ')[-1].lower() }/{ payment.payment_for_document }",
"link": get_lms_route(
f"billing/{payment.payment_for_document_type.split(' ')[-1].lower()}/{payment.payment_for_document}"
),
}
if custom_template:
+2 -1
View File
@@ -1,3 +1,4 @@
frappe.pages["lms-home"].on_page_load = function (wrapper) {
window.location.href = "/lms/courses";
const lmsPath = frappe.boot.lms_path || "lms";
window.location.href = `/${lmsPath}/courses`;
};
+2 -1
View File
@@ -14,6 +14,7 @@ from lms.lms.utils import (
get_lesson_index,
get_lesson_url,
get_lessons,
get_lms_route,
get_membership,
get_reviews,
has_course_instructor_role,
@@ -118,7 +119,7 @@ class TestLMSUtils(BaseTestUtils):
def test_get_lesson_url(self):
lessons = get_lessons(self.course.name)
for lesson in lessons:
expected_url = f"/lms/courses/{self.course.name}/learn/{lesson.number}"
expected_url = get_lms_route(f"courses/{self.course.name}/learn/{lesson.number}")
self.assertEqual(get_lesson_url(self.course.name, lesson.number), expected_url)
def test_is_instructor(self):
+2 -2
View File
@@ -4,7 +4,7 @@ from frappe.model.naming import append_number_if_name_exists
from frappe.utils import escape_html, random_string
from frappe.website.utils import cleanup_page_name, is_signup_disabled
from lms.lms.utils import get_country_code
from lms.lms.utils import get_country_code, get_lms_route
def validate_username_duplicates(doc, method):
@@ -88,4 +88,4 @@ def set_country_from_ip(login_manager=None, user=None):
def on_login(login_manager):
default_app = frappe.db.get_single_value("System Settings", "default_app")
if default_app == "lms":
frappe.local.response["home_page"] = "/lms"
frappe.local.response["home_page"] = get_lms_route()
+20 -4
View File
@@ -32,6 +32,22 @@ from lms.lms.md import find_macros
RE_SLUG_NOTALLOWED = re.compile("[^a-z0-9]+")
def get_lms_path():
path = frappe.conf.get("lms_path") or "lms"
return path.strip("/")
def get_lms_route(path=""):
base = f"/{get_lms_path()}"
if not path:
return base
return f"{base}/{path.lstrip('/')}"
def extend_bootinfo(bootinfo):
bootinfo["lms_path"] = get_lms_path()
def slugify(title, used_slugs=None):
"""Converts title to a slug.
@@ -270,7 +286,7 @@ def get_lesson_index(lesson_name):
def get_lesson_url(course, lesson_number):
if not lesson_number:
return
return f"/lms/courses/{course}/learn/{lesson_number}"
return get_lms_route(f"courses/{course}/learn/{lesson_number}")
def get_progress(course, lesson, member=None):
@@ -414,7 +430,7 @@ def get_batch_details_for_notification(topic):
users = []
batch_title = frappe.db.get_value("LMS Batch", topic.reference_docname, "title")
subject = _("New comment in batch {0}").format(batch_title)
link = f"/lms/batches/{topic.reference_docname}#discussions"
link = get_lms_route(f"batches/{topic.reference_docname}#discussions")
instructors = frappe.db.get_all(
"Course Instructor",
{"parenttype": "LMS Batch", "parent": topic.reference_docname},
@@ -468,7 +484,7 @@ def notify_mentions_on_portal(doc, topic):
subject = _("{0} mentioned you in a comment in {1}").format(
frappe.bold(from_user_name), frappe.bold(batch_title)
)
link = f"/lms/batches/{topic.reference_docname}#discussions"
link = get_lms_route(f"batches/{topic.reference_docname}#discussions")
for user in mentions:
notification = frappe._dict(
@@ -1313,7 +1329,7 @@ def get_assignment_details(assessment, member):
assessment.edit_url = f"/assignments/{assessment.assessment_name}"
submission_name = existing_submission if existing_submission else "new-submission"
assessment.url = f"/lms/assignment-submission/{assessment.assessment_name}/{submission_name}"
assessment.url = get_lms_route(f"assignment-submission/{assessment.assessment_name}/{submission_name}")
return assessment
@@ -11,7 +11,7 @@
{{ widgets.CourseCard(course=course, read_only=False) }}
{% endfor %}
</div>
<a class="d-flex justify-content-center align-items-center mt-12" href="/lms/courses">
<a class="d-flex justify-content-center align-items-center mt-12" href="{{ get_lms_route('courses') }}">
<span>{{ _("Explore More") }}</span>
</a>
</div>
@@ -12,7 +12,7 @@
{% endfor %}
</div>
<a class="d-flex justify-content-center align-items-center mt-12" href="/lms/courses">
<a class="d-flex justify-content-center align-items-center mt-12" href="{{ get_lms_route('courses') }}">
<span>{{ _("Explore More") }}</span>
</a>
</div>
</div>
+1 -1
View File
@@ -1,6 +1,6 @@
{% set color = get_palette(member.full_name) %}
<span class="avatar {{ avatar_class }}" title="{{ member.full_name }}">
<a class="button-links" href="/lms/users/{{ member.username }}">
<a class="button-links" href="{{ get_lms_route('users/' ~ member.username) }}">
{% if member.user_image %}
<img class="avatar-frame standard-image" style="object-fit: cover;" src="{{ member.user_image }}"
title="{{ member.full_name }}">
+2 -2
View File
@@ -93,7 +93,7 @@
</div>
{% endif %}
{% endfor %}
<a class="button-links" href="/lms/users/{{ instructors[0].username }}">
<a class="button-links" href="{{ get_lms_route('users/' ~ instructors[0].username) }}">
<span class="course-instructor">
{% if ins_len == 1 %}
{{ instructors[0].full_name }}
@@ -128,7 +128,7 @@
<a class="stretched-link" href="{{ get_lesson_url(course.name, lesson_index) }}{{ query_parameter }}"></a>
{% else %}
<a class="stretched-link" href="/lms/courses/{{ course.name }}"></a>
<a class="stretched-link" href="{{ get_lms_route('courses/' ~ course.name) }}"></a>
{% endif %}
{% endif %}
</div>
@@ -4,7 +4,6 @@
<br>
<p> {{ _(" Please evaluate and grade it.") }} </p>
<br>`
<a href="/lms/assignment-submission/{{ assignment_name }}/{{ submission_name }}">
<a href="{{ get_lms_route('assignment-submission/' ~ assignment_name ~ '/' ~ submission_name) }}">
{{ _("Open Assignment") }}
</a>
+1 -2
View File
@@ -23,7 +23,7 @@
<br>
<p>
{{ _("Visit the following link to view your ") }}
<a href="/lms/batches/{{ name }}">{{ _("Batch Details") }}</a>
<a href="{{ get_lms_route('batches/' ~ name) }}">{{ _("Batch Details") }}</a>
</p>
<p>
{{ _("If you have any questions or require assistance, feel free to contact us.") }}
@@ -32,4 +32,3 @@
<p>
{{ _("Best Regards") }}
</p>
@@ -20,7 +20,7 @@
</p>
<br>
<p>
<a href="/lms/batches/{{ name }}">👉 {{ _("Visit your batch") }}</a>
<a href="{{ get_lms_route('batches/' ~ name) }}">👉 {{ _("Visit your batch") }}</a>
</p>
<br>
<p>
@@ -17,7 +17,7 @@
</p>
<br>
<p>
<a href="/lms/batches/{{ batch_name }}">👉 {{ _("Visit your batch") }}</a>
<a href="{{ get_lms_route('batches/' ~ batch_name) }}">👉 {{ _("Visit your batch") }}</a>
</p>
<br>
<p>
@@ -26,4 +26,4 @@
<br>
<p>
{{ _("Best Regards") }}
</p>
</p>
+21 -18
View File
@@ -5,6 +5,8 @@ from bs4 import BeautifulSoup
from frappe import _
from frappe.utils.telemetry import capture
from lms.lms.utils import get_lms_path, get_lms_route
no_cache = 1
@@ -32,6 +34,7 @@ def get_boot():
"read_only_mode": frappe.flags.read_only,
"csrf_token": frappe.sessions.get_csrf_token(),
"site_name": frappe.local.site,
"lms_path": get_lms_path(),
}
)
@@ -85,7 +88,7 @@ def get_meta_from_document(app_path):
return {
"title": _("Course List"),
"keywords": "All Courses, Courses, Learn",
"link": "/courses",
"link": get_lms_route("courses"),
}
if re.match(r"^courses/.*$", app_path):
@@ -94,7 +97,7 @@ def get_meta_from_document(app_path):
"title": _("New Course"),
"image": frappe.db.get_single_value("Website Settings", "banner_image"),
"keywords": "New Course, Create Course",
"link": "/lms/courses/new/edit",
"link": get_lms_route("courses/new/edit"),
}
course_name = app_path.split("/")[1]
course = frappe.db.get_value(
@@ -113,14 +116,14 @@ def get_meta_from_document(app_path):
"image": course.image,
"description": course.description,
"keywords": course.tags,
"link": f"/courses/{course_name}",
"link": get_lms_route(f"courses/{course_name}"),
}
if app_path == "batches":
return {
"title": _("Batches"),
"keywords": "All Batches, Batches, Learn",
"link": "/batches",
"link": get_lms_route("batches"),
}
if re.match(r"^batches/details/.*$", app_path):
batch_name = app_path.split("/")[2]
@@ -140,7 +143,7 @@ def get_meta_from_document(app_path):
"image": batch.meta_image,
"description": batch.batch_details,
"keywords": f"{batch.category} {batch.medium}",
"link": f"/batches/details/{batch_name}",
"link": get_lms_route(f"batches/details/{batch_name}"),
}
if re.match(r"^batches/.*$", app_path):
@@ -149,7 +152,7 @@ def get_meta_from_document(app_path):
return {
"title": _("New Batch"),
"keywords": "New Batch, Create Batch",
"link": "/lms/batches/new/edit",
"link": get_lms_route("batches/new/edit"),
}
batch = frappe.db.get_value(
"LMS Batch",
@@ -167,14 +170,14 @@ def get_meta_from_document(app_path):
"image": batch.meta_image,
"description": batch.batch_details,
"keywords": f"{batch.category} {batch.medium}",
"link": f"/batches/{batch_name}",
"link": get_lms_route(f"batches/{batch_name}"),
}
if app_path == "job-openings":
return {
"title": _("Job Openings"),
"keywords": "Job Openings, Jobs, Vacancies",
"link": "/job-openings",
"link": get_lms_route("job-openings"),
}
if re.match(r"^job-openings/.*$", app_path):
@@ -195,14 +198,14 @@ def get_meta_from_document(app_path):
"image": job_opening.company_logo,
"description": job_opening.description,
"keywords": "Job Openings, Jobs, Vacancies",
"link": f"/job-openings/{job_opening_name}",
"link": get_lms_route(f"job-openings/{job_opening_name}"),
}
if app_path == "statistics":
return {
"title": _("Statistics"),
"keywords": "Enrollment Count, Completion, Signups",
"link": "/statistics",
"link": get_lms_route("statistics"),
}
if re.match(r"^user/.*$", app_path):
@@ -225,7 +228,7 @@ def get_meta_from_document(app_path):
"image": user.user_image,
"description": user.bio,
"keywords": f"{user.full_name}, {user.bio}",
"link": f"/user/{username}",
"link": get_lms_route(f"user/{username}"),
}
if re.match(r"^badges/.*/.*$", app_path):
@@ -242,14 +245,14 @@ def get_meta_from_document(app_path):
"image": badge.image,
"description": badge.description,
"keywords": f"{badge.title}, {badge.description}",
"link": f"/badges/{badgeName}/{email}",
"link": get_lms_route(f"badges/{badgeName}/{email}"),
}
if app_path == "quizzes":
return {
"title": _("Quizzes"),
"keywords": "Quizzes, interactive quizzes, online quizzes",
"link": "/quizzes",
"link": get_lms_route("quizzes"),
}
if re.match(r"^quizzes/[^/]+$", app_path):
@@ -264,14 +267,14 @@ def get_meta_from_document(app_path):
return {
"title": quiz.title,
"keywords": quiz.title,
"link": f"/quizzes/{quiz_name}",
"link": get_lms_route(f"quizzes/{quiz_name}"),
}
if app_path == "assignments":
return {
"title": _("Assignments"),
"keywords": "Assignments, interactive assignments, online assignments",
"link": "/assignments",
"link": get_lms_route("assignments"),
}
if re.match(r"^assignments/[^/]+$", app_path):
@@ -286,21 +289,21 @@ def get_meta_from_document(app_path):
return {
"title": assignment.title,
"keywords": assignment.title,
"link": f"/assignments/{assignment_name}",
"link": get_lms_route(f"assignments/{assignment_name}"),
}
if app_path == "programs":
return {
"title": _("Programs"),
"keywords": "All Programs, Programs, Learn",
"link": "/programs",
"link": get_lms_route("programs"),
}
if app_path == "certified-participants":
return {
"title": _("Certified Participants"),
"keywords": "All Certified Participants, Certified Participants, Learn, Certification",
"link": "/certified-participants",
"link": get_lms_route("certified-participants"),
}
return {}