mirror of
https://github.com/frappe/lms.git
synced 2026-05-02 13:39:31 +03:00
Merge pull request #2001 from frappe/feat/config-fe-base-path
feat: configurable frontend base path
This commit is contained in:
@@ -12,4 +12,5 @@ node_modules
|
||||
package-lock.json
|
||||
lms/public/frontend
|
||||
lms/www/lms.html
|
||||
lms/www/_lms.html
|
||||
frappe-ui
|
||||
@@ -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": {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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`)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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,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,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
|
||||
}
|
||||
|
||||
@@ -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}`
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'>
|
||||
|
||||
@@ -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
@@ -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
@@ -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])
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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`;
|
||||
};
|
||||
|
||||
@@ -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
@@ -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
@@ -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,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 }}">
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {}
|
||||
Reference in New Issue
Block a user