diff --git a/.gitignore b/.gitignore index 95242ce7..e6c74964 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,5 @@ node_modules package-lock.json lms/public/frontend lms/www/lms.html +lms/www/_lms.html frappe-ui \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index bead43a6..9bd26413 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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": { diff --git a/frontend/src/components/AssessmentPlugin.vue b/frontend/src/components/AssessmentPlugin.vue index 75e747c5..53b51392 100644 --- a/frontend/src/components/AssessmentPlugin.vue +++ b/frontend/src/components/AssessmentPlugin.vue @@ -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') + } } diff --git a/frontend/src/pages/Batch.vue b/frontend/src/pages/Batch.vue index ad5c47bb..3bd57614 100644 --- a/frontend/src/pages/Batch.vue +++ b/frontend/src/pages/Batch.vue @@ -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 = () => { diff --git a/frontend/src/pages/Billing.vue b/frontend/src/pages/Billing.vue index 00a98a94..02f9ce11 100644 --- a/frontend/src/pages/Billing.vue +++ b/frontend/src/pages/Billing.vue @@ -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}`) " />
@@ -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`) } }) diff --git a/frontend/src/pages/Lesson.vue b/frontend/src/pages/Lesson.vue index d503b35c..1c658774 100644 --- a/frontend/src/pages/Lesson.vue +++ b/frontend/src/pages/Lesson.vue @@ -378,6 +378,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') @@ -902,7 +903,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(() => { diff --git a/frontend/src/pages/ProfileAbout.vue b/frontend/src/pages/ProfileAbout.vue index f6497645..d2fb068c 100644 --- a/frontend/src/pages/ProfileAbout.vue +++ b/frontend/src/pages/ProfileAbout.vue @@ -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 diff --git a/frontend/src/pages/ProgrammingExercises/ProgrammingExerciseSubmission.vue b/frontend/src/pages/ProgrammingExercises/ProgrammingExerciseSubmission.vue index d11adcea..71f9563c 100644 --- a/frontend/src/pages/ProgrammingExercises/ProgrammingExerciseSubmission.vue +++ b/frontend/src/pages/ProgrammingExercises/ProgrammingExerciseSubmission.vue @@ -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('$user') const code = ref('') @@ -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 diff --git a/frontend/src/router.js b/frontend/src/router.js index fe6b311d..8894f3b9 100644 --- a/frontend/src/router.js +++ b/frontend/src/router.js @@ -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 = [ { @@ -268,7 +269,7 @@ const routes = [ ] let router = createRouter({ - history: createWebHistory('/lms'), + history: createWebHistory(`/${getLmsBasePath()}`), routes, }) diff --git a/frontend/src/utils/assignment.js b/frontend/src/utils/assignment.js index 99f9b101..1f1e894d 100644 --- a/frontend/src/utils/assignment.js +++ b/frontend/src/utils/assignment.js @@ -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 = `` + const submissionPath = getLmsRoute( + `assignment-submission/${assignment}/${submission}?fromLesson=1` + ) + this.wrapper.innerHTML = `` }) return } diff --git a/frontend/src/utils/basePath.js b/frontend/src/utils/basePath.js new file mode 100644 index 00000000..78782a9b --- /dev/null +++ b/frontend/src/utils/basePath.js @@ -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}` +} diff --git a/frontend/src/utils/program.ts b/frontend/src/utils/program.ts index a2d9b548..22cc48eb 100644 --- a/frontend/src/utils/program.ts +++ b/frontend/src/utils/program.ts @@ -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 = `` + const submissionPath = getLmsRoute( + `programming-exercises/${exercise}/submission/${submission}?fromLesson=1` + ) + this.wrapper.innerHTML = `` }) return } @@ -100,4 +104,4 @@ export class Program { exercise: this.data.exercise, } } -} \ No newline at end of file +} diff --git a/frontend/src/utils/quiz.js b/frontend/src/utils/quiz.js index add3d680..b84f5286 100644 --- a/frontend/src/utils/quiz.js +++ b/frontend/src/utils/quiz.js @@ -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 = `` + const quizPath = getLmsRoute(`quiz/${quiz}?fromLesson=1`) + this.wrapper.innerHTML = `` return } this.wrapper.innerHTML = `
diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 3d08802b..704e5ea1 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -17,7 +17,7 @@ export default defineConfig(async ({ mode }) => { lucideIcons: true, jinjaBootData: true, buildConfig: { - indexHtmlPath: '../lms/www/lms.html', + indexHtmlPath: '../lms/www/_lms.html', }, }), vue(), diff --git a/lms/hooks.py b/lms/hooks.py index aeb2e4b2..8a0fd55e 100644 --- a/lms/hooks.py +++ b/lms/hooks.py @@ -1,3 +1,5 @@ +import frappe + from . import __version__ as app_version app_name = "frappe_lms" @@ -6,11 +8,13 @@ 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" +lms_path = frappe.conf.get("lms_path") or "lms" +app_icon_route = f"/{lms_path}" + # Includes in # ------------------ @@ -163,7 +167,8 @@ override_whitelisted_methods = { # Add all simple route rules here website_route_rules = [ - {"from_route": "/lms/", "to_route": "lms"}, + {"from_route": f"/{lms_path}/", "to_route": "_lms"}, + {"from_route": f"/{lms_path}", "to_route": "_lms"}, { "from_route": "/courses//", "to_route": "certificate", @@ -172,24 +177,25 @@ website_route_rules = [ website_redirects = [ {"source": "/update-profile", "target": "/edit-profile"}, - {"source": "/courses", "target": "/lms/courses"}, + {"source": "/courses", "target": f"/{lms_path}/courses"}, { "source": r"^/courses/.*$", - "target": "/lms/courses", + "target": f"/{lms_path}/courses", }, - {"source": "/batches", "target": "/lms/batches"}, + {"source": "/batches", "target": f"/{lms_path}/batches"}, { "source": r"/batches/(.*)", - "target": "/lms/batches", + "target": f"/{lms_path}/batches", "match_with_query_string": True, }, - {"source": "/job-openings", "target": "/lms/job-openings"}, + {"source": "/job-openings", "target": f"/{lms_path}/job-openings"}, { "source": r"/job-openings/(.*)", - "target": "/lms/job-openings", + "target": f"/{lms_path}/job-openings", "match_with_query_string": True, }, - {"source": "/statistics", "target": "/lms/statistics"}, + {"source": "/statistics", "target": f"/{lms_path}/statistics"}, + {"source": "_lms", "target": f"/{lms_path}"}, ] update_website_context = [ @@ -203,11 +209,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 = [] @@ -256,7 +267,7 @@ add_to_apps_screen = [ "name": "lms", "logo": "/assets/lms/frontend/learning.svg", "title": "Learning", - "route": "/lms", + "route": f"/{lms_path}", "has_permission": "lms.lms.api.check_app_permission", } ] diff --git a/lms/lms/api.py b/lms/lms/api.py index 984ee519..731c0dde 100644 --- a/lms/lms/api.py +++ b/lms/lms/api.py @@ -28,6 +28,7 @@ from frappe.utils import ( ) from frappe.utils.response import Response +from lms.hooks import lms_path from lms.lms.doctype.course_lesson.course_lesson import save_progress from lms.lms.utils import ( get_average_rating, @@ -1673,7 +1674,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": f"/{lms_path}", "icons": [ { "src": banner_image or "/assets/lms/frontend/manifest/manifest-icon-192.maskable.png", diff --git a/lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.py b/lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.py index 9392b2cf..79a6cd0f 100644 --- a/lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.py +++ b/lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.py @@ -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]) diff --git a/lms/lms/doctype/lms_batch/lms_batch.js b/lms/lms/doctype/lms_batch/lms_batch.js index f196c867..7aa70bbc 100644 --- a/lms/lms/doctype/lms_batch/lms_batch.js +++ b/lms/lms/doctype/lms_batch/lms_batch.js @@ -48,8 +48,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" ); }, diff --git a/lms/lms/doctype/lms_batch/lms_batch.py b/lms/lms/doctype/lms_batch/lms_batch.py index 19208402..23431626 100644 --- a/lms/lms/doctype/lms_batch/lms_batch.py +++ b/lms/lms/doctype/lms_batch/lms_batch.py @@ -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) diff --git a/lms/lms/doctype/lms_course/lms_course.js b/lms/lms/doctype/lms_course/lms_course.js index ab913d32..c4429a2d 100644 --- a/lms/lms/doctype/lms_course/lms_course.js +++ b/lms/lms/doctype/lms_course/lms_course.js @@ -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 diff --git a/lms/lms/doctype/lms_course/lms_course.py b/lms/lms/doctype/lms_course/lms_course.py index 431035a9..15597a80 100644 --- a/lms/lms/doctype/lms_course/lms_course.py +++ b/lms/lms/doctype/lms_course/lms_course.py @@ -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) diff --git a/lms/lms/doctype/lms_mentor_request/lms_mentor_request.py b/lms/lms/doctype/lms_mentor_request/lms_mentor_request.py index 40b5ea44..1fa05439 100644 --- a/lms/lms/doctype/lms_mentor_request/lms_mentor_request.py +++ b/lms/lms/doctype/lms_mentor_request/lms_mentor_request.py @@ -5,6 +5,8 @@ import frappe from frappe import _ from frappe.model.document import Document +from lms.lms.utils import get_lms_route + class LMSMentorRequest(Document): def on_update(self): @@ -37,7 +39,7 @@ class LMSMentorRequest(Document): email_template.response, { "member_name": frappe.db.get_value("User", frappe.session.user, "full_name"), - "course_url": "/lms/courses/" + course_details.slug, + "course_url": get_lms_route(f"courses/{course_details.slug}"), "course": course_details.title, }, ) diff --git a/lms/lms/doctype/lms_payment/lms_payment.py b/lms/lms/doctype/lms_payment/lms_payment.py index f55982e6..e38cccf4 100644 --- a/lms/lms/doctype/lms_payment/lms_payment.py +++ b/lms/lms/doctype/lms_payment/lms_payment.py @@ -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: diff --git a/lms/lms/page/lms_home/lms_home.js b/lms/lms/page/lms_home/lms_home.js index 35f91552..f5c5f47b 100644 --- a/lms/lms/page/lms_home/lms_home.js +++ b/lms/lms/page/lms_home/lms_home.js @@ -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`; }; diff --git a/lms/lms/test_utils.py b/lms/lms/test_utils.py index c33c6fac..cbfe8640 100644 --- a/lms/lms/test_utils.py +++ b/lms/lms/test_utils.py @@ -2,6 +2,7 @@ import frappe from frappe.tests import UnitTestCase from frappe.utils import add_days, nowdate +from lms.hooks import lms_path from lms.lms.api import get_certified_participants from lms.lms.doctype.lms_certificate.lms_certificate import get_default_certificate_template, is_certified @@ -235,7 +236,7 @@ class TestUtils(UnitTestCase): 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 = f"/{lms_path}/courses/{self.course.name}/learn/{lesson.number}" self.assertEqual(get_lesson_url(self.course.name, lesson.number), expected_url) def test_is_instructor(self): diff --git a/lms/lms/user.py b/lms/lms/user.py index 5e31e5d8..d89455ce 100644 --- a/lms/lms/user.py +++ b/lms/lms/user.py @@ -4,6 +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.hooks import lms_path from lms.lms.utils import get_country_code @@ -88,4 +89,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"] = f"/{lms_path}" diff --git a/lms/lms/utils.py b/lms/lms/utils.py index 9cd9a516..f12acfef 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -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. @@ -277,7 +293,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): @@ -421,7 +437,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}" + link = get_lms_route(f"batches/{topic.reference_docname}") instructors = frappe.db.get_all( "Course Instructor", {"parenttype": "LMS Batch", "parent": topic.reference_docname}, @@ -475,7 +491,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( @@ -1295,7 +1311,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 diff --git a/lms/lms/web_template/course_cards/course_cards.html b/lms/lms/web_template/course_cards/course_cards.html index 8eb3ab8a..d7255473 100644 --- a/lms/lms/web_template/course_cards/course_cards.html +++ b/lms/lms/web_template/course_cards/course_cards.html @@ -11,7 +11,7 @@ {{ widgets.CourseCard(course=course, read_only=False) }} {% endfor %}
- + {{ _("Explore More") }} diff --git a/lms/lms/web_template/recently_published_courses/recently_published_courses.html b/lms/lms/web_template/recently_published_courses/recently_published_courses.html index 448ef3d5..ce2fdcb6 100644 --- a/lms/lms/web_template/recently_published_courses/recently_published_courses.html +++ b/lms/lms/web_template/recently_published_courses/recently_published_courses.html @@ -12,7 +12,7 @@ {% endfor %} - + {{ _("Explore More") }} - \ No newline at end of file + diff --git a/lms/lms/widgets/Avatar.html b/lms/lms/widgets/Avatar.html index ab21f7ba..bfb71327 100644 --- a/lms/lms/widgets/Avatar.html +++ b/lms/lms/widgets/Avatar.html @@ -1,6 +1,6 @@ {% set color = get_palette(member.full_name) %} - + {% if member.user_image %} diff --git a/lms/lms/widgets/CourseCard.html b/lms/lms/widgets/CourseCard.html index de74f2c0..0c541ad2 100644 --- a/lms/lms/widgets/CourseCard.html +++ b/lms/lms/widgets/CourseCard.html @@ -93,7 +93,7 @@ {% endif %} {% endfor %} - + {% if ins_len == 1 %} {{ instructors[0].full_name }} @@ -128,7 +128,7 @@ {% else %} - + {% endif %} {% endif %} diff --git a/lms/templates/emails/assignment_submission.html b/lms/templates/emails/assignment_submission.html index d3942aaa..96ff3d5b 100644 --- a/lms/templates/emails/assignment_submission.html +++ b/lms/templates/emails/assignment_submission.html @@ -4,7 +4,6 @@

{{ _(" Please evaluate and grade it.") }}


` - + {{ _("Open Assignment") }} - diff --git a/lms/templates/emails/batch_confirmation.html b/lms/templates/emails/batch_confirmation.html index d4cc26df..5176e5db 100644 --- a/lms/templates/emails/batch_confirmation.html +++ b/lms/templates/emails/batch_confirmation.html @@ -23,7 +23,7 @@

{{ _("Visit the following link to view your ") }} - {{ _("Batch Details") }} + {{ _("Batch Details") }}

{{ _("If you have any questions or require assistance, feel free to contact us.") }} @@ -32,4 +32,3 @@

{{ _("Best Regards") }}

- diff --git a/lms/templates/emails/batch_start_reminder.html b/lms/templates/emails/batch_start_reminder.html index de240de6..95443e6a 100644 --- a/lms/templates/emails/batch_start_reminder.html +++ b/lms/templates/emails/batch_start_reminder.html @@ -20,7 +20,7 @@


- 👉 {{ _("Visit your batch") }} + 👉 {{ _("Visit your batch") }}


diff --git a/lms/templates/emails/live_class_reminder.html b/lms/templates/emails/live_class_reminder.html index cd1db5ef..20865d7a 100644 --- a/lms/templates/emails/live_class_reminder.html +++ b/lms/templates/emails/live_class_reminder.html @@ -17,7 +17,7 @@


- 👉 {{ _("Visit your batch") }} + 👉 {{ _("Visit your batch") }}


@@ -26,4 +26,4 @@

{{ _("Best Regards") }} -

\ No newline at end of file +

diff --git a/lms/www/lms.py b/lms/www/_lms.py similarity index 88% rename from lms/www/lms.py rename to lms/www/_lms.py index fcda12cd..10a9b796 100644 --- a/lms/www/lms.py +++ b/lms/www/_lms.py @@ -5,6 +5,9 @@ from bs4 import BeautifulSoup from frappe import _ from frappe.utils.telemetry import capture +from lms.hooks import lms_path +from lms.lms.utils import get_lms_route + no_cache = 1 @@ -32,6 +35,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": lms_path, } ) @@ -85,7 +89,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 +98,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 +117,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 +144,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 +153,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 +171,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 +199,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 +229,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 +246,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 +268,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 +290,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 {}