+
{{ __('This course has:') }}
diff --git a/frontend/src/components/Sidebar/Apps.vue b/frontend/src/components/Sidebar/Apps.vue
index 35ef2ae6..3e2e8545 100644
--- a/frontend/src/components/Sidebar/Apps.vue
+++ b/frontend/src/components/Sidebar/Apps.vue
@@ -48,7 +48,7 @@ const apps = createResource({
name: 'frappe',
logo: '/assets/lms/images/desk.png',
title: __('Desk'),
- route: '/desk/lms',
+ route: '/desk/learning',
},
]
data.map((app) => {
diff --git a/frontend/src/pages/Courses/CourseForm.vue b/frontend/src/pages/Courses/CourseForm.vue
index df1110d4..21ad70eb 100644
--- a/frontend/src/pages/Courses/CourseForm.vue
+++ b/frontend/src/pages/Courses/CourseForm.vue
@@ -164,7 +164,7 @@
:label="__('Preview Video')"
:placeholder="
__(
- 'Paste the youtube link of a short video introducing the course'
+ 'Paste a YouTube link of a short video introducing the course'
)
"
@input="makeFormDirty()"
diff --git a/frontend/src/utils/index.js b/frontend/src/utils/index.js
index 3f828dcf..989f0f2a 100644
--- a/frontend/src/utils/index.js
+++ b/frontend/src/utils/index.js
@@ -694,7 +694,6 @@ export const escapeHTML = (text) => {
'"': '"',
"'": ''',
'`': '`',
- '=': '=',
}
return String(text).replace(
@@ -707,6 +706,19 @@ export const sanitizeHTML = (text) => {
text = DOMPurify.sanitize(decodeEntities(text), {
ALLOWED_TAGS: [
'b',
+ 'br',
+ 'h1',
+ 'h2',
+ 'h3',
+ 'h4',
+ 'h5',
+ 'h6',
+ 'table',
+ 'thead',
+ 'tbody',
+ 'tr',
+ 'th',
+ 'td',
'i',
'em',
'strong',
@@ -717,6 +729,7 @@ export const sanitizeHTML = (text) => {
'ol',
'li',
'img',
+ 'blockquote',
],
ALLOWED_ATTR: ['href', 'target', 'src'],
})
diff --git a/lms/lms/doctype/lms_certificate/lms_certificate.py b/lms/lms/doctype/lms_certificate/lms_certificate.py
index abefd712..76f70fd3 100644
--- a/lms/lms/doctype/lms_certificate/lms_certificate.py
+++ b/lms/lms/doctype/lms_certificate/lms_certificate.py
@@ -164,7 +164,8 @@ def is_certified(course):
@frappe.whitelist()
def create_certificate(course: str):
- if is_certified(course):
+ certificate = is_certified(course)
+ if certificate:
return frappe.db.get_value(
"LMS Certificate", certificate, ["name", "course", "template"], as_dict=True
)
diff --git a/lms/lms/doctype/lms_certificate_request/lms_certificate_request.js b/lms/lms/doctype/lms_certificate_request/lms_certificate_request.js
index b4e20e60..7f034bda 100644
--- a/lms/lms/doctype/lms_certificate_request/lms_certificate_request.js
+++ b/lms/lms/doctype/lms_certificate_request/lms_certificate_request.js
@@ -16,7 +16,7 @@ frappe.ui.form.on("LMS Certificate Request", {
frappe.call({
method: "lms.lms.doctype.lms_certificate_request.lms_certificate_request.setup_calendar_event",
args: {
- eval: frm.doc,
+ eval_name: frm.doc.name,
},
});
});
diff --git a/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py b/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py
index af3a0122..69b99356 100644
--- a/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py
+++ b/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py
@@ -161,16 +161,20 @@ def schedule_evals():
},
["name", "member", "member_name", "evaluator", "date", "start_time", "end_time"],
)
- for eval in evals:
- setup_calendar_event(eval)
+ for evaluation in evals:
+ setup_calendar_event(evaluation.name)
@frappe.whitelist()
-def setup_calendar_event(eval: str):
- if isinstance(eval, str):
- eval = frappe._dict(json.loads(eval))
+def setup_calendar_event(eval_name: str):
+ evaluation = frappe.db.get_value(
+ "LMS Certificate Request",
+ eval_name,
+ ["name", "member", "member_name", "evaluator", "date", "start_time", "end_time"],
+ as_dict=1,
+ )
- is_member = eval.member == frappe.session.user
+ is_member = evaluation.member == frappe.session.user
roles = frappe.get_roles(frappe.session.user)
is_admin = "Moderator" in roles or "Batch Evaluator" in roles
@@ -180,29 +184,31 @@ def setup_calendar_event(eval: str):
frappe.PermissionError,
)
- calendar = frappe.db.get_value("Google Calendar", {"user": eval.evaluator, "enable": 1}, "name")
+ calendar = frappe.db.get_value("Google Calendar", {"user": evaluation.evaluator, "enable": 1}, "name")
if calendar:
- event = create_event(eval)
- add_participants(eval, event)
- update_meeting_details(eval, event, calendar)
+ event = create_event(evaluation)
+ add_participants(evaluation, event)
+ update_meeting_details(evaluation, event, calendar)
-def create_event(eval: dict):
+def create_event(evaluation: dict):
event = frappe.get_doc(
{
"doctype": "Event",
- "subject": f"Evaluation of {eval.member_name}",
- "starts_on": f"{eval.date} {eval.start_time}",
- "ends_on": f"{eval.date} {eval.end_time}",
+ "subject": f"Evaluation of {evaluation.member_name}",
+ "starts_on": f"{evaluation.date} {evaluation.start_time}",
+ "ends_on": f"{evaluation.date} {evaluation.end_time}",
+ "reference_doctype": "LMS Certificate Request",
+ "reference_docname": evaluation.name,
}
)
event.save()
return event
-def add_participants(eval: dict, event: Document):
- participants = [eval.member, eval.evaluator]
+def add_participants(evaluation: dict, event: Document):
+ participants = [evaluation.member, evaluation.evaluator]
for participant in participants:
contact_name = frappe.db.get_value("Contact", {"email_id": participant}, "name")
frappe.get_doc(
@@ -218,7 +224,7 @@ def add_participants(eval: dict, event: Document):
).save()
-def update_meeting_details(eval: dict, event: Document, calendar: str):
+def update_meeting_details(evaluation: dict, event: Document, calendar: str):
event.reload()
event.update(
{
@@ -230,7 +236,9 @@ def update_meeting_details(eval: dict, event: Document, calendar: str):
event.save()
event.reload()
- frappe.db.set_value("LMS Certificate Request", eval.name, "google_meet_link", event.google_meet_link)
+ frappe.db.set_value(
+ "LMS Certificate Request", evaluation.name, "google_meet_link", event.google_meet_link
+ )
@frappe.whitelist()
diff --git a/lms/lms/doctype/lms_course/lms_course.py b/lms/lms/doctype/lms_course/lms_course.py
index 15597a80..49d8168e 100644
--- a/lms/lms/doctype/lms_course/lms_course.py
+++ b/lms/lms/doctype/lms_course/lms_course.py
@@ -47,7 +47,9 @@ class LMSCourse(Document):
).save(ignore_permissions=True)
def validate_video_link(self):
- if self.video_link and "/" in self.video_link:
+ if self.video_link and "watch?v=" in self.video_link:
+ self.video_link = self.video_link.split("watch?v=")[-1]
+ elif self.video_link and "/" in self.video_link:
self.video_link = self.video_link.split("/")[-1]
def validate_status(self):
diff --git a/lms/workspace_sidebar/lms.json b/lms/workspace_sidebar/lms.json
new file mode 100644
index 00000000..85be264c
--- /dev/null
+++ b/lms/workspace_sidebar/lms.json
@@ -0,0 +1,116 @@
+{
+ "app": "lms",
+ "creation": "2025-11-24 14:35:18.461657",
+ "docstatus": 0,
+ "doctype": "Workspace Sidebar",
+ "header_icon": "education",
+ "idx": 0,
+ "items": [
+ {
+ "child": 0,
+ "collapsible": 1,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Home",
+ "link_to": "Learning",
+ "link_type": "Workspace",
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 0,
+ "collapsible": 1,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Users",
+ "link_to": "User",
+ "link_type": "DocType",
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 0,
+ "collapsible": 1,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Course",
+ "link_to": "LMS Course",
+ "link_type": "DocType",
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 0,
+ "collapsible": 1,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Enrollments",
+ "link_to": "LMS Enrollment",
+ "link_type": "DocType",
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 0,
+ "collapsible": 1,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Batch",
+ "link_to": "LMS Batch",
+ "link_type": "DocType",
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 0,
+ "collapsible": 1,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Batch Enrollment",
+ "link_to": "LMS Batch Enrollment",
+ "link_type": "DocType",
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 0,
+ "collapsible": 1,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Evaluation Request",
+ "link_to": "LMS Certificate Request",
+ "link_type": "DocType",
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 0,
+ "collapsible": 1,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Evaluation",
+ "link_to": "LMS Certificate Evaluation",
+ "link_type": "DocType",
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 0,
+ "collapsible": 1,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Certificate",
+ "link_to": "LMS Certificate",
+ "link_type": "DocType",
+ "show_arrow": 0,
+ "type": "Link"
+ }
+ ],
+ "modified": "2026-03-02 13:07:37.040316",
+ "modified_by": "sayali@frappe.io",
+ "module": "LMS",
+ "name": "LMS",
+ "owner": "Administrator",
+ "standard": 1,
+ "title": "LMS"
+}