Merge pull request #2108 from pateljannat/issues-185
fix: misc permission issues
This commit is contained in:
17
lms/hooks.py
17
lms/hooks.py
@@ -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
|
||||
# ---------------
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
// }
|
||||
});
|
||||
@@ -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": []
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2022, Frappe and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
|
||||
class TestJobSettings(unittest.TestCase):
|
||||
pass
|
||||
@@ -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
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)"""
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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."))
|
||||
|
||||
@@ -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},
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
5
lms/patches/v2_0/enable_allow_job_posting.py
Normal file
5
lms/patches/v2_0/enable_allow_job_posting.py
Normal file
@@ -0,0 +1,5 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
frappe.db.set_single_value("LMS Settings", "allow_job_posting", 1)
|
||||
7
lms/patches/v2_0/rename_badge_assignment_event.py
Normal file
7
lms/patches/v2_0/rename_badge_assignment_event.py
Normal 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")
|
||||
Reference in New Issue
Block a user