From 8febe21aa8f3c1bcdb22fd8ed7e6cf898e61063d Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Tue, 3 Feb 2026 10:48:59 +0530 Subject: [PATCH 1/9] fix: dont allow contact us email sending to guest users --- frontend/src/utils/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/utils/index.js b/frontend/src/utils/index.js index e0a69d55..2261349c 100644 --- a/frontend/src/utils/index.js +++ b/frontend/src/utils/index.js @@ -513,7 +513,8 @@ const getSidebarItems = () => { : settings.data?.contact_us_email, condition: () => { return ( - settings?.data?.contact_us_email || + (settings?.data?.contact_us_email && + userResource?.data) || settings?.data?.contact_us_url ) }, From e4268d04372166adb3f97b6968565ca187794f78 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Tue, 3 Feb 2026 10:49:48 +0530 Subject: [PATCH 2/9] fix: allow attaching payment information for batch enrollment --- .../src/components/Controls/Autocomplete.vue | 21 +++-- .../src/components/Modals/StudentModal.vue | 78 ++++++++++--------- .../pages/Courses/CourseEnrollmentModal.vue | 8 +- 3 files changed, 56 insertions(+), 51 deletions(-) diff --git a/frontend/src/components/Controls/Autocomplete.vue b/frontend/src/components/Controls/Autocomplete.vue index 4da8274e..dee1268f 100644 --- a/frontend/src/components/Controls/Autocomplete.vue +++ b/frontend/src/components/Controls/Autocomplete.vue @@ -99,18 +99,17 @@ name="item-label" v-bind="{ active, selected, option }" > -
-
- {{ option.label }} +
+
+ {{ + option.value == option.label + ? option.description + : option.label + }} +
+
+ {{ option.value }}
-
diff --git a/frontend/src/components/Modals/StudentModal.vue b/frontend/src/components/Modals/StudentModal.vue index 6847b358..5c9c7b97 100644 --- a/frontend/src/components/Modals/StudentModal.vue +++ b/frontend/src/components/Modals/StudentModal.vue @@ -2,7 +2,7 @@ + diff --git a/frontend/src/pages/Courses/CourseEnrollmentModal.vue b/frontend/src/pages/Courses/CourseEnrollmentModal.vue index 7e69c589..ebc2f95d 100644 --- a/frontend/src/pages/Courses/CourseEnrollmentModal.vue +++ b/frontend/src/pages/Courses/CourseEnrollmentModal.vue @@ -19,8 +19,7 @@ placeholder=" " v-model="student" :required="true" - :allowCreate="true" - @create=" + :onCreate=" () => { openSettings('Members') show = false @@ -33,8 +32,7 @@ :label="__('Payment')" placeholder=" " v-model="payment" - :allowCreate="true" - @create=" + :onCreate=" () => { openSettings('Transactions') show = false @@ -54,9 +52,9 @@ diff --git a/frontend/src/pages/Home/StudentHome.vue b/frontend/src/pages/Home/StudentHome.vue index 40f66849..1c45e331 100644 --- a/frontend/src/pages/Home/StudentHome.vue +++ b/frontend/src/pages/Home/StudentHome.vue @@ -72,7 +72,7 @@
-
+
{{ diff --git a/lms/hooks.py b/lms/hooks.py index 26f1ce31..2a289ce1 100644 --- a/lms/hooks.py +++ b/lms/hooks.py @@ -3,7 +3,7 @@ import frappe from . import __version__ as app_version app_name = "frappe_lms" -app_title = "Frappe LMS" +app_title = "Learning" app_publisher = "Frappe" app_description = "Frappe LMS App" app_icon_url = "/assets/lms/images/lms-logo.png" diff --git a/lms/lms/api.py b/lms/lms/api.py index d7ac8173..9421853e 100644 --- a/lms/lms/api.py +++ b/lms/lms/api.py @@ -1654,8 +1654,12 @@ def get_progress_distribution(progressList): "value": len([p for p in progressList if 30 <= p < 60]), }, { - "name": "Advanced (60-100%)", - "value": len([p for p in progressList if 60 <= p <= 100]), + "name": "Advanced (60-99%)", + "value": len([p for p in progressList if 60 <= p < 100]), + }, + { + "name": "Completed (100%)", + "value": len([p for p in progressList if p == 100]), }, ] @@ -2048,13 +2052,17 @@ def get_lesson_completion_stats(course): Lesson = frappe.qb.DocType("Course Lesson") rows = ( - frappe.qb.from_(CourseProgress) - .join(LessonReference) - .on(CourseProgress.lesson == LessonReference.lesson) + frappe.qb.from_(LessonReference) .join(ChapterReference) .on(LessonReference.parent == ChapterReference.chapter) .join(Lesson) - .on(CourseProgress.lesson == Lesson.name) + .on(LessonReference.lesson == Lesson.name) + .left_join(CourseProgress) + .on( + (CourseProgress.lesson == LessonReference.lesson) + & (CourseProgress.course == course) + & (CourseProgress.status == "Complete") + ) .select( LessonReference.idx, ChapterReference.idx.as_("chapter_idx"), @@ -2063,10 +2071,132 @@ def get_lesson_completion_stats(course): Lesson.name.as_("lesson_name"), fn.Count(CourseProgress.name).as_("completion_count"), ) - .where((CourseProgress.course == course) & (CourseProgress.status == "Complete")) - .groupby(CourseProgress.lesson) + .where(ChapterReference.parent == course) + .groupby(LessonReference.lesson) .orderby(ChapterReference.idx, LessonReference.idx) .run(as_dict=True) ) return rows + + +@frappe.whitelist() +def get_course_assessment_progress(course, member): + if not can_modify_course(course): + frappe.throw( + _("You do not have permission to access this course's assessment data."), frappe.PermissionError + ) + + quizzes = get_course_quiz_progress(course, member) + assignments = get_course_assignment_progress(course, member) + programming_exercises = get_course_programming_exercise_progress(course, member) + + return { + "quizzes": quizzes, + "assignments": assignments, + "exercises": programming_exercises, + } + + +def get_course_quiz_progress(course, member): + quizzes = get_assessment_from_lesson(course, "quiz") + attempts = [] + + for quiz in quizzes: + submissions = frappe.get_all( + "LMS Quiz Submission", + { + "quiz": quiz, + "member": member, + }, + ["name", "score", "percentage", "quiz", "quiz_title"], + order_by="creation desc", + limit=1, + ) + if len(submissions): + attempts.append(submissions[0]) + else: + attempts.append( + { + "quiz": quiz, + "quiz_title": frappe.db.get_value("LMS Quiz", quiz, "title"), + "score": 0, + "percentage": 0, + } + ) + + return attempts + + +def get_course_assignment_progress(course, member): + assignments = get_assessment_from_lesson(course, "assignment") + submissions = [] + + for assignment in assignments: + assignment_subs = frappe.get_all( + "LMS Assignment Submission", + { + "assignment": assignment, + "member": member, + }, + ["name", "status", "assignment", "assignment_title"], + order_by="creation desc", + limit=1, + ) + if len(assignment_subs): + submissions.append(assignment_subs[0]) + else: + submissions.append( + { + "assignment": assignment, + "assignment_title": frappe.db.get_value("LMS Assignment", assignment, "title"), + "status": "Not Submitted", + } + ) + + return submissions + + +def get_course_programming_exercise_progress(course, member): + exercises = get_assessment_from_lesson(course, "program") + submissions = [] + + for exercise in exercises: + exercise_subs = frappe.get_all( + "LMS Programming Exercise Submission", + { + "exercise": exercise, + "member": member, + }, + ["name", "status", "exercise", "exercise_title"], + order_by="creation desc", + limit=1, + ) + if len(exercise_subs): + submissions.append(exercise_subs[0]) + else: + submissions.append( + { + "exercise": exercise, + "exercise_title": frappe.db.get_value("LMS Programming Exercise", exercise, "title"), + "status": "Not Attempted", + } + ) + + return submissions + + +def get_assessment_from_lesson(course, assessmentType): + assessments = [] + lessons = frappe.get_all("Course Lesson", {"course": course}, ["name", "title", "content"]) + + for lesson in lessons: + if lesson.content: + content = json.loads(lesson.content) + for block in content.get("blocks", []): + if block.get("type") == assessmentType: + data_field = "exercise" if assessmentType == "program" else assessmentType + quiz_name = block.get("data", {}).get(data_field) + assessments.append(quiz_name) + + return assessments diff --git a/lms/lms/utils.py b/lms/lms/utils.py index 052efbdc..8c05ba82 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -205,6 +205,10 @@ def get_lesson_icon(body, content): if block.get("type") == "quiz": return "icon-quiz" + if block.get("type") == "assignment": + return "icon-assignment" + if block.get("type") == "program": + return "icon-code" return "icon-list" From 9814abf55f56fa555a5f5f34b0c63b3b1820f840 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Wed, 4 Feb 2026 16:04:28 +0530 Subject: [PATCH 5/9] fix: dark mode issues of course dashboard --- frontend/src/components/NumberChartGraph.vue | 2 +- .../src/pages/Courses/CourseDashboard.vue | 6 +-- .../pages/Courses/StudentCourseProgress.vue | 46 +++++++++++++++---- 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/NumberChartGraph.vue b/frontend/src/components/NumberChartGraph.vue index 51c771c3..67fdd508 100644 --- a/frontend/src/components/NumberChartGraph.vue +++ b/frontend/src/components/NumberChartGraph.vue @@ -5,7 +5,7 @@
-
+
{{ value }}
diff --git a/frontend/src/pages/Courses/CourseDashboard.vue b/frontend/src/pages/Courses/CourseDashboard.vue index 5bb8680f..186c29f1 100644 --- a/frontend/src/pages/Courses/CourseDashboard.vue +++ b/frontend/src/pages/Courses/CourseDashboard.vue @@ -22,7 +22,7 @@
-
+
{{ __('Students') }}
@@ -132,7 +132,7 @@
-
+