Merge v2.46.0 into develop — resolve all conflicts

- yarn.lock, components.d.ts, ru.po: accepted upstream v2.46.0
- AppSidebar.vue: kept custom sidebar links (MyPoints, LeaderBoard,
  ChatGPT, MyChild, Profile) + adopted async watch from v2.46.0
- MobileLayout.vue: merged onMounted with sidebarSettings.reload +
  custom addSideBar() for role-based mobile links
- EditProfile.vue: adopted new Dialog options structure (size only)
- Courses/Courses.vue: unified tab values to lowercase (enrolled,
  upcoming, new, created, unpublished)
- CourseDetail.vue (old): removed — logic migrated to Courses/CourseDetail.vue;
  transferred custom tag flex-wrap styling to CourseOverview.vue
- LessonForm.vue: kept Rutube video support
- App.vue: clean (no conflict markers)
- user.py: merged imports + kept custom sign_up params (phone, user_role)
- utils.py: kept render_html (Rutube), is_mentor, is_eligible_to_review;
  added type hints for get_course_progress from v2.46.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Nicolai
2026-03-09 18:25:58 +03:00
430 changed files with 64129 additions and 47136 deletions

View File

@@ -1,16 +1,24 @@
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_description = "Open Source Learning Management System built with Frappe Framework"
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>
# ------------------
@@ -64,6 +72,9 @@ after_install = "lms.install.after_install"
after_sync = "lms.install.after_sync"
before_uninstall = "lms.install.before_uninstall"
setup_wizard_requires = "assets/lms/js/setup_wizard.js"
after_migrate = [
"lms.sqlite.build_index_in_background",
]
# Desk Notifications
# ------------------
@@ -75,13 +86,16 @@ setup_wizard_requires = "assets/lms/js/setup_wizard.js"
# -----------
# Permissions evaluated in scripted ways
# permission_query_conditions = {
# "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions",
# }
#
# has_permission = {
# "Event": "frappe.desk.doctype.event.event.has_permission",
# }
permission_query_conditions = {
"LMS Certificate": "lms.lms.doctype.lms_certificate.lms_certificate.get_permission_query_conditions",
}
has_permission = {
"LMS Live Class": "lms.lms.doctype.lms_live_class.lms_live_class.has_permission",
"LMS Batch": "lms.lms.doctype.lms_batch.lms_batch.has_permission",
"LMS Program": "lms.lms.doctype.lms_program.lms_program.has_permission",
"LMS Certificate": "lms.lms.doctype.lms_certificate.lms_certificate.has_permission",
}
# DocType Class
# ---------------
@@ -101,7 +115,10 @@ doc_events = {
"lms.lms.doctype.lms_badge.lms_badge.process_badges",
]
},
"Discussion Reply": {"after_insert": "lms.lms.utils.handle_notifications"},
"Discussion Reply": {
"after_insert": "lms.lms.utils.handle_notifications",
"validate": "lms.lms.utils.validate_discussion_reply",
},
"Notification Log": {"on_change": "lms.lms.utils.publish_notifications"},
"User": {
"validate": "lms.lms.user.validate_username_duplicates",
@@ -112,6 +129,9 @@ doc_events = {
# Scheduled Tasks
# ---------------
scheduler_events = {
"all": [
"lms.sqlite.build_index_in_background",
],
"hourly": [
"lms.lms.doctype.lms_certificate_request.lms_certificate_request.schedule_evals",
"lms.lms.api.update_course_statistics",
@@ -123,6 +143,7 @@ scheduler_events = {
"lms.lms.doctype.lms_payment.lms_payment.send_payment_reminder",
"lms.lms.doctype.lms_batch.lms_batch.send_batch_start_reminder",
"lms.lms.doctype.lms_live_class.lms_live_class.send_live_class_reminder",
"lms.lms.doctype.lms_course.lms_course.send_notification_for_published_courses",
],
}
@@ -153,7 +174,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",
@@ -162,24 +184,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 = [
@@ -188,17 +211,20 @@ update_website_context = [
jinja = {
"methods": [
"lms.lms.utils.get_signup_optin_checks",
"lms.lms.utils.get_tags",
"lms.lms.utils.get_lesson_count",
"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 = []
@@ -248,7 +274,11 @@ 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",
}
]
sqlite_search = ["lms.sqlite.LearningSearch"]
auth_hooks = ["lms.auth.authenticate"]
require_type_annotated_api_methods = True