Merge pull request #2108 from pateljannat/issues-185

fix: misc permission issues
This commit is contained in:
Jannat Patel
2026-02-23 11:22:52 +05:30
committed by GitHub
45 changed files with 541 additions and 480 deletions

View File

@@ -86,13 +86,16 @@ after_migrate = [
# -----------
# 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
# ---------------

View File

@@ -130,7 +130,7 @@
}
],
"make_attachments_public": 1,
"modified": "2025-12-02 16:58:49.903274",
"modified": "2026-02-19 14:26:14.027340",
"modified_by": "sayali@frappe.io",
"module": "Job",
"name": "Job Opportunity",
@@ -149,24 +149,16 @@
"share": 1,
"write": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "LMS Student",
"select": 1,
"share": 1
},
{
"create": 1,
"email": 1,
"export": 1,
"if_owner": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "LMS Student",
"select": 1,
"share": 1,
"write": 1
}

View File

@@ -1,7 +0,0 @@
// Copyright (c) 2022, Frappe and contributors
// For license information, please see license.txt
frappe.ui.form.on("Job Settings", {
// refresh: function(frm) {
// }
});

View File

@@ -1,54 +0,0 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2022-02-07 12:01:41.422955",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"allow_posting",
"title",
"subtitle"
],
"fields": [
{
"default": "0",
"fieldname": "allow_posting",
"fieldtype": "Check",
"label": "Allow Job Posting From Website"
},
{
"fieldname": "title",
"fieldtype": "Data",
"label": "Job Board Title"
},
{
"fieldname": "subtitle",
"fieldtype": "Data",
"label": "Job Board Subtitle"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2022-02-11 15:56:38.958317",
"modified_by": "Administrator",
"module": "Job",
"name": "Job Settings",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@@ -1,9 +0,0 @@
# Copyright (c) 2022, Frappe and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class JobSettings(Document):
pass

View File

@@ -1,9 +0,0 @@
# Copyright (c) 2022, Frappe and Contributors
# See license.txt
# import frappe
import unittest
class TestJobSettings(unittest.TestCase):
pass

View File

@@ -31,6 +31,7 @@ from pypika import functions as fn
from lms.lms.doctype.course_lesson.course_lesson import save_progress
from lms.lms.utils import (
LMS_ROLES,
can_modify_batch,
can_modify_course,
get_average_rating,
@@ -41,6 +42,7 @@ from lms.lms.utils import (
get_lms_route,
has_course_instructor_role,
has_evaluator_role,
has_lms_role,
has_moderator_role,
)
@@ -606,12 +608,7 @@ def check_app_permission():
if frappe.session.user == "Administrator":
return True
roles = frappe.get_roles()
lms_roles = ["Moderator", "Course Creator", "Batch Evaluator", "LMS Student"]
if any(role in roles for role in lms_roles):
return True
return False
return has_lms_role()
@frappe.whitelist()
@@ -1296,6 +1293,7 @@ def get_lms_settings():
"contact_us_url",
"livecode_url",
"disable_pwa",
"allow_job_posting",
]
settings = frappe._dict()
@@ -1308,7 +1306,6 @@ def get_lms_settings():
@frappe.whitelist()
def cancel_evaluation(evaluation: dict):
evaluation = frappe._dict(evaluation)
print(evaluation.member, frappe.session.user)
if evaluation.member != frappe.session.user:
frappe.throw(_("You do not have permission to cancel this evaluation."), frappe.PermissionError)
@@ -1369,6 +1366,9 @@ def get_certification_details(course: str):
@frappe.whitelist()
def save_role(user: str, role: str, value: int):
frappe.only_for("Moderator")
if role not in LMS_ROLES:
frappe.throw(_("You do not have permission to modify this role."), frappe.PermissionError)
if cint(value):
doc = frappe.get_doc(
{
@@ -1716,8 +1716,12 @@ def get_profile_details(username: str):
],
as_dict=True,
)
details.roles = frappe.get_roles(details.name)
roles = frappe.get_roles(details.name)
if not has_lms_role():
frappe.throw(
_("User does not have permission to access this user's profile details."), frappe.PermissionError
)
details.roles = roles
return details
@@ -2204,3 +2208,17 @@ def get_assessment_from_lesson(course: str, assessmentType: str):
assessments.append(quiz_name)
return assessments
@frappe.whitelist()
def get_badges(member: str):
if not has_lms_role():
frappe.throw(_("You do not have permission to access badges."), frappe.PermissionError)
badges = frappe.get_all(
"LMS Badge Assignment",
{"member": member},
["name", "member", "badge", "badge_image", "badge_description", "issued_on"],
)
return badges

View File

@@ -5,7 +5,7 @@ frappe.ui.form.on("LMS Badge", {
refresh: (frm) => {
frm.events.set_field_options(frm);
if (frm.doc.event == "Auto Assign") {
if (frm.doc.event == "Manual Assignment" && frm.doc.enabled) {
add_assign_button(frm);
}
},
@@ -49,11 +49,13 @@ const add_assign_button = (frm) => {
frappe.call({
method: "lms.lms.doctype.lms_badge.lms_badge.assign_badge",
args: {
badge: frm.doc,
badge_name: frm.doc.name,
},
callback: function (r) {
if (r.message) {
frappe.msgprint(r.message);
if (r.message == "success") {
frappe.toast(__("Badge assigned successfully"));
} else {
frappe.toast(__("Failed to assign badge"));
}
},
});

View File

@@ -52,14 +52,14 @@
"fieldtype": "Select",
"in_list_view": 1,
"label": "Event",
"options": "New\nValue Change\nAuto Assign",
"options": "New\nValue Change\nManual Assignment",
"reqd": 1
},
{
"fieldname": "condition",
"fieldtype": "Code",
"label": "Condition",
"mandatory_depends_on": "eval:doc.event == \"Auto Assign\""
"reqd": 1
},
{
"depends_on": "eval:doc.event == 'Value Change'",
@@ -100,7 +100,7 @@
"link_fieldname": "badge"
}
],
"modified": "2026-02-03 10:52:37.122370",
"modified": "2026-02-20 17:58:25.924109",
"modified_by": "sayali@frappe.io",
"module": "LMS",
"name": "LMS Badge",
@@ -131,15 +131,6 @@
"role": "Moderator",
"share": 1,
"write": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "LMS Student",
"share": 1
}
],
"row_format": "Dynamic",

View File

@@ -10,7 +10,7 @@ from frappe.model.document import Document
class LMSBadge(Document):
def on_update(self):
if self.event == "Auto Assign" and self.condition:
if self.event == "Manual Assignment" and self.condition:
try:
json.loads(self.condition)
except ValueError:
@@ -54,6 +54,7 @@ def award(doc, member):
}
)
assignment.save()
return assignment.name
def eval_condition(doc, condition):
@@ -61,16 +62,30 @@ def eval_condition(doc, condition):
@frappe.whitelist()
def assign_badge(badge: str, user: str):
badge = frappe._dict(json.loads(badge))
if not badge.event == "Auto Assign":
def assign_badge(badge_name: str):
assignments = []
badge = frappe.db.get_value(
"LMS Badge",
badge_name,
["name", "event", "reference_doctype", "condition", "user_field"],
as_dict=True,
)
if not badge:
frappe.throw(_("Badge {0} not found").format(badge_name), frappe.DoesNotExistError)
if not badge.event == "Manual Assignment":
return
fields = ["name"]
fields.append(badge.user_field)
list = frappe.get_all(badge.reference_doctype, filters=badge.condition, fields=fields)
for doc in list:
award(badge, doc.get(badge.user_field))
docs = frappe.get_all(badge.reference_doctype, filters=json.loads(badge.condition), fields=fields)
for doc in docs:
assignment_name = award(badge, doc.get(badge.user_field))
if assignment_name:
assignments.append(assignment_name)
return "success" if assignments else "failed"
def process_badges(doc, state):

View File

@@ -84,7 +84,7 @@
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-12-04 17:06:26.090276",
"modified": "2026-02-19 15:06:08.389081",
"modified_by": "sayali@frappe.io",
"module": "LMS",
"name": "LMS Badge Assignment",
@@ -120,10 +120,6 @@
"read": 1,
"role": "LMS Student"
},
{
"read": 1,
"role": "LMS Student"
},
{
"create": 1,
"delete": 1,

View File

@@ -10,17 +10,17 @@ from lms.lms.doctype.lms_badge.lms_badge import eval_condition
class LMSBadgeAssignment(Document):
def validate(self):
self.validate_owner()
self.validate_duplicate_badge_assignment()
self.validate_badge_criteria()
self.validate_owner()
def validate_owner(self):
if self.owner == self.member:
return
roles = frappe.get_roles(self.owner)
if "Moderator" not in roles:
frappe.throw(_("You must be a Moderator to assign badges to users."))
event = frappe.db.get_value("LMS Badge", self.badge, "event")
if event == "Manual Assignment":
roles = frappe.get_roles(frappe.session.user)
admins = ["Moderator", "Course Creator", "Batch Evaluator"]
if not any(role in roles for role in admins):
frappe.throw(_("You must be an Admin to assign badges to users."))
def validate_duplicate_badge_assignment(self):
grant_only_once = frappe.db.get_value("LMS Badge", self.badge, "grant_only_once")
@@ -40,25 +40,27 @@ class LMSBadgeAssignment(Document):
"LMS Badge", self.badge, ["reference_doctype", "user_field", "condition", "enabled"], as_dict=True
)
if badge_details:
if badge_details.reference_doctype and badge_details.user_field and badge_details.condition:
user_fieldname = frappe.db.get_value(
"DocField",
{"parent": badge_details.reference_doctype, "fieldname": badge_details.user_field},
"fieldname",
if not badge_details:
return
if badge_details.reference_doctype and badge_details.user_field and badge_details.condition:
user_fieldname = frappe.db.get_value(
"DocField",
{"parent": badge_details.reference_doctype, "fieldname": badge_details.user_field},
"fieldname",
)
documents = frappe.get_all(
badge_details.reference_doctype,
{user_fieldname: self.member},
)
for document in documents:
reference_value = eval_condition(
frappe.get_doc(badge_details.reference_doctype, document.name),
badge_details.condition,
)
if reference_value:
return
documents = frappe.get_all(
badge_details.reference_doctype,
{user_fieldname: self.member},
)
for document in documents:
reference_value = eval_condition(
frappe.get_doc(badge_details.reference_doctype, document.name),
badge_details.condition,
)
if reference_value:
return
frappe.throw(_("Member does not meet the criteria for the badge {0}.").format(self.badge))
frappe.throw(_("Member does not meet the criteria for the badge {0}.").format(self.badge))

View File

@@ -20,6 +20,7 @@ from lms.lms.utils import (
get_lesson_url,
get_lms_route,
get_quiz_details,
guest_access_allowed,
update_payment_record,
)
@@ -213,6 +214,10 @@ def create_live_class(
auto_recording: str,
description: str = None,
):
roles = frappe.get_roles()
if not any(role in roles for role in ["Moderator", "Batch Evaluator"]):
frappe.throw(_("You do not have permission to create a live class."))
payload = {
"topic": title,
"start_time": format_datetime(f"{date} {time}", "yyyy-MM-ddTHH:mm:ssZ"),
@@ -391,3 +396,26 @@ def send_mail(batch, student):
args=args,
header=[_(f"Batch Start Reminder: {batch.title}"), "orange"],
)
def has_permission(doc, ptype="read", user=None):
user = user or frappe.session.user
if user == "Guest" and not guest_access_allowed():
return False
roles = frappe.get_roles(user)
if "Moderator" in roles or "Batch Evaluator" in roles:
return True
if ptype not in ("read", "select", "print"):
return False
is_enrolled = frappe.db.exists("LMS Batch Enrollment", {"batch": doc.name, "member": user})
if is_enrolled:
return True
is_batch_published = frappe.db.get_value("LMS Batch", doc.name, "published")
if is_batch_published:
return True
return False

View File

@@ -123,7 +123,7 @@
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-12-17 16:50:31.128747",
"modified": "2026-02-20 17:32:34.580862",
"modified_by": "sayali@frappe.io",
"module": "LMS",
"name": "LMS Certificate",
@@ -153,27 +153,6 @@
"share": 1,
"write": 1
},
{
"create": 1,
"email": 1,
"export": 1,
"if_owner": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "LMS Student",
"share": 1,
"write": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "LMS Student",
"share": 1
},
{
"create": 1,
"delete": 1,
@@ -197,6 +176,15 @@
"role": "Course Creator",
"share": 1,
"write": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "LMS Student",
"share": 1
}
],
"row_format": "Dynamic",

View File

@@ -12,6 +12,7 @@ from frappe.utils.telemetry import capture
class LMSCertificate(Document):
def validate(self):
self.validate_criteria()
self.validate_duplicate_certificate()
def autoname(self):
@@ -54,6 +55,43 @@ class LMSCertificate(Document):
header=[subject, "green"],
)
def validate_criteria(self):
self.validate_role_of_owner()
self.validate_batch_enrollment()
self.validate_course_enrollment()
def validate_role_of_owner(self):
roles = frappe.get_roles()
is_admin = any(role in roles for role in ["Moderator", "Course Creator", "Batch Evaluator"])
if not self.course and not self.batch_name and not is_admin:
frappe.throw(_("Course or Batch is required to issue a certificate."))
def validate_batch_enrollment(self):
if self.batch_name:
is_enrolled = frappe.db.exists(
"LMS Batch Enrollment", {"batch": self.batch_name, "member": self.member}
)
if not is_enrolled:
frappe.throw(_("Certification cannot be issued as the member is not enrolled in this batch."))
def validate_course_enrollment(self):
if self.course:
is_enrolled = frappe.db.exists("LMS Enrollment", {"course": self.course, "member": self.member})
if not is_enrolled:
frappe.throw(
_("Certification cannot be issued as the member is not enrolled in this course.")
)
completion_certificate = frappe.db.get_value("LMS Course", self.course, "enable_certification")
if completion_certificate:
progress = frappe.db.get_value(
"LMS Enrollment", {"course": self.course, "member": self.member}, "progress"
)
if progress < 100:
frappe.throw(
_("Certification cannot be issued as the member has not completed the course.")
)
def validate_duplicate_certificate(self):
self.validate_course_duplicates()
self.validate_batch_duplicates()
@@ -177,3 +215,23 @@ def validate_certification_eligibility(course):
)
if progress < 100:
frappe.throw(_("You have not completed the course yet."))
def has_permission(doc, ptype="read", user=None):
user = user or frappe.session.user
roles = frappe.get_roles(user)
if "Moderator" in roles or "Course Creator" in roles or "Batch Evaluator" in roles:
return True
if doc.owner == user:
return True
if ptype not in ("read", "select", "print"):
return False
return doc.published
def get_permission_query_conditions(user):
user = user or frappe.session.user
roles = frappe.get_roles(user)
if "Moderator" in roles or "Course Creator" in roles or "Batch Evaluator" in roles:
return None
return """(`tabLMS Certificate`.published = 1)"""

View File

@@ -157,7 +157,7 @@
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-11-10 11:40:50.679211",
"modified": "2026-02-19 16:01:43.810407",
"modified_by": "sayali@frappe.io",
"module": "LMS",
"name": "LMS Certificate Request",
@@ -192,6 +192,7 @@
"create": 1,
"email": 1,
"export": 1,
"if_owner": 1,
"print": 1,
"read": 1,
"report": 1,

View File

@@ -41,7 +41,7 @@
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2026-01-29 16:10:47.787285",
"modified": "2026-02-20 17:40:39.823017",
"modified_by": "sayali@frappe.io",
"module": "LMS",
"name": "LMS Course Review",
@@ -63,8 +63,8 @@
"create": 1,
"email": 1,
"export": 1,
"if_owner": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "LMS Student",
"share": 1,

View File

@@ -8,7 +8,7 @@ from frappe.utils import ceil
class LMSEnrollment(Document):
def validate(self):
def before_insert(self):
self.validate_duplicate_enrollment()
self.validate_course_enrollment_eligibility()
self.validate_owner()
@@ -27,6 +27,7 @@ class LMSEnrollment(Document):
{
"course": self.course,
"member": self.member,
"name": ["!=", self.name],
},
)
@@ -49,7 +50,10 @@ class LMSEnrollment(Document):
)
if self.enrollment_from_batch:
return
if frappe.db.exists(
"LMS Batch Enrollment", {"batch": self.enrollment_from_batch, "member": self.member}
):
return
if not course_details.published and not is_admin():
frappe.throw(_("You cannot enroll in an unpublished course."))

View File

@@ -169,3 +169,18 @@ def get_minutes(duration_in_seconds):
if duration_in_seconds:
return int(duration_in_seconds) // 60
return 0
def has_permission(doc, ptype="read", user=None):
user = user or frappe.session.user
roles = frappe.get_roles(user)
if "Moderator" in roles or "Batch Evaluator" in roles:
return True
if ptype not in ("read", "select", "print"):
return False
return frappe.db.exists(
"LMS Batch Enrollment",
{"batch": doc.batch_name, "member": user},
)

View File

@@ -5,6 +5,8 @@ import frappe
from frappe import _
from frappe.model.document import Document
from lms.lms.utils import guest_access_allowed
class LMSProgram(Document):
def validate(self):
@@ -41,3 +43,27 @@ class LMSProgram(Document):
if self.member_count != member_count:
self.member_count = member_count
def has_permission(doc, ptype="read", user=None):
user = user or frappe.session.user
if user == "Guest" and not guest_access_allowed():
return False
roles = frappe.get_roles(user)
if "Moderator" in roles or "Course Creator" in roles:
return True
if ptype not in ("read", "select", "print"):
return False
is_enrolled = frappe.db.exists("LMS Program Member", {"parent": doc.name, "member": user})
if is_enrolled:
return True
is_program_published = frappe.db.get_value("LMS Program", doc.name, "published")
if is_program_published:
return True
return False

View File

@@ -88,7 +88,7 @@
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-06-24 14:42:08.288983",
"modified": "2026-02-20 14:43:56.587110",
"modified_by": "sayali@frappe.io",
"module": "LMS",
"name": "LMS Programming Exercise Submission",
@@ -146,6 +146,7 @@
"create": 1,
"email": 1,
"export": 1,
"if_owner": 1,
"print": 1,
"read": 1,
"report": 1,

View File

@@ -113,11 +113,12 @@
"read_only": 1
}
],
"grid_page_length": 50,
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-10-07 16:52:04.162521",
"modified_by": "Administrator",
"modified": "2026-02-19 16:31:23.401819",
"modified_by": "sayali@frappe.io",
"module": "LMS",
"name": "LMS Quiz Submission",
"owner": "Administrator",
@@ -133,21 +134,12 @@
"role": "System Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "LMS Student",
"share": 1
}
],
"row_format": "Dynamic",
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "member_name",
"track_changes": 1
}
}

View File

@@ -76,7 +76,9 @@
"contact_us_tab",
"contact_us_email",
"column_break_gcgv",
"contact_us_url"
"contact_us_url",
"jobs_tab",
"allow_job_posting"
],
"fields": [
{
@@ -471,13 +473,24 @@
{
"fieldname": "column_break_dtns",
"fieldtype": "Column Break"
},
{
"fieldname": "jobs_tab",
"fieldtype": "Tab Break",
"label": "Jobs"
},
{
"default": "1",
"fieldname": "allow_job_posting",
"fieldtype": "Check",
"label": "Allow Job Posting"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2026-01-01 19:36:54.443390",
"modified": "2026-02-19 16:28:15.310145",
"modified_by": "sayali@frappe.io",
"module": "LMS",
"name": "LMS Settings",
@@ -493,13 +506,6 @@
"share": 1,
"write": 1
},
{
"email": 1,
"print": 1,
"read": 1,
"role": "LMS Student",
"share": 1
},
{
"create": 1,
"delete": 1,

View File

@@ -102,7 +102,7 @@
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-07-30 14:38:52.555010",
"modified": "2026-02-20 12:02:29.458645",
"modified_by": "sayali@frappe.io",
"module": "LMS",
"name": "LMS Video Watch Duration",
@@ -120,17 +120,6 @@
"share": 1,
"write": 1
},
{
"create": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "LMS Student",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
@@ -154,6 +143,18 @@
"role": "Course Creator",
"share": 1,
"write": 1
},
{
"create": 1,
"email": 1,
"export": 1,
"if_owner": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "LMS Student",
"share": 1,
"write": 1
}
],
"row_format": "Dynamic",

View File

@@ -31,6 +31,7 @@ from pypika import functions as fn
from lms.lms.md import find_macros
RE_SLUG_NOTALLOWED = re.compile("[^a-z0-9]+")
LMS_ROLES = ["Moderator", "Course Creator", "Batch Evaluator", "LMS Student"]
def get_lms_path():
@@ -1209,6 +1210,9 @@ def get_country_code():
@frappe.whitelist()
def get_question_details(question: str) -> dict:
if not has_lms_role():
frappe.throw(_("You are not authorized to view the question details."))
fields = ["question", "type", "multiple"]
for i in range(1, 5):
fields.append(f"option_{i}")
@@ -1239,6 +1243,10 @@ def get_batch_courses(batch: str) -> list:
@frappe.whitelist()
def get_assessments(batch: str) -> list:
member = frappe.session.user
is_enrolled = frappe.db.exists("LMS Batch Enrollment", {"batch": batch, "member": member})
if not is_enrolled and not can_modify_batch(batch):
frappe.throw(_("You are not authorized to view the assessments of this batch."))
assessments = frappe.get_all(
"LMS Assessment",
{"parent": batch},
@@ -2297,3 +2305,10 @@ def can_modify_batch(batch: str) -> bool:
if not (has_moderator_role() or is_instructor):
return False
return True
def has_lms_role():
roles = frappe.get_roles()
lms_roles = set(LMS_ROLES)
user_roles = set(roles)
return not lms_roles.isdisjoint(user_roles)

View File

@@ -117,4 +117,6 @@ lms.patches.v2_0.fix_job_application_resume_urls
lms.patches.v2_0.open_to_opportunities
lms.patches.v2_0.open_to_work
lms.patches.v2_0.share_enrollment
lms.patches.v2_0.give_user_list_permission #11-02-2026
lms.patches.v2_0.give_user_list_permission #11-02-2026
lms.patches.v2_0.rename_badge_assignment_event
lms.patches.v2_0.enable_allow_job_posting

View File

@@ -0,0 +1,5 @@
import frappe
def execute():
frappe.db.set_single_value("LMS Settings", "allow_job_posting", 1)

View File

@@ -0,0 +1,7 @@
import frappe
def execute():
badge_with_auto_assign = frappe.get_all("LMS Badge", filters={"event": "Auto Assign"}, fields=["name"])
for badge in badge_with_auto_assign:
frappe.db.set_value("LMS Badge", badge.name, "event", "Manual Assignment")